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

9.2-releasereleng/10.0releng/10.0.1releng/10.0.2
Last change on this file since c71b68c was c71b68c, checked in by Ken Moore <ken@…>, 13 months ago

Add a bunch of error loggin to the backend processes, and also add a new dialog for displaying these error logs. Also fix the detection of an update failure.

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