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

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

Fix up the browser app page status notifications with the new backend process class.

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