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

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

Clean up a couple more backup functions in the AppCafe?:
1) If you try to remove a non-installed PBI, it will simply cancel the installation instead
2) Make sure that if a non-installed PBI is right-clicked, it only shows the "cancel" option.

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