source: src-qt4/pc-softwaremanager/pbiBackend.cpp @ 7e41a70

releng/10.0.1releng/10.0.2releng/10.0.3
Last change on this file since 7e41a70 was 7e41a70, checked in by Ken Moore <ken@…>, 7 months ago

Fix the auto-update refresh problem after it is clicked. It flickers once, but at least it correctly shows the status of the auto-update for the app now.

  • Property mode set to 100644
File size: 61.0 KB
Line 
1/***************************************************************************
2 *   Copyright (C) 2011 - iXsystems                                       *
3 *   kris@pcbsd.org  *
4 *   tim@pcbsd.org   *
5 *   ken@pcbsd.org   *
6 *                                                                         *
7 *   Permission is hereby granted, free of charge, to any person obtaining *
8 *   a copy of this software and associated documentation files (the       *
9 *   "Software"), to deal in the Software without restriction, including   *
10 *   without limitation the rights to use, copy, modify, merge, publish,   *
11 *   distribute, sublicense, and/or sell copies of the Software, and to    *
12 *   permit persons to whom the Software is furnished to do so, subject to *
13 *   the following conditions:                                             *
14 *                                                                         *
15 *   The above copyright notice and this permission notice shall be        *
16 *   included in all copies or substantial portions of the Software.       *
17 *                                                                         *
18 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
19 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
20 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
21 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
22 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
23 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
24 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
25 ***************************************************************************/
26 //ID FORMATS: [<name> = Extras::nameToID(formalName)]
27 //  pbiID: <name>-<version>-<arch> (of locally installed PBI)
28 //  catID: <name> (of category in repo)
29 //  appID: <name> (of app in repo)
30 
31 #include <unistd.h>
32 #include "pbiBackend.h"
33
34 
35 PBIBackend::PBIBackend(QWidget *parent) : QObject(){
36   parentWidget = parent;
37   //initialize the background processes
38   PMAN = new ProcessManager();
39   connect(PMAN, SIGNAL(ProcessFinished(int)),this,SLOT(slotProcessFinished(int)) );
40   connect(PMAN, SIGNAL(ProcessMessage(int, QString)),this,SLOT(slotProcessMessage(int, QString)) );
41   connect(PMAN, SIGNAL(ProcessError(int,QStringList)),this,SLOT(slotProcessError(int,QStringList)) );
42   PENDINGREMOVAL.clear(); PENDINGDL.clear(); PENDINGINSTALL.clear(); PENDINGUPDATE.clear(); PENDINGOTHER.clear();
43   sDownload=FALSE; sInstall=FALSE; sUpdate=FALSE; sRemove=FALSE;
44   //setup the base paths
45   baseDBDir = "/var/db/pbi/";
46   sysDB = new PBIDBAccess();
47   noRepo=FALSE;
48   wardenMode=FALSE;
49   //Default User Preferences
50   settingsFile = QDir::homePath()+"/.appcafeprefs";
51   baseDlDir = QDir::homePath()+"/Downloads/";
52   if(!QFile::exists(baseDlDir)){ baseDlDir = QDir::homePath()+"/"; }
53   keepDownloads = FALSE;
54   autoXDG.clear(); autoXDG << "desktop" << "menu" << "mime" << "paths";
55   currentRepoNum = "001"; //first repo by default
56   //Filesystem watcher
57   watchTimer = new QTimer(this);
58     watchTimer->setSingleShot(true);
59     watchTimer->setInterval(500); //1/2 a second intervals (to make sure we do not run it too freqently)
60     connect(watchTimer, SIGNAL(timeout()), this, SLOT(slotSyncToDatabase()) );
61   watcher = new QFileSystemWatcher();
62   connect(watcher,SIGNAL(directoryChanged(QString)),this,SLOT(slotWatcherNotification()) );
63   
64 }
65 
66 // ==============================
67 // ====== PUBLIC FUNCTIONS ======
68 // ==============================
69 void PBIBackend::setWardenMode(QString dir, QString ip){
70   wardenDir = dir; wardenIP = ip;
71   //wardenDir: used for direct access to a directory in the jail
72   //wardenIP: used to chroot into the jail to run commands
73   if( wardenDir.isEmpty() || wardenIP.isEmpty() ){ wardenMode = FALSE; }
74   else{ 
75     if(!wardenDir.endsWith("/")){wardenDir.append("/"); } //make sure it has a / on the end
76     wardenMode = TRUE; 
77   }
78 }
79 
80 bool PBIBackend::start(){
81   sysArch = Extras::getSystemArch(); //get the architecture for the current system
82   //Setup the base paths for the system database and downloads
83   if(wardenMode){ 
84     DBDir = wardenDir + baseDBDir;
85   }else{ 
86     DBDir = baseDBDir;
87   }
88   if(!DBDir.endsWith("/")){ DBDir.append("/"); }
89   if( !loadSettings() ){
90     saveSettings();       
91   }
92   updateDlDirPath(baseDlDir);
93   //Now setup the database access class
94   sysDB->setDBPath(DBDir);
95   sysDB->setRootCMDPrefix( addRootCMD("",TRUE).remove("\"") );
96     //Make sure the deisred repo is valid
97     QStringList repos = sysDB->availableRepos();
98     if(repos.length() > 0){ 
99       if(!repos.contains(currentRepoNum)){currentRepoNum = repos[0];} //make sure the desired repo actually exists
100     }else{
101       qDebug() << "No PBI Repositories available: disabling the browser";
102       emit NoRepoAvailable();
103       noRepo = TRUE;
104     }
105   //Set the repo
106   if(!noRepo){ sysDB->setRepo(currentRepoNum); }
107   //Now start the filesystem watcher
108   startFileSystemWatcher();
109   //Now initialize the hash lists with the database info
110   QTimer::singleShot(1,this,SLOT(slotSyncToDatabase()) );
111
112   return TRUE;
113 }
114
115 QStringList PBIBackend::installedList(){
116   return QStringList( PBIHASH.keys() ); 
117 }
118 
119 QStringList PBIBackend::browserCategories(){
120   return QStringList( CATHASH.keys() ); 
121 }
122 
123QStringList PBIBackend::browserApps( QString catID ){
124  QStringList output;
125  if(!CATHASH.contains(catID)){ return output; }
126  QStringList apps = APPHASH.keys();
127  for(int i=0; i<apps.length(); i++){
128    if(Extras::nameToID(APPHASH[apps[i]].category)==catID){ output << apps[i]; }
129  }
130  return output;
131}
132
133QStringList PBIBackend::getRecentApps(){
134  //List all new/updated applications in the last 10 days (sorted by oldest first)
135  QStringList output;
136  //Generate a string for today's date
137  QDate date = QDate::currentDate();
138  date = date.addDays(-10);
139  QString chk = date.toString("yyyyMMdd");
140  //Now compare the latest apps to this date
141  QStringList apps = APPHASH.keys();
142  QStringList  unsorted;
143  for(int i=0; i<apps.length(); i++){
144    if(Extras::newerDateTime(APPHASH[apps[i]].latestDatetime, chk)){
145      unsorted << APPHASH[apps[i]].latestDatetime + "::"+apps[i];           
146    }
147  }
148  //Now sort them by datetime and then remove the datetime stamp
149  unsorted.sort(); //oldest->newest
150  for(int i=(unsorted.length()-1); i>=0; i--){ //go in reverse order
151    output << unsorted[i].section("::",1,50);
152  }
153  return output; //newest->oldest
154}
155
156QStringList PBIBackend::getRecommendations(){
157  QStringList apps;
158  QStringList out = Extras::readFile(":defaultrecommendations.txt"); //make this load a db file later
159  for(int i=0; i<out.length(); i++){
160    //Make sure they are all valid apps in the repo
161    if(APPHASH.contains(out[i])){ apps << out[i]; }
162  }
163  return apps;
164}
165
166bool PBIBackend::safeToQuit(){
167  //returns true if there is no pending/current processes
168  bool ok = ( PENDINGDL.isEmpty() && PENDINGUPDATE.isEmpty() && PENDINGREMOVAL.isEmpty() \
169            && PENDINGINSTALL.isEmpty() && PENDINGOTHER.isEmpty() && cDownload.isEmpty() \
170            && cUpdate.isEmpty() && cRemove.isEmpty() && cInstall.isEmpty() && cOther.isEmpty() );
171  return ok;
172}
173// ===== Local/Repo Interaction Functions =====
174QString PBIBackend::isInstalled(QString appID){
175  //check if the pbiID was given (quick)
176  if(PBIHASH.contains(appID)){
177    if(PBIHASH[appID].path.isEmpty()){ return ""; }
178    else{ return appID; }
179  }
180  //Returns pbiID of the installed application
181  QString output;
182  if(!APPHASH.contains(appID)){
183    qDebug() << "Invalid application ID:" << appID;
184    return output;
185  }
186  QStringList pbiID = PBIHASH.keys(); //get list of installed PBI's
187  for(int i=0; i<pbiID.length();i++){
188    QString pbi = Extras::nameToID(PBIHASH[pbiID[i]].metaID);
189    if( (pbi == appID) && !PBIHASH[pbiID[i]].path.isEmpty() ){
190      output = pbiID[i];
191      break;
192    }
193  }
194  return output;
195}
196
197QString PBIBackend::upgradeAvailable(QString pbiID){
198  QString output;
199  if(!PBIHASH.contains(pbiID)){return output;}
200  QString appID = Extras::nameToID(PBIHASH[pbiID].metaID);
201  if(APPHASH.contains(appID)){
202    if(APPHASH[appID].latestVersion != PBIHASH[pbiID].version){output = APPHASH[appID].latestVersion;} 
203  }
204  return output;
205}
206
207// === PBI Actions ===
208void PBIBackend::cancelActions(QStringList pbiID){
209  qDebug() << "Cancel Actions requested for:" << pbiID;
210  for(int i=0; i<pbiID.length(); i++){
211    if(PBIHASH.contains(pbiID[i]) ){
212      //Remove any pending actions for this pbiID
213      PENDINGDL = removePbiCMD(pbiID[i],PENDINGDL);
214      PENDINGINSTALL = removePbiCMD(pbiID[i],PENDINGINSTALL);
215      PENDINGREMOVAL = removePbiCMD(pbiID[i],PENDINGREMOVAL);
216      PENDINGUPDATE = removePbiCMD(pbiID[i],PENDINGUPDATE);
217      PENDINGOTHER = removePbiCMD(pbiID[i],PENDINGOTHER); //doubtful that there will even be anything here
218      //Now cancel any current operations for this pbiID
219      if(cDownload==pbiID[i]){ sDownload=TRUE; PMAN->stopProcess(ProcessManager::DOWNLOAD); }
220      if(cUpdate==pbiID[i]){ 
221              if( lUpdate.startsWith("DLSTAT::") || lUpdate.startsWith("DLSTART") ){
222                //In download phase of the update - just kill it
223                PMAN->stopProcess(ProcessManager::UPDATE); 
224              }else{
225                //In the install phase of the update
226                sUpdate=TRUE; //need to clean up after it finishes
227              }
228      }
229      if(cRemove==pbiID[i]){ sRemove=TRUE; }
230      if(cInstall==pbiID[i]){ sInstall=TRUE; }
231      //Ignore OTHER process - those commands are pretty much instant
232    }
233  }
234  //Now refresh the PBIHASH info (remove stale entries, update statuses, etc..)
235  slotSyncToDatabase(TRUE);
236}
237
238void PBIBackend::upgradePBI(QStringList pbiID){
239  qDebug() << "PBI Upgrades requested for:" << pbiID;
240  for(int i=0; i<pbiID.length(); i++){
241    if( PBIHASH.contains(pbiID[i]) ){
242      if( PBIHASH[pbiID[i]].status == InstalledPBI::UPDATEAVAILABLE ){
243        QString cmd = generateUpdateCMD(pbiID[i]);
244        PENDINGUPDATE << pbiID[i] + ":::" + cmd;
245        PBIHASH[pbiID[i]].setStatus(InstalledPBI::PENDINGUPDATE);
246        emit PBIStatusChange(pbiID[i]);
247      }else{
248        qDebug() << "No update available for:" << pbiID[i];     
249      }
250    }else{
251      qDebug() << pbiID[i] << "not a valid PBI to update";         
252    }
253  }
254  //Now check/start the update process
255  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
256}
257
258void PBIBackend::removePBI(QStringList pbiID){
259  qDebug() << "PBI Removals requested for:" << pbiID;
260  QStringList xdgrem; xdgrem << "remove-desktop" << "remove-menu" << "remove-mime" << "remove-paths";
261  for(int i=0; i<pbiID.length(); i++){
262    if(PBIHASH.contains(pbiID[i])){
263      //Remove XDG entries for this app
264      PENDINGREMOVAL << pbiID[i]+":::"+generateXDGCMD(pbiID[i],xdgrem, FALSE);
265      //Remove the app itself
266      PENDINGREMOVAL << pbiID[i]+":::"+generateRemoveCMD(pbiID[i]);
267      //Now update the status
268      PBIHASH[pbiID[i]].setStatus(InstalledPBI::PENDINGREMOVAL);
269      emit PBIStatusChange(pbiID[i]);
270    }else{
271      qDebug() << pbiID[i] << "not a valid PBI to remove";         
272    }
273  }
274  //Now check/start the remove process
275  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
276}
277
278void PBIBackend::installApp(QStringList appID){
279  qDebug() << "Install App requested for:" << appID;
280  for(int i=0; i<appID.length(); i++){
281    if(!APPHASH.contains(appID[i])){ 
282      qDebug() << appID[i] << "is not a valid application";
283      continue; //go to the next item is this one is invalid
284    } 
285    //Find out if app is installed already
286    QString pbiID = isInstalled(appID[i]);
287    if(pbiID.isEmpty()){
288      //Not installed at the moment, queue up the installation
289      qDebug() << "Fresh install of appID:" << appID[i];
290      queueInstall(appID[i]);
291    }else{
292      //It is already registered as installed
293      if( APPHASH[appID[i]].latestVersion == PBIHASH[pbiID].version ){
294        //latest version already installed, revert to backup version
295        qDebug() << "Revert install of appID:" << appID[i];
296        queueInstall(appID[i], APPHASH[appID[i]].backupVersion);
297      }else{
298        //older version installed - try to update the app
299        qDebug() << "Update install of appID:" << appID[i];
300        upgradePBI(QStringList() << pbiID);
301      }
302    }
303  } // end of loop over items
304  //Now check/start the remove process
305  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
306  //Now emit the signal that items have changed or been added
307  emit LocalPBIChanges();
308}
309
310void PBIBackend::installPBIFromFile(QStringList files){
311  qDebug() << "Install PBI files requested for:" << files;
312  for(int i=0; i<files.length(); i++){
313    if(!files[i].endsWith(".pbi")){ continue; } //invalid file
314    //Now load the info about this PBI
315    bool root = PBIFileNeedsRoot(files[i]);
316    //Now create the commands
317    QString cmd = addRootCMD("pc-pbigui "+files[i], root);
318    //Don't bother integrating it furthur, the pc-pbigui will handle it all better
319    //  and if it actually gets installed it will automatically get detected/added to the AppCafe list
320    PENDINGOTHER << "EXTERNAL:::"+cmd;
321  }
322  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
323}
324
325void PBIBackend::addDesktopIcons(QStringList pbiID, bool allusers){ // add XDG desktop icons
326  for(int i=0; i<pbiID.length(); i++){
327    if(PBIHASH.contains(pbiID[i])){
328      //generate the command
329      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"desktop",allusers);
330      //Now add it to the queue
331      PENDINGOTHER << pbiID[i]+":::"+cmd;
332    }
333  }
334  //Now check/start the process
335  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
336}
337
338void PBIBackend::addMenuIcons(QStringList pbiID, bool allusers){ // add XDG menu icons
339  for(int i=0; i<pbiID.length(); i++){
340    if(PBIHASH.contains(pbiID[i])){
341      //generate the command
342      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"menu",allusers);
343      //Now add it to the queue
344      PENDINGOTHER << pbiID[i]+":::"+cmd;
345    }
346  }
347  //Now check/start the process
348  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
349}
350
351void PBIBackend::addPaths(QStringList pbiID, bool allusers){ // create path links
352  for(int i=0; i<pbiID.length(); i++){
353    if(PBIHASH.contains(pbiID[i])){
354      //generate the command
355      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"paths",allusers);
356      //Now add it to the queue
357      PENDINGOTHER << pbiID[i]+":::"+cmd;
358    }
359  }
360  //Now check/start the process
361  QTimer::singleShot(0,this,SLOT(checkProcesses()) );   
362}
363
364void PBIBackend::addMimeTypes(QStringList pbiID, bool allusers){ // remove path links
365  for(int i=0; i<pbiID.length(); i++){
366    if(PBIHASH.contains(pbiID[i])){
367      //generate the command
368      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"mime",allusers);
369      //Now add it to the queue
370      PENDINGOTHER << pbiID[i]+":::"+cmd;
371    }
372  }
373  //Now check/start the process
374  QTimer::singleShot(0,this,SLOT(checkProcesses()) );   
375}
376
377void PBIBackend::rmDesktopIcons(QStringList pbiID, bool allusers){ // remove XDG desktop icons
378  for(int i=0; i<pbiID.length(); i++){
379    if(PBIHASH.contains(pbiID[i])){
380      //generate the command
381      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"remove-desktop",allusers);
382      //Now add it to the queue
383      PENDINGOTHER << pbiID[i]+":::"+cmd;
384    }
385  }
386  //Now check/start the process
387  QTimer::singleShot(0,this,SLOT(checkProcesses()) );   
388}
389
390void PBIBackend::rmMenuIcons(QStringList pbiID, bool allusers){ // remove XDG menu icons
391  for(int i=0; i<pbiID.length(); i++){
392    if(PBIHASH.contains(pbiID[i])){
393      //generate the command
394      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"remove-menu",allusers);
395      //Now add it to the queue
396      PENDINGOTHER << pbiID[i]+":::"+cmd;
397    }
398  }
399  //Now check/start the process
400  QTimer::singleShot(0,this,SLOT(checkProcesses()) );   
401}
402
403void PBIBackend::rmPaths(QStringList pbiID, bool allusers){ // remove path links
404  for(int i=0; i<pbiID.length(); i++){
405    if(PBIHASH.contains(pbiID[i])){
406      //generate the command
407      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"remove-paths",allusers);
408      //Now add it to the queue
409      PENDINGOTHER << pbiID[i]+":::"+cmd;
410    }
411  }
412  //Now check/start the process
413  QTimer::singleShot(0,this,SLOT(checkProcesses()) );   
414}
415
416void PBIBackend::rmMimeTypes(QStringList pbiID, bool allusers){ // remove path links
417  for(int i=0; i<pbiID.length(); i++){
418    if(PBIHASH.contains(pbiID[i])){
419      //generate the command
420      QString cmd = generateXDGCMD(pbiID[i],QStringList()<<"remove-mime",allusers);
421      //Now add it to the queue
422      PENDINGOTHER << pbiID[i]+":::"+cmd;
423    }
424  }
425  //Now check/start the process
426  QTimer::singleShot(0,this,SLOT(checkProcesses()) );   
427}
428
429void PBIBackend::enableAutoUpdate(QString pbiID, bool enable){
430  if(!PBIHASH.contains(pbiID)){return;}
431  //Generate the command
432  QString cmd = generateAutoUpdateCMD(pbiID,enable);
433  //Now put it in the queue
434  PENDINGOTHER << pbiID+":::"+cmd;
435  //Now check/start the process
436  QTimer::singleShot(0,this,SLOT(checkProcesses()) );
437}
438
439 // === Information Retrieval functions ===
440QStringList PBIBackend::PBIInfo( QString pbiID, QStringList infoList){
441  QStringList output;
442  if(!PBIHASH.contains(pbiID)){ return output; }
443  for(int i=0; i<infoList.length(); i++){
444    if(infoList[i]=="name"){ output << PBIHASH[pbiID].name; }
445    else if(infoList[i]=="version"){ output << PBIHASH[pbiID].version; }
446    else if(infoList[i]=="author"){ output << PBIHASH[pbiID].author; }
447    else if(infoList[i]=="website"){ output << PBIHASH[pbiID].website; }
448    else if(infoList[i]=="arch"){ output << PBIHASH[pbiID].arch; }
449    else if(infoList[i]=="path"){ output << PBIHASH[pbiID].path; }
450    else if(infoList[i]=="icon"){ 
451        QString icon = PBIHASH[pbiID].icon;
452        if( QFile::exists(icon) ){ output << icon; }
453        else{ output << ""; }
454    }else if(infoList[i]=="license"){ output << PBIHASH[pbiID].license; }
455    else if(infoList[i]=="metaid"){ output << PBIHASH[pbiID].metaID; }
456    else if(infoList[i]=="status"){ output << currentAppStatus(pbiID); }
457    else if(infoList[i]=="maintainer"){ output << PBIHASH[pbiID].maintainer; }
458    else if(infoList[i]=="description"){ output << PBIHASH[pbiID].description; }
459    else if(infoList[i]=="date"){ output << PBIHASH[pbiID].mdate; }
460    else if(infoList[i]=="fbsdversion"){ output << PBIHASH[pbiID].fbsdversion; }
461    //Now the boolians
462    else if(infoList[i]=="requiresroot"){ 
463      if(PBIHASH[pbiID].rootInstall){output<<"true";}
464      else{ output<<"false";}
465    }
466    else if(infoList[i]=="autoupdate"){ 
467      if(PBIHASH[pbiID].autoUpdate){output<<"true";}
468      else{ output<<"false";}
469    }
470    else if(infoList[i]=="hasdesktopicons"){ 
471      if(PBIHASH[pbiID].desktopIcons){output<<"true";}
472      else{ output<<"false";}
473    }
474    else if(infoList[i]=="hasmenuicons"){ 
475      if(PBIHASH[pbiID].menuIcons){output<<"true";}
476      else{ output<<"false";}
477    }
478    else if(infoList[i]=="hasmimetypes"){ 
479      if(PBIHASH[pbiID].mimetypes){output<<"true";}
480      else{ output<<"false";}
481    }
482    else{ output << ""; }
483  }
484  //qDebug()<< "Info Requested for:" << pbiID << infoList << "Info:" << output;
485  return output;
486}       
487
488QStringList PBIBackend::CatInfo( QString catID, QStringList infoList){
489  QStringList output;
490  if(!CATHASH.contains(catID)){ return output; }
491  for(int i=0; i<infoList.length(); i++){
492    if(infoList[i]=="name"){ output << CATHASH[catID].name; }
493    else if(infoList[i]=="icon"){ output << CATHASH[catID].localIcon; }
494    else if(infoList[i]=="description"){ output << CATHASH[catID].description; }   
495    else{ output << ""; }
496  }
497  //qDebug()<< "Info Requested for:" << catID << infoList << "Info:" << output;
498  return output;
499}
500
501QStringList PBIBackend::AppInfo( QString appID, QStringList infoList){
502  QStringList output;
503  if(!APPHASH.contains(appID)){ return output; }
504  for(int i=0; i<infoList.length(); i++){
505    if(infoList[i]=="name"){ output << APPHASH[appID].name; }
506    else if(infoList[i]=="author"){ output << APPHASH[appID].author; }
507    else if(infoList[i]=="website"){ output << APPHASH[appID].website; }
508    else if(infoList[i]=="icon"){ output << APPHASH[appID].localIcon; }
509    else if(infoList[i]=="license"){ output << APPHASH[appID].license; }
510    else if(infoList[i]=="type"){ output << APPHASH[appID].appType; }
511    else if(infoList[i]=="description"){ output << APPHASH[appID].description; }
512    else if(infoList[i]=="shortdescription"){ output << APPHASH[appID].shortdescription; }
513    else if(infoList[i]=="maintainer"){ output << APPHASH[appID].maintainer; }
514    else if(infoList[i]=="category"){ output << APPHASH[appID].category; }
515    else if(infoList[i]=="latestversion"){ output << APPHASH[appID].latestVersion; }
516    else if(infoList[i]=="latestarch"){ output << APPHASH[appID].latestArch; }
517    else if(infoList[i]=="latestsize"){ output << APPHASH[appID].latestSizeK; }
518    else if(infoList[i]=="backupversion"){ output << APPHASH[appID].backupVersion; }
519    else if(infoList[i]=="backuparch"){ output << APPHASH[appID].backupArch; }
520    else if(infoList[i]=="backupsize"){ output << APPHASH[appID].backupSizeK; }
521    //Now the boolians
522    else if(infoList[i]=="requiresroot"){ 
523      if(APPHASH[appID].requiresroot){output<<"true";}
524      else{ output<<"false";}
525    }
526    else{ output << ""; }
527  }
528  //qDebug()<< "Info Requested for:" << appID << infoList << "Info:" << output;
529  return output;
530}
531
532QString PBIBackend::currentAppStatus( QString appID, bool rawstatus ){
533  QString output;
534  int status = -999;
535  QString metaID;
536  //pbiID given (quicker)
537  if(PBIHASH.contains(appID)){ status = PBIHASH[appID].status; metaID = PBIHASH[appID].metaID; }
538  else{
539    //appID given
540    if(!APPHASH.contains(appID)){ return ""; }
541    QStringList pbilist = PBIHASH.keys();
542    bool active = false;
543    for(int i=0; i<pbilist.length(); i++){
544      //Be careful about more than one pbiID matching the given appID - grab the active one
545      if(PBIHASH[pbilist[i]].metaID == appID){ 
546        status = PBIHASH[pbilist[i]].status; 
547        metaID=appID;
548        active = !(status==InstalledPBI::UPDATEAVAILABLE || status==InstalledPBI::NONE);
549      }
550      if(active){ break; }
551    }
552  }
553  //Determine if the app is currently in a pending/running state
554  if(rawstatus){ //output the raw status for active processes
555    switch (status){
556        case InstalledPBI::DOWNLOADING:
557          output = lDownload;
558          if(output.isEmpty()){ output = "Downloading"; }
559          break;
560        case InstalledPBI::INSTALLING:
561          output = lInstall; 
562          if(output.isEmpty()){ output = "Installing"; }
563          break;
564        case InstalledPBI::REMOVING:
565          output = lRemove; 
566          if(output.isEmpty()){ output = "Removing"; }
567          break;
568        case InstalledPBI::UPDATING:
569          output = lUpdate; 
570          if(output.isEmpty()){ output = "Updating"; }
571          break;
572        case InstalledPBI::NONE:
573        case InstalledPBI::UPDATEAVAILABLE:
574          output.clear(); break;
575        default:
576          output = "Pending";
577    }
578  }else{
579    switch (status){
580        case InstalledPBI::DOWNLOADING:
581          if(sDownload){ output = tr("Download Canceled"); }
582          if(lDownload.startsWith("DLSTAT::")){
583            QString percent = lDownload.section("::",1,1);
584            QString spd = lDownload.section("::",3,3);
585            if(spd=="??"){
586              output = QString(tr("Downloading: %1%")).arg( percent );
587            }else{
588              output = QString(tr("Downloading: %1% @ %2")).arg( percent, spd );
589            }
590          }else{
591            output = lDownload;
592          }
593          break;
594        case InstalledPBI::INSTALLING:
595          if(sInstall){ output = tr("Install Canceled (will remove)"); }
596          else{ output = tr("Installing"); }
597          break;
598        case InstalledPBI::REMOVING:
599          if(sRemove){ output = tr("Removal Canceled (will reinstall)"); }
600          else{ output = tr("Removing Application"); }
601          break;
602        case InstalledPBI::UPDATING:
603          if(sUpdate){ output = tr("Update's cannot be canceled"); }
604          else if(lUpdate.startsWith("DLSTAT::")){
605            QString percent = lUpdate.section("::",1,1);
606            output = QString(tr("Update Downloading: %1%")).arg( percent );
607          }else if(lUpdate == "DLDONE"){
608            output = tr("Starting Update");
609          }else if(lUpdate == "DLSTART"){
610            output = tr("Starting Download");
611          }else{
612            output = tr("Updating");
613          }
614          break;
615        case InstalledPBI::PENDINGDOWNLOAD:
616          output = tr("Pending Download"); break;
617        case InstalledPBI::PENDINGINSTALL:
618          output = tr("Pending Install"); break;
619        case InstalledPBI::PENDINGREMOVAL:
620          output = tr("Pending Removal"); break;
621        case InstalledPBI::PENDINGUPDATE:
622          output = tr("Pending Update"); break;
623        case InstalledPBI::UPDATEAVAILABLE:
624          output = QString(tr("Update Available: %1")).arg(APPHASH[metaID].latestVersion);
625          break;
626        default: //do nothing for the rest
627          output.clear();
628    }
629  }
630  return output;
631}
632
633bool PBIBackend::isWorking(QString pbiID){
634  if( !PBIHASH.contains(pbiID) ){ return FALSE; }
635
636  bool notworking = (PBIHASH[pbiID].status == InstalledPBI::UPDATEAVAILABLE || PBIHASH[pbiID].status == InstalledPBI::NONE );
637  return !notworking;
638}
639
640QStringList PBIBackend::pbiBinList(QString pbiID){
641  QStringList output;
642  //qDebug() << "Start Search for Binaries:" << pbiID;
643  if( !PBIHASH.contains(pbiID) ){ return output; }
644  //Now prod the database to see what binaries are currently available to run
645  QDir bDir(PBIHASH[pbiID].path+"/.xdg-menu/"); 
646  if(bDir.exists()){
647    QStringList bList = bDir.entryList(QStringList() << "*.desktop");
648    for(int i =0; i<bList.length(); i++){
649      QStringList contents = Extras::readFile(bDir.absoluteFilePath(bList[i]));
650        //qDebug() << " - Check file:" << bList[i] << contents;
651      //Make sure that it is a visible entry
652      if(!contents.contains("NoDisplay=true")){
653        //qDebug() << " -- visible entry";
654        QString name;
655        //Get the locale code
656        QString loc = QLocale::system().name();
657        bool lName=false;
658        for(int j=0; j<contents.length(); j++){
659          if(contents[j].startsWith("Name=") && !lName){ name = contents[j].section("=",1,10);  }
660          else if(contents[j].startsWith("Name["+loc+"]=")){ name = contents[j].section("=",1,10);  lName=true; }
661        }
662        //Format the output string
663        if( !name.isEmpty() ){
664          output << name.simplified()+"::::"+bDir.absoluteFilePath(bList[i]);
665        }
666      }
667    } //end loop over files
668  }
669  return output;
670}
671
672QString PBIBackend::pbiToAppID(QString pbiID){
673  QString appID;
674  if( PBIHASH.contains(pbiID) ){ appID = PBIHASH[pbiID].metaID; }       
675  return appID;
676}
677
678// === Configuration Management ===
679void PBIBackend::openConfigurationDialog(){
680  //temporarily disable the filesystem watcher (causes issues with repo changes)
681  stopFileSystemWatcher();
682  //Now setup the UI
683  ConfigDialog *cfg = new ConfigDialog(parentWidget);
684  cfg->xdgOpts = autoXDG;
685  cfg->keepDownloads = keepDownloads;
686  cfg->downloadDir = baseDlDir;
687  cfg->DB = sysDB; //for access to database operations
688  cfg->setupDone();
689  //Now run it
690  cfg->exec(); //makes it modal and waits for it to finish
691  //Now see if there are changes to save
692  if(cfg->applyChanges){
693    autoXDG = cfg->xdgOpts;
694    keepDownloads = cfg->keepDownloads;
695    updateDlDirPath(cfg->downloadDir);
696    //Now save the configuration data to file
697    saveSettings();
698
699  }
700  //Now re-enable the filesystem watcher
701  startFileSystemWatcher();
702  //Now re-sync the repo info
703  syncCurrentRepo();
704}
705
706// === Import/Export PBI Lists ===
707bool PBIBackend::exportPbiListToFile(QString filepath){
708  bool ok = FALSE;
709  //get the currently installed PBI's
710  QStringList list = PBIHASH.keys();
711  QStringList installed;
712  for(int i=0; i<list.length(); i++){
713    if(!PBIHASH[list[i]].path.isEmpty()){ installed << PBIHASH[list[i]].metaID; }         
714  }
715  qDebug() << "Export List:" << installed;
716  //Now save the list
717  if(installed.isEmpty()){ ok = TRUE; }
718  else{ ok = Extras::writeFile(filepath,installed); }
719  return ok;
720}
721
722bool PBIBackend::importPbiListFromFile(QString filepath){
723  bool ok = FALSE;
724  if(!QFile::exists(filepath)){ return ok; }
725  QStringList inlist = Extras::readFile(filepath);
726  if(inlist.isEmpty()){ return ok; }
727  qDebug() << "Import List:" << inlist;
728  //Now sort the apps based on validity
729  QStringList good, bad, current; 
730  for(int i=0; i<inlist.length(); i++){
731    QString app = inlist[i].toLower();
732    if(app.isEmpty()){continue;}
733    else if(!APPHASH.contains(app)){ bad << inlist[i]; }
734    else if( !isInstalled(app).isEmpty() ){ current << inlist[i]; }
735    else{ good << app; } //make sure to use the appID here
736  }
737  ok = TRUE;
738  //List the results
739  if(good.isEmpty()){
740    QString results = tr("No applications to install from this list.")+"\n"+tr("Results:")+"\n";
741    if(!bad.isEmpty()){ results.append( QString(tr("Unavailable Apps: %1")).arg(bad.join(",  "))+"\n" ); }
742    if(!current.isEmpty()){ results.append( QString(tr("Currently Installed: %1")).arg(current.join(",  "))+"\n" ); }
743    QMessageBox::information(0,tr("Import Results"),results);
744  }else{
745    QString results = tr("Are you sure you wish to install these applications?")+"\n\n"+good.join(", ");
746    if( QMessageBox::question(0,tr("Import Results"),results,QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes){
747      //Go ahead and install these applications
748      installApp(good);
749    }
750  }
751  return ok;
752}
753
754 // ==========================
755 // ====== PUBLIC SLOTS ======
756 // ==========================
757 // NOTE: These functions should only be called from a QTimer::singleShot()
758 //    Because outputs come via different signals (due to time for these functions to run)
759 
760void PBIBackend::startAppSearch(){
761 //  Outputs come via the "SearchComplete(QStringList best,QStringList rest)" signal
762 QString search = searchTerm; //This public variable needs to be set beforehand by the calling process
763 QStringList best, rest;
764 //Now perform the search and categorize it
765 search = search.toLower();
766 QStringList namematch, tagmatch, descmatch;
767 QStringList app = APPHASH.keys();
768 for(int i=0; i<app.length(); i++){
769   if(APPHASH[app[i]].name.toLower() == search){ best << app[i]; } //exact match - top of the "best" list
770   else if(APPHASH[app[i]].name.toLower().contains(search)){ namematch << app[i]; }
771   else if(APPHASH[app[i]].tags.contains(search)){ tagmatch << app[i]; }
772   else if(APPHASH[app[i]].description.contains(search)){ descmatch << app[i]; }
773 }
774 //Now sort the lists and assign a priority
775 namematch.sort(); tagmatch.sort(); descmatch.sort();
776 best << namematch;
777 if(best.isEmpty()){ best << tagmatch; }
778 else{ rest << tagmatch; }
779 if(best.isEmpty()){ best << descmatch; }
780 else{ rest << descmatch; }
781
782 //Now emit the signal with the results
783 emit SearchComplete(best,rest);
784}
785
786void PBIBackend::startSimilarSearch(){
787  //  Outputs come via the "SimilarFound(QStringList results)" signal
788  QString sID = searchSimilar; // this public variable needs to be set beforehand by the calling process
789  QStringList output; 
790  if(!APPHASH.contains(sID)){ return; } 
791  //Now find the tags on the given ID
792  QStringList stags = APPHASH[sID].tags;
793  QStringList apps = APPHASH.keys();
794  QStringList unsorted;
795  int maxMatch=0;
796  for(int i=0; i<apps.length(); i++){
797    if(apps[i]==sID){continue;} //skip the app we were given for search parameters
798    QStringList tags = APPHASH[apps[i]].tags;
799    int match=0;
800    for(int j=0; j<stags.length(); j++){
801       if(tags.indexOf(stags[j]) != -1){ match++; }
802    }
803    if(match > 1){ unsorted << QString::number(match)+"::"+apps[i]; }
804    if(match > maxMatch){ maxMatch = match; }
805  }
806  unsorted.sort();
807  for(int i=unsorted.length(); i>0; i--){
808    if( unsorted[i-1].section("::",0,0).toInt() > (maxMatch-1) ){
809      output << unsorted[i-1].section("::",1,50);
810    }else if( output.length() < 5 ){ //not enough matches, grab the next set too
811      output << unsorted[i-1].section("::",1,50);
812      maxMatch--;
813    }else{ break; } //already sorted and the rest are even lower match rating
814  }
815  //Now emit the signal with the results
816  emit SimilarFound(output);
817}
818
819 // ===============================
820 // ====== PRIVATE FUNCTIONS ======
821 // ===============================
822bool PBIBackend::saveSettings(){
823  //Generate the file layout
824  QStringList file;
825  file << "POSTINSTALL="+autoXDG.join(",").simplified();
826  file << "REPONUMBER="+sysDB->currentRepo();
827  if(keepDownloads){ file << "KEEPDOWNLOADS=TRUE"; }
828  else{ file << "KEEPDOWNLOADS=FALSE"; }
829  file << "DOWNLOADDIR="+baseDlDir;
830  //Now save the file
831  bool ok = Extras::writeFile(settingsFile, file);
832  return ok;
833}
834
835bool PBIBackend::loadSettings(){
836  if(!QFile::exists(settingsFile)){ return FALSE; }
837  QStringList file = Extras::readFile(settingsFile);
838  if(file.isEmpty()){ return FALSE; }
839  for(int i=0; i<file.length(); i++){
840    if(file[i].startsWith("POSTINSTALL=")){ autoXDG = file[i].section("=",1,1).split(","); }
841    else if(file[i].startsWith("REPONUMBER=")){ sysDB->setRepo(file[i].section("=",1,1)); }
842    else if(file[i].startsWith("KEEPDOWNLOADS=")){ 
843      if(file[i].section("=",1,1) == "TRUE"){ keepDownloads = TRUE; }
844      else{ keepDownloads = FALSE; }
845    }else if(file[i].startsWith("DOWNLOADDIR=")){ updateDlDirPath(file[i].section("=",1,1)); }
846  }
847  return TRUE;
848}
849 
850 QString PBIBackend::addRootCMD(QString cmd, bool needRoot){
851   //Check for warden and root permissions and adjust the command accordingly
852   if(wardenMode){
853     cmd.prepend("warden chroot "+wardenIP+" \""); cmd.append("\"");
854   }else if( needRoot ){
855     cmd.prepend("pc-su \""); cmd.append("\"");     
856   }
857   return cmd;
858 }
859 
860 QString PBIBackend::generateUpdateCMD(QString pbiID){
861   QString output;
862   if(!PBIHASH.contains(pbiID)){ return output; }
863   output = "pbi_update "+pbiID;
864   output = addRootCMD(output, PBIHASH[pbiID].rootInstall);
865   return output;
866 }
867 
868 QString PBIBackend::generateRemoveCMD(QString pbiID){
869   QString output;
870   if(!PBIHASH.contains(pbiID)){ return output; }
871   output = "pbi_delete "+pbiID;
872   output = addRootCMD(output, PBIHASH[pbiID].rootInstall);
873   return output;       
874 }
875 
876 QString PBIBackend::generateAutoUpdateCMD(QString pbiID, bool enable){
877   QString output;
878   if(!PBIHASH.contains(pbiID)){ return output; }
879   output = "pbi_update";
880   if(enable){ output.append(" --enable-auto"); }
881   else{ output.append(" --disable-auto"); }
882   output.append(" "+PBIHASH[pbiID].metaID);
883   output = addRootCMD(output, PBIHASH[pbiID].rootInstall);
884   return output;       
885 }
886
887 QString PBIBackend::generateXDGCMD(QString pbiID, QStringList actions, bool allusers){
888   QString output;
889   if(!PBIHASH.contains(pbiID)){ return output; }
890   output = "pbi_icon";
891   if(actions.contains("desktop")){ output.append(" add-desktop"); }
892   if(actions.contains("menu")){ output.append(" add-menu"); }
893   if(actions.contains("paths")){ output.append(" add-pathlnk"); }
894   if(actions.contains("mime")){ output.append(" add-mime"); }
895   if(actions.contains("remove-desktop")){ output.append(" del-desktop"); }
896   if(actions.contains("remove-menu")){ output.append(" del-menu"); }
897   if(actions.contains("remove-paths")){ output.append(" del-pathlnk"); }
898   if(actions.contains("remove-mime")){ output.append(" del-mime"); }
899   //Check command actions
900   if(output == "pbi_icon"){ 
901     output.clear(); //no actions - do nothing
902   }else{
903     output.append(" "+pbiID);
904     output = addRootCMD(output,allusers);
905   }
906   return output;       
907 }
908 
909 QString PBIBackend::generateDownloadCMD(QString appID, QString version){
910   if(!APPHASH.contains(appID)){ return ""; }
911   QString output = "pbi_add -R --repo "+sysDB->currentRepo();
912   if(!version.isEmpty()){ output.append(" --rVer "+version); }
913   output.append(" "+appID);
914   return output;
915 }
916 
917 QString PBIBackend::generateInstallCMD(QString appID, QString filename){
918   QString output = "pbi_add --licagree "+filename;
919   output = addRootCMD(output, PBIHASH[appID].rootInstall);
920   return output;
921 }
922 
923QStringList PBIBackend::removePbiCMD(QString pbiID, QStringList list){
924   //Used for removing any pending CMD's for a particular pbiID
925   QStringList output;
926   for(int i=0; i<list.length(); i++){
927     if(!list[i].startsWith(pbiID)){ output << list[i]; }         
928   }
929   return output;
930}
931
932bool PBIBackend::PBIFileNeedsRoot(QString filepath){
933  //Used to grab information from a stand-along *.pbi file
934  bool root = false;
935  QStringList info = Extras::getCmdOutput("pbi_add -i "+filepath);
936  //Only check if it needs root
937  root = !info.filter("RootInstall:").join("").contains("NO");
938  return root;
939}
940
941void PBIBackend::queueInstall(QString appID, QString version){
942  //This function assumes that the new app/version combination is not already installed on the system
943  //  and that upgrading is not an option (fresh download/install)     
944  if( !APPHASH.contains(appID) ){ return; }
945  //verify that the version is available
946  if( version.isEmpty() ){ version = APPHASH[appID].latestVersion; }
947  bool useLatest = false;
948  if( version == APPHASH[appID].latestVersion){ useLatest=true; }
949  else if( version != APPHASH[appID].backupVersion ){ return; } //invalid version
950  //Check to see if the file is already downloaded
951  QString dlFile, arch;
952  if(useLatest){ dlFile = APPHASH[appID].latestFilename; arch = APPHASH[appID].latestArch; }
953  else{ dlFile = APPHASH[appID].backupFilename; arch = APPHASH[appID].backupArch; }
954  //Generate PBI ID
955  QString newID = appID+"-"+version+"-"+arch;
956  QString oldID = isInstalled(appID); //look for existing installation of this app
957
958  //Create Commands and add them to the proper queue
959  if( QFile::exists(dlDir+dlFile) ){
960    if(!oldID.isEmpty()){
961      //Remove the old application first
962      PENDINGINSTALL << oldID+":::"+generateRemoveCMD(oldID);
963    }
964    PENDINGINSTALL << newID+":::"+generateInstallCMD(appID,dlFile);
965  }else{
966    //Generate download command
967    PENDINGDL << newID+":::"+generateDownloadCMD(appID, version);
968  }
969  //Setup the HASH entry for the new PBI
970  PBIHASH.insert(newID, InstalledPBI());
971  PBIHASH[newID].metaID = appID;
972  PBIHASH[newID].version = version;
973  PBIHASH[newID].arch = arch;
974  PBIHASH[newID].downloadfile = dlFile;
975  syncPBI(newID,FALSE);
976 
977}
978 
979 // ===============================
980 // ======   PRIVATE SLOTS   ======
981 // ===============================
982 void PBIBackend::updateDlDirPath(QString newbase){
983   baseDlDir = newbase;
984   if(wardenMode){ 
985     dlDir = wardenDir + baseDlDir;
986   }else{ 
987     dlDir = baseDlDir; 
988   }
989   if(!dlDir.endsWith("/")){ dlDir.append("/"); } 
990 }
991 
992 void PBIBackend::slotWatcherNotification(){
993  watchTimer->start();   
994 }
995 
996 // Internal Process Management
997 void PBIBackend::checkProcesses(){
998   bool again=FALSE;
999   if( cDownload.isEmpty() && !PENDINGDL.isEmpty() ){
1000     //internal management
1001     cDownload = PENDINGDL[0].section(":::",0,0); //should be a pbiID -ONLY-
1002     QString cmd = PENDINGDL[0].section(":::",1,50);
1003     PENDINGDL.removeAt(0);       
1004     if( !cmd.isEmpty() && PBIHASH.contains(cDownload) ){
1005       //Update the PBI status
1006       PBIHASH[cDownload].setStatus(InstalledPBI::DOWNLOADING);
1007       emit PBIStatusChange(cDownload);
1008       //Start the process
1009       PMAN->goToDirectory(ProcessManager::DOWNLOAD,dlDir);
1010       PMAN->startProcess(ProcessManager::DOWNLOAD,cmd);
1011     }else{
1012       cDownload.clear();
1013       again=TRUE; //Move to the next pending download
1014     }
1015   }
1016   if( cInstall.isEmpty() && !PENDINGINSTALL.isEmpty() ){
1017     //internal management
1018     cInstall = PENDINGINSTALL[0].section(":::",0,0); //should be a pbiID -ONLY-
1019     QString cmd = PENDINGINSTALL[0].section(":::",1,50);
1020     PENDINGINSTALL.removeAt(0);         
1021     if( !cmd.isEmpty() && PBIHASH.contains(cInstall) ){
1022       //Update the PBI status
1023       PBIHASH[cInstall].setStatus(InstalledPBI::INSTALLING);
1024       emit PBIStatusChange(cInstall);
1025       //Start the process
1026       PMAN->goToDirectory(ProcessManager::INSTALL,dlDir);
1027       PMAN->startProcess(ProcessManager::INSTALL,cmd);
1028     }else{
1029       cInstall.clear();
1030       again=TRUE; //Move to the next pending install
1031     }
1032   }
1033   if( cRemove.isEmpty() && !PENDINGREMOVAL.isEmpty() ){
1034     //internal management
1035     cRemove = PENDINGREMOVAL[0].section(":::",0,0); //should be a pbiID -ONLY-
1036     QString cmd = PENDINGREMOVAL[0].section(":::",1,50);
1037     PENDINGREMOVAL.removeAt(0);         
1038     if( !cmd.isEmpty() && PBIHASH.contains(cRemove) ){
1039       //Update the PBI status
1040       PBIHASH[cRemove].setStatus(InstalledPBI::REMOVING);
1041       emit PBIStatusChange(cRemove);
1042       //Start the process
1043       PMAN->startProcess(ProcessManager::REMOVE,cmd);
1044     }else{
1045       cRemove.clear();
1046       again=TRUE; //Move to the next pending removal
1047     }
1048   }
1049   if( cUpdate.isEmpty() && !PENDINGUPDATE.isEmpty() ){
1050     //internal management
1051     cUpdate = PENDINGUPDATE[0].section(":::",0,0); //should be a pbiID -ONLY-
1052     QString cmd = PENDINGUPDATE[0].section(":::",1,50);
1053     PENDINGUPDATE.removeAt(0);   
1054     if( !cmd.isEmpty() && PBIHASH.contains(cUpdate) ){
1055       //Update the PBI status
1056       PBIHASH[cUpdate].setStatus(InstalledPBI::UPDATING);
1057       emit PBIStatusChange(cUpdate);
1058       //Start the process
1059       PMAN->startProcess(ProcessManager::UPDATE,cmd);
1060     }else{
1061       cUpdate.clear();
1062       again=TRUE; //Move to the next pending update
1063     }
1064   }
1065   if( cOther.isEmpty() && !PENDINGOTHER.isEmpty() ){
1066     //internal management
1067     cOther = PENDINGOTHER[0].section(":::",0,0); //should be a pbiID -ONLY-
1068     QString cmd = PENDINGOTHER[0].section(":::",1,50);
1069     PENDINGOTHER.removeAt(0);   
1070     if( !cmd.isEmpty() && (PBIHASH.contains(cOther) || cOther=="EXTERNAL") ){
1071       //Update the PBI status
1072       if(cOther!="EXTERNAL"){
1073         PBIHASH[cOther].setStatus(InstalledPBI::WORKING);
1074         emit PBIStatusChange(cOther);
1075       }
1076       //Start the process
1077       PMAN->startProcess(ProcessManager::OTHER,cmd);
1078     }else{
1079       cOther.clear();
1080       again=TRUE; //Move to the next pending command
1081     }
1082   }
1083   if(again){ QTimer::singleShot(10,this,SLOT(checkProcesses()) ); }
1084 }
1085 
1086 void PBIBackend::slotProcessFinished(int ID){
1087   bool resync = FALSE;
1088   if(ID == ProcessManager::UPDATE){
1089     if(sUpdate){
1090       //Update stopped during installation of new version: re-install old version
1091        //get metaID of app
1092        /*QString metaID = PBIHASH[cUpdate].metaID;
1093        slotSyncToDatabase(TRUE);
1094        sleep(2);
1095        installApp(QStringList() << metaID); //will install backup version if available*/
1096     }
1097     //Update the PBIHASH for installed versions
1098     resync=TRUE;
1099     cUpdate.clear(); //remove that it is finished
1100     lUpdate.clear();
1101     sUpdate=FALSE;
1102     resync=TRUE;
1103   }else if(ID == ProcessManager::REMOVE){
1104     if(sRemove){
1105       //Removal Cancelled: Re-install the PBI
1106       QString metaID = PBIHASH[cRemove].metaID; //get the metaID
1107       slotSyncToDatabase(TRUE);
1108       sleep(2);
1109       installApp(QStringList() << metaID);
1110     }
1111     sRemove=FALSE;
1112     cRemove.clear(); //remove that it is finished     
1113     lRemove.clear();
1114   }else if(ID == ProcessManager::INSTALL){
1115     //Add XDG commands to the queue
1116     if(sInstall){
1117       //Installation Cancelled: remove the PBI now that the install is complete
1118       QString cmd = generateRemoveCMD(cInstall);
1119       PENDINGREMOVAL << cInstall+":::"+cmd;
1120     }else{ 
1121       // Installation NOT canceled
1122       qDebug() << "Installation Finished:" << cInstall;
1123       if(!keepDownloads){ QFile::remove(dlDir+PBIHASH[cInstall].downloadfile); }
1124       //Generate XDG commands
1125       QString cmd = generateXDGCMD(cInstall, autoXDG, FALSE);
1126       PENDINGOTHER << cInstall+":::"+cmd;
1127     }
1128     sInstall = FALSE;
1129     cInstall.clear(); //remove that it is finished
1130     lInstall.clear();
1131     resync=TRUE; //make sure to reload local files
1132   }else if(ID == ProcessManager::DOWNLOAD){
1133     //Make sure the download was successful
1134     //qDebug() << "dlDir:" << dlDir << "file:" << PBIHASH[cDownload].downloadfile;
1135     if(sDownload){
1136       //Download Cancelled: remove the (partially) downloaded file
1137       QString fPath = dlDir+PBIHASH[cDownload].downloadfile;
1138       if(QFile::exists(fPath)){
1139         QFile::remove(fPath);
1140       }
1141     }else{
1142       //Download not cancelled
1143       if(!QFile::exists(dlDir+PBIHASH[cDownload].downloadfile)){
1144         qDebug() << "Download Error:" << cDownload << PBIHASH[cDownload].downloadfile;
1145         QString title = QString(tr("%1 Download Error:")).arg(PBIHASH[cDownload].name);
1146         QString err = tr("The PBI could not be downloaded, please try again later");
1147         QStringList log = PMAN->getProcessLog(ProcessManager::DOWNLOAD);
1148         emit Error(title,err,log);
1149       }else{
1150         //Now Check to see if an alternate version needs to be removed
1151         QString otherID = isInstalled( PBIHASH[cDownload].metaID );
1152         QString cmd;
1153         if(!otherID.isEmpty()){
1154           cmd = generateRemoveCMD(otherID);
1155           PENDINGINSTALL << otherID+":::"+cmd; //make sure it happens before the install, so put it in the same queue
1156         }
1157         //Now add the installation of this PBI to the queue
1158         cmd = generateInstallCMD(cDownload, PBIHASH[cDownload].downloadfile);
1159         PENDINGINSTALL << cDownload+":::"+cmd;
1160       }
1161     }
1162     sDownload = FALSE;
1163     cDownload.clear(); //remove that it is finished   
1164     lDownload.clear();
1165   }else if(ID == ProcessManager::OTHER){
1166     //qDebug() << "Other Finished:" << cOther;
1167     //emit PBIStatusChange(cOther);
1168     cOther.clear();       
1169   }
1170   //Get the next processes going
1171   slotSyncToDatabase(resync); //update internal database with/without reading local files again
1172   QTimer::singleShot(0,this,SLOT(checkProcesses()) ); //look for more processes to start
1173 }
1174 
1175void PBIBackend::slotProcessMessage(int ID, QString info){
1176   if(ID == ProcessManager::UPDATE){
1177     //PBIHASH[cUpdate].setStatus(InstalledPBI::UPDATING, dlinfo);
1178     lUpdate = info;
1179     emit PBIStatusChange(cUpdate);
1180   }else if(ID == ProcessManager::DOWNLOAD){
1181     //PBIHASH[cDownload].setStatus(InstalledPBI::DOWNLOADING, dlinfo);
1182     lDownload = info;
1183     emit PBIStatusChange(cDownload);
1184   }else if( ID == ProcessManager::REMOVE){
1185     lRemove = info;
1186     emit PBIStatusChange(cRemove);
1187   }else if( ID == ProcessManager::INSTALL){
1188     lInstall = info;
1189     emit PBIStatusChange(cInstall);
1190   }
1191}
1192
1193void PBIBackend::slotProcessError(int ID, QStringList log){
1194   QString title;
1195   QString name;
1196   QString message;
1197   if(ID == ProcessManager::UPDATE){
1198     if(!sUpdate){ //not stopped manually
1199       //Try to do the update through full download instead
1200       if(PBIHASH.contains(cUpdate)){
1201         QString metaID = PBIHASH[cUpdate].metaID;
1202         if(APPHASH.contains(metaID)){
1203          QString version = APPHASH[metaID].latestVersion;
1204          queueInstall(metaID, version);
1205         }else{
1206          QString name = PBIHASH[cUpdate].name;
1207          title = QString(tr("%1 Update Error:")).arg(name); 
1208          message = tr("The update process experienced an error and could not be completed");
1209         }
1210       }
1211     }
1212   }
1213   else if(ID == ProcessManager::INSTALL){ 
1214     if(!sInstall){ //not stopped manually
1215       if(APPHASH.contains(cInstall)){name = APPHASH[cInstall].name; }
1216       title = QString(tr("%1 Installation Error:")).arg(name);
1217       message = tr("The installation process experienced an error and could not be completed");
1218     }
1219   }
1220   else if(ID == ProcessManager::REMOVE){ 
1221     if(!sRemove){ //not stopped manually
1222       if(PBIHASH.contains(cRemove)){name = PBIHASH[cRemove].name; }
1223       title = QString(tr("%1 Removal Error:")).arg(name);
1224       message = tr("The removal process experienced an error and could not be completed");
1225     }
1226   }
1227   else if(ID == ProcessManager::DOWNLOAD){ 
1228       //The ProcessFinished function has a bit more robust check for download failures
1229       //       - use it instead to prevent duplicate error messages
1230   }
1231   else if(ID == ProcessManager::OTHER){ 
1232     if(PBIHASH.contains(cOther)){name = PBIHASH[cOther].name; }
1233     title = QString(tr("%1 PBI Error:")).arg(name); 
1234     message = tr("The process experienced an error and could not be completed");
1235   }
1236   if(!title.isEmpty() && !message.isEmpty()){
1237     qDebug() << "Process Error:" << title << log;
1238     emit Error(title,message,log); //send error signal
1239   }
1240   slotProcessFinished(ID); //clean up
1241}
1242
1243 // === Database Synchronization ===
1244 void PBIBackend::slotSyncToDatabase(bool localChanges){
1245   //qDebug() << "Sync Database with local changes:" << localChanges;
1246   //Locally Installed PBI Changes
1247   QStringList currInst = installedList();
1248   QStringList sysList = sysDB->installed();
1249   numInstalled = sysList.length();
1250   //All locally installed applications
1251   for(int i=0; i<sysList.length(); i++){
1252     int index = currInst.indexOf(sysList[i]);
1253     if( index == -1){ //New entry
1254       PBIHASH.insert(sysList[i],InstalledPBI()); //add a blank entry
1255       syncPBI(sysList[i],TRUE); //Now update the info
1256       //Add it to the watcher
1257       watcher->addPath(DBDir+"installed/"+sysList[i]);
1258     }else{  //Existing entry - remove it from the currInst list
1259       if(localChanges){ syncPBI(sysList[i],TRUE); } //synchronize the data with local file changes
1260       else{ updateStatus(sysList[i]); } //just update the status
1261       currInst.removeAt(index);
1262     }     
1263   }
1264   //Non-Installed applications
1265   for(int i=0; i<currInst.length(); i++){
1266     updateStatus(currInst[i]); //update status
1267     InstalledPBI::PBISTATUS stat = PBIHASH[currInst[i]].status;
1268     bool actionPending = (stat != InstalledPBI::NONE) && (stat != InstalledPBI::UPDATEAVAILABLE);
1269     if( !actionPending ){ PBIHASH.remove(currInst[i]); }
1270     else{} //do nothing here
1271   }
1272   emit LocalPBIChanges(); //Let others know that the local PBI's have been updated
1273   //Repo Changes
1274   //qDebug() << "noRepo" << noRepo << "Invalid Repo:" << sysDB->currentRepo() << sysDB->currentRepoInvalid();
1275   if( noRepo || sysDB->currentRepoInvalid() ){
1276     //Change to the first repo available
1277     qDebug() << "Try to find an alternate Repo:";
1278     QStringList repos = sysDB->availableRepos();
1279     if(repos.length() > 0){ 
1280       sysDB->setRepo(repos[0]);
1281     }
1282     syncCurrentRepo();
1283   }else if( CATHASH.isEmpty() && APPHASH.isEmpty() ){
1284     qDebug() << "Load Repo Information";
1285     //If successful, the repo data should only be loaded once
1286     syncCurrentRepo(); 
1287     //If the sync was successful, re-run the PBI sync process to update the license info
1288     if( !APPHASH.isEmpty() ){               
1289       QTimer::singleShot(10,this,SLOT(slotSyncToDatabase())); 
1290     }else{
1291       QTimer::singleShot(60000,this,SLOT(slotSyncToDatabase())); //try again in a minute 
1292     }
1293   }
1294 }
1295 
1296 void PBIBackend::syncPBI(QString pbiID, bool useDB){
1297   //This function is mainly used for initializing the PBIHASH entry
1298   //  but is also used for updating the entry if the local installation settings change (useDB=true)
1299         
1300   //useDB: pull info from the locally installed database (pbiID MUST be installed locally)
1301   //Get the PBI structure
1302   if( !PBIHASH.contains(pbiID) ){ return; }
1303   InstalledPBI pbi = PBIHASH[pbiID];
1304   //Get the associated appID
1305   QString appID = pbi.metaID;
1306   QStringList info = sysDB->installedPbiInfo(pbiID);
1307   if(useDB && !info.isEmpty()){
1308     //Now get additional database info
1309     bool autoUp = sysDB->installedPbiAutoUpdate(pbiID);
1310     bool root = sysDB->installedPbiNeedsRoot(pbiID);
1311     bool desktop = sysDB->installedPbiHasXdgDesktop(info[6]);
1312     bool menu = sysDB->installedPbiHasXdgMenu(info[6]);
1313     bool mime = sysDB->installedPbiHasXdgMime(info[6]);
1314     //Now add this info to the PBI structure
1315     pbi.name    = info[0];
1316     pbi.version = info[1];
1317     pbi.arch    = info[2];
1318     pbi.mdate   = info[3];
1319     pbi.author  = info[4];
1320     pbi.website = info[5];
1321     if(pbi.website.endsWith("/")){ pbi.website.chop(1); }
1322     pbi.path    = info[6];
1323     pbi.icon    = info[7];
1324     pbi.maintainer = info[8];
1325     pbi.description = info[9];
1326     pbi.fbsdversion = info[10];
1327     if(appID.isEmpty()){ 
1328       appID = Extras::nameToID(pbi.name); 
1329       pbi.metaID = appID;
1330     } //for new item initialization
1331     if(APPHASH.contains(appID)){
1332       pbi.license = APPHASH[appID].license;       
1333     }else{
1334       pbi.license = tr("Unknown");
1335     }
1336     pbi.rootInstall = root;
1337     pbi.autoUpdate  = autoUp;
1338     pbi.desktopIcons= desktop;
1339     pbi.menuIcons   = menu;
1340     pbi.mimetypes = mime;
1341     //Clean up the mdate to make it human-readable
1342     QDate date(pbi.mdate.left(4).toInt(), pbi.mdate.mid(4,2).toInt(), pbi.mdate.right(2).toInt() );
1343     pbi.mdate =date.toString(Qt::SystemLocaleShortDate); //put it in the current locale format (short version)
1344   }else{
1345     //Pull basic info from the pre-loaded App database instead
1346     // This is for application entries still in a pending state and not fully installed
1347     if(!APPHASH.contains(appID)){ return; }
1348     pbi.name = APPHASH[appID].name;
1349     //Do not change the version or arch - this is usually why the app is in a pending state
1350     pbi.author = APPHASH[appID].author;
1351     pbi.website = APPHASH[appID].website;
1352     pbi.icon = APPHASH[appID].localIcon;
1353     pbi.license = APPHASH[appID].license;
1354     pbi.rootInstall = APPHASH[appID].requiresroot;
1355     pbi.autoUpdate=FALSE;
1356     pbi.desktopIcons=FALSE;
1357     pbi.menuIcons=FALSE;
1358     if(pbi.metaID.isEmpty()){ pbi.metaID = appID; }
1359   }
1360   //Now add this pbi structure back into the hash
1361   PBIHASH.insert(pbiID, pbi); 
1362   //Now update the status
1363   updateStatus(pbiID);
1364 }
1365 
1366 void PBIBackend::slotUpdateAllStatus(){
1367   QStringList pbiID = PBIHASH.keys();
1368   for(int i=0; i<pbiID.length(); i++){
1369     updateStatus(pbiID[i]);     
1370   }
1371 }
1372 
1373 void PBIBackend::updateStatus(QString pbiID){
1374   if(!PBIHASH.contains(pbiID)){ return; }
1375   QString upgrade = upgradeAvailable(pbiID);
1376   QString chk = pbiID+":::"; //for list checking
1377   QString iIndex = PENDINGINSTALL.filter(chk).join(" "); //special case for install list
1378   if(cDownload == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::DOWNLOADING);}
1379   else if(cInstall == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::INSTALLING);}
1380   else if(cRemove == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::REMOVING);}
1381   else if(cUpdate == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::UPDATING);}
1382   //Look through the pending lists
1383   else if(PENDINGDL.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGDOWNLOAD);}
1384   else if(!iIndex.isEmpty()){ //install queue can also have special-case removals
1385     if(iIndex.contains("pbi_delete")){ PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGREMOVAL); }
1386     else{ PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGINSTALL); }
1387   }else if(PENDINGREMOVAL.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGREMOVAL);}
1388   else if(PENDINGUPDATE.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGUPDATE);}
1389   //else if(PENDINGOTHER.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::WORKING);}
1390   else if( !upgrade.isEmpty() ){PBIHASH[pbiID].setStatus(InstalledPBI::UPDATEAVAILABLE); }
1391   else{ PBIHASH[pbiID].setStatus(InstalledPBI::NONE); }
1392 }
1393 
1394 void PBIBackend::syncCurrentRepo(){
1395  //Calling this function will automatically clear and re-populate the APPHASH and CATHASH fields
1396  APPHASH.clear(); CATHASH.clear();
1397  QString mFile = sysDB->metaFilePath(); 
1398  QString iFile = sysDB->indexFilePath();
1399  if( QFile::exists(mFile) && QFile::exists(iFile) ){
1400   //First do the meta data (app/cat info)
1401   QStringList metaFile = Extras::readFile(mFile);
1402   QStringList catsUsed, catsAvail;
1403   //qDebug() << "Sync Meta File Info";
1404   for(int i=0; i<metaFile.length(); i++){
1405     if(metaFile[i].startsWith("App=")){
1406       QStringList info = sysDB->parseAppMetaLine(metaFile[i].section("=",1,50,QString::SectionSkipEmpty));
1407       //qDebug() << "App Meta data:" << info;
1408       if(!info.isEmpty()){ //Make sure the info list is valid
1409         //info[name,category,remoteIcon,author,website,license,apptype,tags,description,requiresroot] (5/1/2013)
1410         //Simplify a couple pieces of info
1411         QString metaID = Extras::nameToID(info[0]);
1412         QString localIcon = sysDB->remoteToLocalIcon(info[0],info[2]);
1413         //Create the container and fill it
1414         MetaPBI app;
1415         app.name=info[0]; app.category=info[1]; app.remoteIcon=info[2];
1416         app.localIcon=localIcon; app.author=info[3]; app.website=info[4];
1417         app.license=info[5]; app.appType=info[6]; app.tags=info[7].toLower().split(","); 
1418         app.description=info[8];
1419         if(info[9]=="true"){ app.requiresroot=TRUE; }
1420         else{ app.requiresroot=FALSE; }
1421         app.dateadded=info[10];
1422         app.maintainer=info[11];
1423         app.shortdescription=info[12];
1424         //Fix the website if needed
1425         if(app.website.endsWith("/")){ app.website.chop(1); }
1426         //Add it to the hash
1427         APPHASH.insert(metaID,app);
1428       }
1429     }else if(metaFile[i].startsWith("Cat=")){
1430       QStringList info = sysDB->parseCatMetaLine(metaFile[i].section("=",1,50,QString::SectionSkipEmpty));
1431       //qDebug() << "Cat Meta Data:" << info;
1432       if(info.length() > 2){ //Make sure the info list is complete
1433         //info[name,remoteicon,description,?] (5/1/2013)
1434         QString catID = Extras::nameToID(info[0]);
1435         QString localIcon = sysDB->remoteToLocalIcon(info[0],info[1]);
1436         //Create the container and fill it
1437         MetaCategory cat;
1438         cat.name=info[0]; cat.remoteIcon=info[1]; cat.localIcon=localIcon; cat.description=info[2];
1439         //Add it to the available list
1440         catsAvail << catID;
1441         //Add it to the hash
1442         CATHASH.insert(catID,cat);
1443       }
1444     }
1445     //qDebug() << " - done with meta line";
1446   }
1447   //qDebug() << "Sync Index File Info";
1448   //Then do the list of available PBI's
1449   QStringList indexFile = Extras::readFile(iFile);
1450   bool sys64 = (sysArch=="amd64");
1451   for(int i=0; i<indexFile.length(); i++){
1452     QStringList info = sysDB->parseIndexLine(indexFile[i]);
1453       //info[name, arch, version, datetime, size, isLatest(true/false)]
1454     if(!info.isEmpty()){
1455       QString metaID = Extras::nameToID(info[0]);
1456       if(APPHASH.contains(metaID)){
1457         bool islatest = (info[5]=="true");
1458         bool is64 = (info[1] == "amd64");
1459         bool save=FALSE;
1460         //Determine whether to save the data or not
1461         if( !sys64 && is64 ){}   // do not save 64-bit apps on a 32-bit system
1462         else if(info[1] == sysArch){ save=TRUE; }  // high priority for identical architecture
1463         else{ //lower priority for 32-bit App on 64-bit system
1464           if(islatest){ 
1465             if(APPHASH[metaID].latestDatetime.isEmpty()){ save=TRUE; } //nothing saved yet, go ahead
1466             else if(APPHASH[metaID].latestArch==sysArch){} // do not save over an app that is the proper arch
1467             else if(Extras::newerDateTime(info[3], APPHASH[metaID].latestDatetime) ){ save=TRUE; } //save over older app
1468           }else{
1469             if(APPHASH[metaID].backupDatetime.isEmpty()){ save=TRUE; } //nothing saved yet, go ahead
1470             else if(APPHASH[metaID].backupArch==sysArch){} // do not save over an app that is the proper arch
1471             else if(Extras::newerDateTime(info[3], APPHASH[metaID].backupDatetime) ){ save=TRUE; } //save over older app
1472           }
1473         }
1474         //Save the data if appropriate
1475         if(save && islatest){
1476           APPHASH[metaID].latestVersion=info[2];
1477           APPHASH[metaID].latestDatetime=info[3];
1478           APPHASH[metaID].latestArch=info[1]; 
1479           APPHASH[metaID].latestSizeK=info[4];
1480           APPHASH[metaID].latestFilename = info[6];
1481         }else if(save){
1482           APPHASH[metaID].backupVersion=info[2];
1483           APPHASH[metaID].backupDatetime=info[3];
1484           APPHASH[metaID].backupArch=info[1]; 
1485           APPHASH[metaID].backupSizeK=info[4];
1486           APPHASH[metaID].backupFilename=info[6];
1487         } 
1488         //if(save){ qDebug() << "APP:" << metaID << info[1] << info[2] << info[3] << info[4] << info[5]; }
1489       } //end check for ID in hash
1490     } //end check for info available
1491   } //end loop over index file
1492   
1493   //Now clean up the APPHASH to remove any entries that do not have a PBI associated with them
1494   QStringList apps = APPHASH.keys();
1495   for(int i=0; i<apps.length(); i++){
1496     if(APPHASH[apps[i]].latestVersion.isEmpty()){
1497       //Make sure there is not a backup version we can use instead
1498       if(APPHASH[apps[i]].backupVersion.isEmpty()){
1499         APPHASH.remove(apps[i]);           
1500       }else{
1501         //Move over the backup to the latest
1502         APPHASH[apps[i]].latestVersion=  APPHASH[apps[i]].backupVersion;
1503         APPHASH[apps[i]].latestDatetime= APPHASH[apps[i]].backupDatetime;
1504         APPHASH[apps[i]].latestArch=     APPHASH[apps[i]].backupArch; 
1505         APPHASH[apps[i]].latestSizeK=    APPHASH[apps[i]].backupSizeK;
1506         APPHASH[apps[i]].latestFilename= APPHASH[apps[i]].backupFilename;
1507         //Now clear the backup
1508         APPHASH[apps[i]].backupVersion.clear();
1509         APPHASH[apps[i]].backupDatetime.clear();
1510         APPHASH[apps[i]].backupArch.clear(); 
1511         APPHASH[apps[i]].backupSizeK.clear();
1512         APPHASH[apps[i]].backupFilename.clear();
1513       }
1514     }else{
1515       //Category being used, remove this category from the list
1516       int catID = catsAvail.indexOf( Extras::nameToID(APPHASH[apps[i]].category) );
1517       if(catID != -1){ catsAvail.removeAt(catID); }
1518     }
1519   }
1520   //Now remove any empty categories (ones left over in catAvail list)
1521   for(int i=0; i<catsAvail.length(); i++){
1522     if( CATHASH.contains(catsAvail[i]) ){
1523       //qDebug() << " - Empty category:" << catsAvail[i];
1524       CATHASH.remove(catsAvail[i]); 
1525     }
1526   }
1527  } // end check for both files existing
1528 
1529  if(APPHASH.isEmpty() && CATHASH.isEmpty()){
1530    numAvailable = -1;
1531    emit NoRepoAvailable();       
1532  }else{
1533    numAvailable = QStringList(APPHASH.keys()).length(); //update the number of apps available
1534    emit RepositoryInfoReady();
1535  }
1536 }
1537 
1538void PBIBackend::startFileSystemWatcher(){
1539   QStringList watched = watcher->directories();
1540   if(!watched.isEmpty()){ watcher->removePaths(watched); }//clear the watcher first
1541   watcher->addPath( DBDir+"installed" ); //look for installed app changes
1542   watcher->addPath( DBDir+"repos" ); //look for repo changes
1543   watcher->addPath( DBDir+"index" ); //look for index/meta file changes         
1544}
1545
1546void PBIBackend::stopFileSystemWatcher(){
1547   QStringList watched = watcher->directories();
1548   if(!watched.isEmpty()){ watcher->removePaths(watched); }//clear the watcher first     
1549}
Note: See TracBrowser for help on using the repository browser.