Change the newRepoDownloaded signal.
[meego-garage:garage-client-services.git] / src / catalog.cpp
1 #include "catalog.h"
2 #include <QTimer>
3 #include <QtDebug>
4 #include <QFile>
5 #include <QStringList>
6 #include <QListIterator>
7 #include "applicationmanager.h"
8 #include <garageclientservices.h>
9 #include <errorhandler.h>
10 #include "category.h"
11 #include "garagesettings.h"
12
13 using namespace MeeGoGarage;
14
15 const char Catalog::XML_APP[] = "applications";
16 const char Catalog::XML_RAT[] = "ratings";
17 const char Catalog::XML_CAT[] = "categories";
18 const char Catalog::XML_USER[] = "userprofile";
19 const char Catalog::XML_FEATURE[] = "features";
20 const char Catalog::XML_TXN[] = "transactions";
21 const char Catalog::XML_REPO[] = "repositories";
22
23 Catalog::Catalog(QObject *parent) :
24     QObject(parent), categoryListIsReady(false), applicationListIsReady(false)
25 {
26 }
27
28 Catalog::Catalog(const Catalog::CatalogSetting& setting, QObject *parent) :
29     QObject(parent), categoryListIsReady(false), applicationListIsReady(false)
30 {
31     catalogSetting = setting;
32
33     //Create networKAccessManager
34     networkAccessManager = new QNetworkAccessManager(this);
35
36     if (setting.sourceType == Catalog::WebService) {
37         qDebug() << "Webservice is not supported now.";
38     } else if (setting.sourceType == Catalog::RemoteXml) {
39
40         networkAccessManager->setProxy(setting.remoteXmlProxy);
41
42         QNetworkReply *networkReply = networkAccessManager->get(QNetworkRequest(setting.remoteXmlUrl));
43         qDebug() << "Catalog: requesting " << catalogSetting.remoteXmlUrl;
44         connect(networkReply, SIGNAL(finished()), SLOT(catalogXmlFileDownloaded()));
45         connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
46                 SLOT(xmlDownloadReplyError(QNetworkReply::NetworkError)));
47
48         return;
49     }
50
51     //We can not emit the ready signal immediately but delay to after gcs is ready
52     QTimer *timer = new QTimer(this);
53     timer->setSingleShot(TRUE);
54     connect(timer, SIGNAL(timeout()), SLOT(loadStaticGarageSource()));
55     timer->start(0);
56
57     return;
58 }
59
60 void Catalog::loadStaticGarageSource()
61 {
62     networkAccessManager->setProxy(catalogSetting.metadataProxy);
63     loadGarageSource(catalogSetting.staticXmlFilePath);
64 }
65
66 QNetworkAccessManager* Catalog::getNetworkAccessManager()
67 {
68     return networkAccessManager;
69 }
70
71 bool Catalog::add(Catalog* cat)
72 {
73     if (!cat->isApplicationListReady() || !cat->isCategoryListReady())
74         return false;
75
76     if (categoryList.count() == 0)
77         categoryList.append(cat->getCategoryList());
78     else {
79         QList<Category*>::iterator i;
80         QList<Category*> catList = cat->getCategoryList();
81         for (i = catList.begin(); i != catList.end(); i++) {
82             bool merged = false;
83
84             Category* cat1 = *i;
85             QList<Category*>::iterator j;
86             for (j = categoryList.begin(); j != categoryList.end(); j++) {
87                 Category* cat2 = *j;
88                 merged = cat2->merge(cat1);
89                 if (merged)
90                     break;
91             }
92
93             if (!merged) {
94                 categoryList.append(cat1);
95             }
96         }
97     }
98
99     emit categoryListUpdate();
100
101     //Merge all applicaitons
102     if (applicationList.count() == 0)
103         applicationList.append(cat->getApplicationList());
104     else {
105         QList<Application*> appList = cat->getApplicationList();
106         QList<Application*>::iterator m;
107         for (m = appList.begin(); m != appList.end(); m++) {
108             Application* app1 = *m;
109             bool exist = false;
110
111             QList<Application*>::iterator n;
112             for (n = applicationList.begin(); n != applicationList.end(); n++) {
113                 Application* app2 = *n;
114                 if (app2->getPackageName() == app1->getPackageName()) {
115                     exist = true;
116                     break;
117                 }
118             }
119
120             if (!exist)
121                 applicationList.append(app1);
122         }
123     }
124
125     emit applicationListUpdate();
126
127     return true;
128 }
129
130 void Catalog::updateCatalogData()
131 {
132     qDebug() << newRepoUrl.toString() << "  " << newRepoTimestamp.toString();
133     if (!newRepoUrl.isEmpty() && newRepoUrl.isValid()
134         && newRepoTimestamp > catalogSetting.repoTimestamp) {
135         qDebug() << "The repo is updated and we should download it again";
136
137         QNetworkAccessManager *manager = new QNetworkAccessManager();
138         manager->setProxy(catalogSetting.repoProxy);
139
140         QNetworkReply *networkReply = manager->get(QNetworkRequest(newRepoUrl));
141         qDebug() << "Catalog: requesting repo file" << newRepoUrl.toString();
142         connect(networkReply, SIGNAL(finished()), SLOT(repoFilesDownloaded()));
143
144         connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
145                 SLOT(repoDownloadReplyError(QNetworkReply::NetworkError)));
146
147 #if 0 //TODO: QObjects created in different threads need better management
148         QFutureWatcher<void> *watcher = new QFutureWatcher<void>();
149         connect(watcher, SIGNAL(finished()), SLOT(repoFileDownloaded()));
150         QFuture<void> future = QtConcurrent::run(this, &GarageClientServices::initGarageService);
151         watcher->setFuture(future);
152 #endif
153     }
154
155     QList<Category*>::iterator i;
156     for (i = categoryList.begin(); i != categoryList.end(); i++) {
157         Category* category = *i;
158
159         QList<Application*>::iterator j;
160         for (j = applicationList.begin(); j != applicationList.end(); j ++) {
161             Application* application = *j;
162
163             if (application->getCategoryName() == category->getName()) {
164                 category->addApplication(application);
165             }
166         }
167     }
168 }
169
170 void Catalog::repoDownloadReplyError(QNetworkReply::NetworkError errorCode)
171 {
172     QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sender());
173
174     if (errorCode == QNetworkReply::NoError)
175         return;
176
177     qDebug() << "Network Reply Error: " << errorCode << networkReply->errorString();
178
179     networkReply->deleteLater();
180     networkReply->manager()->deleteLater();
181
182     qDebug() << "Download catalog " << id() << " repo file: " << networkReply->request().url() << " failed.";
183
184 }
185
186 void Catalog::repoFilesDownloaded()
187 {
188     QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sender());
189     networkReply->deleteLater();
190     networkReply->manager()->deleteLater();
191
192     if (networkReply->error() != QNetworkReply::NoError)
193         return;
194
195     qDebug() << "Download catalog " << id() << " repo file: " << networkReply->request().url() << " success.";
196
197     QString path = catalogSetting.repoLocalFilePath;
198     QFile file(path);
199     file.open(QFile::WriteOnly);
200     file.write(networkReply->readAll());
201     file.close();
202
203     qDebug() << "Downloaded latest repo file to " << path;
204
205     catalogSetting.repoUrl = newRepoUrl;
206     catalogSetting.repoTimestamp = newRepoTimestamp;
207     GarageSettings::writeCatalogSetting(catalogSetting);
208
209     emit newRepoDownloaded(networkReply->request().url(), path);
210
211     return;
212 }
213
214 bool Catalog::loadGarageSource(const QString& xmlFile)
215 {
216     if (xmlFile.isEmpty() || xmlFile.isNull() || !QFile::exists(xmlFile)) {
217         qDebug() << "Failed to load '" << xmlFile << "'\n";
218         emit catalogInitialized(false);
219         return false;
220     }
221
222     qDebug() << "App config file:" << xmlFile;
223     xmlDocPtr doc = xmlReadFile(xmlFile.toUtf8().constData(), NULL, 0);
224     if (doc == NULL) {
225         qDebug() << "Failed to load '" << xmlFile << "'\n";
226         emit catalogInitialized(false);
227         return false;
228     }
229     
230     xmlNodePtr root_element = xmlDocGetRootElement(doc);
231     loadXmlNode(root_element);
232     xmlFreeDoc(doc);
233     xmlCleanupParser();
234
235     updateCatalogData();
236
237     applicationList = Application::sortApplications(applicationList, MeeGoGarage::SortByName);
238
239     categoryListIsReady = TRUE;
240     applicationListIsReady = TRUE;
241
242     emit categoryListReady(this);
243     emit applicationListReady(this);
244
245     // update cache
246     //ApplicationManager::instance()->resolve(applicationList);
247     ApplicationManager::instance()->registerApplications(applicationList);
248     ApplicationManager::instance()->loadStatus();
249
250     // issue update of cache for apps with unknown state or old state
251     ApplicationManager::instance()->resolveOldOrUnknownApps();
252
253     emit catalogInitialized(true);
254
255     qDebug() << "Catalog::loadGarageSource finished";
256
257     return true;
258 }
259
260 bool Catalog::loadXmlNode (xmlNodePtr inNode)
261 {
262     xmlNodePtr node = NULL;
263     for (node = inNode; node!=NULL; node = node->next) {
264         if (node->type!=XML_ELEMENT_NODE) {
265             continue;
266         }
267         if (node->children) {
268             loadXmlNode (node->children);
269             continue;
270         }
271         int nodeType = getXmlNodeType(node);
272
273         if (nodeType==TYPE_APP) {
274             Application *application = new Application(this, node);
275             application->moveToThread(this->thread());
276             application->setParent(this);
277             applicationList.append(application);
278         } else if (nodeType==TYPE_CAT) {
279             Category *category = new Category(this, node);
280             category->moveToThread(this->thread());
281             category->setParent(this);
282             categoryList.append(category);
283         } else if (nodeType == TYPE_REPO) {
284             QString repo;
285             setXmlProp (node, "repo", repo);
286             newRepoUrl = QUrl(repo);
287             QString timestamp;
288             setXmlProp (node, "timestamp", timestamp);
289             //timestamp has format: "20100427 11:11:11"
290             newRepoTimestamp = QDateTime::fromString(timestamp, GarageSettings::TimeFormat);
291             qDebug() << "Read repo data: " << repo << " " << timestamp;
292         }
293     }
294
295     return TRUE;
296 }
297
298 int Catalog::getXmlNodeType (xmlNodePtr node)
299 {
300     if (!node->parent) {
301         return TYPE_NONE;
302     }
303     string s = (char *)node->parent->name;
304     if (s == XML_APP) {
305         return TYPE_APP;
306     } else if (s == XML_CAT) {
307         return TYPE_CAT;
308     } else if (s == XML_RAT) {
309         return TYPE_RAT;
310     } else if (s == XML_USER) {
311         return TYPE_USER;
312     } else if (s == XML_FEATURE) {
313         return TYPE_FEATURE;
314     } else if (s == XML_TXN) {
315         return TYPE_TXN;
316     } else if (s == XML_REPO) {
317         return TYPE_REPO;
318     }
319
320    return TYPE_NONE;
321 }
322
323 void Catalog::requestCategoryList()
324 {
325     QTimer *timer = new QTimer(this);
326     connect(timer, SIGNAL(timeout()), SLOT(categoryListDownloaded()));
327     timer->setSingleShot(TRUE);
328     timer->start(0);
329
330     qDebug() << "Downloading the category list ......";
331 }
332
333 //The list of all categories
334 const QList<Category*> Catalog::getCategoryList () const
335 {
336     return categoryList;
337 }
338
339 const QList<Application*> Catalog::getApplicationList () const
340 {
341     return applicationList;
342 }
343
344 bool Catalog::isCategoryListReady()
345 {
346     return categoryListIsReady;
347 }
348
349 bool Catalog::isApplicationListReady()
350 {
351     return applicationListIsReady;
352 }
353
354 //
355 void Catalog::requestSearchApplications (QStringList keywords,
356                                          MeeGoGarage::SearchType type, MeeGoGarage::SearchMode mode)
357 {
358     QList<Application*> list;
359
360     if (!isApplicationListReady()) {
361         emit searchApplicationsReady(keywords, list);
362         return;
363     }
364
365     list = Application::searchApplications(keywords, applicationList, type, mode);
366
367     emit searchApplicationsReady(keywords, list);
368 }
369
370 void Catalog::xmlDownloadReplyError(QNetworkReply::NetworkError errorCode)
371 {
372     QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sender());
373
374     networkReply->deleteLater();
375
376     if (errorCode == QNetworkReply::NoError)
377         return;
378
379     qDebug() << "Download catalog " << id() << " xml file: " << networkReply->request().url() << " failed.";
380     qDebug() << "Network Reply Error: " << errorCode << networkReply->errorString();
381     qDebug() << "Try to load the local cache file. ";
382
383     loadStaticGarageSource();
384     return;
385 }
386
387 void Catalog::catalogXmlFileDownloaded()
388 {
389     QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sender());
390     networkReply->deleteLater();
391
392     if (networkReply->error() != QNetworkReply::NoError)
393         return;
394
395     qDebug() << "Download catalog " << id() << " xml file: " << networkReply->request().url() << " success.";
396     qDebug() << "Network Reply Error: " << networkReply->errorString();
397
398     QString path = catalogSetting.staticXmlFilePath;
399     QFile file(path);
400     file.open(QFile::WriteOnly);
401     file.write(networkReply->readAll());
402     file.close();
403
404     networkReply->deleteLater();
405
406     qDebug() << "Downloaded latest xml file to " << path;
407
408     networkAccessManager->setProxy(catalogSetting.metadataProxy);
409
410     loadGarageSource(path);
411 }
412
413 void Catalog::categoryListDownloaded()
414 {
415     qDebug() << "Category list downloaded";
416
417     categoryListIsReady = TRUE;
418     emit categoryListReady(this);
419
420     QTimer *timer = qobject_cast<QTimer*>(sender());
421     timer->deleteLater();
422 }
423
424 void Catalog::dumpCategoryList()
425 {
426     qDebug() << "Dump Category List: ";
427     qDebug() << "    Count: " << categoryList.count();
428
429     QList<Category*>::iterator i;
430     for (i = categoryList.begin(); i != categoryList.end(); i++)
431         (*i)->dumpCategory ();
432 }
433
434 void Catalog::dumpFullApplicationList()
435 {
436     qDebug() << "Dump Full Application List: ";
437
438     dumpApplicationList(applicationList);
439 }
440
441 void Catalog::dumpApplicationList(QList<Application*> appList)
442 {
443     qDebug() << "Dump application List: ";
444     qDebug() << "    Count: " << appList.count();
445
446     QList<Application*>::iterator i;
447     for (i = appList.begin(); i != appList.end(); i++)
448         (*i)->dumpApplication();
449 }
450
451 QString Catalog::applicationListAsString(QList<Application*> applicationList)
452 {
453     QString str;
454
455     QListIterator<Application*> i(applicationList);
456     while (i.hasNext()) {
457         Application * app = i.next();
458         str += app->getName();
459         if (i.hasNext()) {
460             str += ", ";
461         }
462     }
463
464     return str;
465 }
466
467 int Catalog::id()
468 {
469     return catalogSetting.id;
470 }
471
472 void Catalog::CatalogSetting::dumpCatalogSetting()
473 {
474     qDebug() << "Dump catalog setting: ";
475     qDebug() << "id: " << id << "\n"
476             << "enabled: " << enabled << "\n"
477             << "sourceType: " << sourceType << "\n"
478             << "CatalogRemoteXmlProxy: " << remoteXmlProxy.hostName() << ":" << remoteXmlProxy.port() << "\n"
479             << "CatalogRemoteXmlUrl: " << remoteXmlUrl << "\n"
480             << "CatalogStaticXmlFilePath: " << staticXmlFilePath << "\n"
481             << "CatalogMetadataProxy: " << metadataProxy.hostName() << ":" << metadataProxy.port() << "\n"
482             << "repoUrl: " << repoUrl << "\n"
483             << "repoTimestamp: " << repoTimestamp.toString(GarageSettings::TimeFormat) << "\n"
484             << "repoLocalFilePath: " << repoLocalFilePath << "\n"
485             << "repoProxy: " << repoProxy.hostName() << ":" << repoProxy.port() << "\n";
486 }