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

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

Clean up the AppCafe? browser home page - now just have a toolbutton to browser categories, and instead put recommended applications on the home page (new defaultrecommendations.txt resource). These recommendations can be automatically updated later if we add that ability for the repo to include an additional file with this information.

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