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

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

Add a quick "cancel" button to the AppCafe?, and make the quick buttons dynamically visible as necessary. Also fix up and add a couple icons for the small update button.

  • Property mode set to 100644
File size: 53.8 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,QString)),this,SLOT(slotProcessError(int,QString)) );
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  for(int i=0; i<pbilist.length(); i++){
561    if(PBIHASH[pbilist[i]].metaID == appID){
562      switch (PBIHASH[pbilist[i]].status){
563        case InstalledPBI::DOWNLOADING:
564          output = tr("Downloading"); break;
565        case InstalledPBI::INSTALLING:
566          output = tr("Installing"); break;
567        case InstalledPBI::REMOVING:
568          output = tr("Removing"); break;
569        case InstalledPBI::UPDATING:
570          output = tr("Updating"); break;
571        case InstalledPBI::PENDINGDOWNLOAD:
572          output = tr("Pending Download"); break;
573        case InstalledPBI::PENDINGINSTALL:
574          output = tr("Pending Install"); break;
575        case InstalledPBI::PENDINGREMOVAL:
576          output = tr("Pending Removal"); break;
577        case InstalledPBI::PENDINGUPDATE:
578          output = tr("Pending Update"); break;
579        default: //do nothing for the rest
580          output.clear();
581      }
582      if(!output.isEmpty()){ break; } //break out of the loop
583    }
584  }
585  return output;*/
586}
587
588// === Configuration Management ===
589void PBIBackend::openConfigurationDialog(){
590  //temporarily disable the filesystem watcher (causes issues with repo changes)
591  stopFileSystemWatcher();
592  //Now setup the UI
593  ConfigDialog *cfg = new ConfigDialog(parentWidget);
594  cfg->xdgOpts = autoXDG;
595  cfg->keepDownloads = keepDownloads;
596  cfg->downloadDir = baseDlDir;
597  cfg->DB = sysDB; //for access to database operations
598  cfg->setupDone();
599  //Now run it
600  cfg->exec(); //makes it modal and waits for it to finish
601  //Now see if there are changes to save
602  if(cfg->applyChanges){
603    autoXDG = cfg->xdgOpts;
604    keepDownloads = cfg->keepDownloads;
605    updateDlDirPath(cfg->downloadDir);
606    //Now save the configuration data to file
607    saveSettings();
608
609  }
610  //Now re-enable the filesystem watcher
611  startFileSystemWatcher();
612  //Now re-sync the repo info
613  syncCurrentRepo();
614}
615
616// === Import/Export PBI Lists ===
617bool PBIBackend::exportPbiListToFile(QString filepath){
618  bool ok = FALSE;
619  //get the currently installed PBI's
620  QStringList list = PBIHASH.keys();
621  QStringList installed;
622  for(int i=0; i<list.length(); i++){
623    if(!PBIHASH[list[i]].path.isEmpty()){ installed << PBIHASH[list[i]].metaID; }         
624  }
625  qDebug() << "Export List:" << installed;
626  //Now save the list
627  if(installed.isEmpty()){ ok = TRUE; }
628  else{ ok = Extras::writeFile(filepath,installed); }
629  return ok;
630}
631
632bool PBIBackend::importPbiListFromFile(QString filepath){
633  bool ok = FALSE;
634  if(!QFile::exists(filepath)){ return ok; }
635  QStringList inlist = Extras::readFile(filepath);
636  if(inlist.isEmpty()){ return ok; }
637  qDebug() << "Import List:" << inlist;
638  //Now sort the apps based on validity
639  QStringList good, bad, current; 
640  for(int i=0; i<inlist.length(); i++){
641    QString app = inlist[i].toLower();
642    if(app.isEmpty()){continue;}
643    else if(!APPHASH.contains(app)){ bad << inlist[i]; }
644    else if( !isInstalled(app).isEmpty() ){ current << inlist[i]; }
645    else{ good << app; } //make sure to use the appID here
646  }
647  ok = TRUE;
648  //List the results
649  if(good.isEmpty()){
650    QString results = tr("No applications to install from this list.")+"\n"+tr("Results:")+"\n";
651    if(!bad.isEmpty()){ results.append( QString(tr("Unavailable Apps: %1")).arg(bad.join(",  "))+"\n" ); }
652    if(!current.isEmpty()){ results.append( QString(tr("Currently Installed: %1")).arg(current.join(",  "))+"\n" ); }
653    QMessageBox::information(0,tr("Import Results"),results);
654  }else{
655    QString results = tr("Are you sure you wish to install these applications?")+"\n\n"+good.join(", ");
656    if( QMessageBox::question(0,tr("Import Results"),results,QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes){
657      //Go ahead and install these applications
658      installApp(good);
659    }
660  }
661  return ok;
662}
663
664 // ==========================
665 // ====== PUBLIC SLOTS ======
666 // ==========================
667 // NOTE: These functions should only be called from a QTimer::singleShot()
668 //    Because outputs come via different signals (due to time for these functions to run)
669 
670void PBIBackend::startAppSearch(){
671 //  Outputs come via the "SearchComplete(QStringList best,QStringList rest)" signal
672 QString search = searchTerm; //This public variable needs to be set beforehand by the calling process
673 QStringList best, rest;
674 //Now perform the search and categorize it
675 search = search.toLower();
676 QStringList namematch, tagmatch, descmatch;
677 QStringList app = APPHASH.keys();
678 for(int i=0; i<app.length(); i++){
679   if(APPHASH[app[i]].name.toLower() == search){ best << app[i]; } //exact match - top of the "best" list
680   else if(APPHASH[app[i]].name.toLower().contains(search)){ namematch << app[i]; }
681   else if(APPHASH[app[i]].tags.contains(search)){ tagmatch << app[i]; }
682   else if(APPHASH[app[i]].description.contains(search)){ descmatch << app[i]; }
683 }
684 //Now sort the lists and assign a priority
685 namematch.sort(); tagmatch.sort(); descmatch.sort();
686 best << namematch;
687 if(best.isEmpty()){ best << tagmatch; }
688 else{ rest << tagmatch; }
689 if(best.isEmpty()){ best << descmatch; }
690 else{ rest << descmatch; }
691
692 //Now emit the signal with the results
693 emit SearchComplete(best,rest);
694}
695
696void PBIBackend::startSimilarSearch(){
697  //  Outputs come via the "SimilarFound(QStringList results)" signal
698  QString sID = searchSimilar; // this public variable needs to be set beforehand by the calling process
699  QStringList output; 
700  if(!APPHASH.contains(sID)){ return; } 
701  //Now find the tags on the given ID
702  QStringList stags = APPHASH[sID].tags;
703  QStringList apps = APPHASH.keys();
704  QStringList unsorted;
705  int maxMatch=0;
706  for(int i=0; i<apps.length(); i++){
707    if(apps[i]==sID){continue;} //skip the app we were given for search parameters
708    QStringList tags = APPHASH[apps[i]].tags;
709    int match=0;
710    for(int j=0; j<stags.length(); j++){
711       if(tags.indexOf(stags[j]) != -1){ match++; }
712    }
713    if(match > 1){ unsorted << QString::number(match)+"::"+apps[i]; }
714    if(match > maxMatch){ maxMatch = match; }
715  }
716  unsorted.sort();
717  for(int i=unsorted.length(); i>0; i--){
718    if( unsorted[i-1].section("::",0,0).toInt() > (maxMatch-1) ){
719      output << unsorted[i-1].section("::",1,50);
720    }else if( output.length() < 5 ){ //not enough matches, grab the next set too
721      output << unsorted[i-1].section("::",1,50);
722      maxMatch--;
723    }else{ break; } //already sorted and the rest are even lower match rating
724  }
725  //Now emit the signal with the results
726  emit SimilarFound(output);
727}
728
729 // ===============================
730 // ====== PRIVATE FUNCTIONS ======
731 // ===============================
732bool PBIBackend::saveSettings(){
733  //Generate the file layout
734  QStringList file;
735  file << "POSTINSTALL="+autoXDG.join(",").simplified();
736  file << "REPONUMBER="+sysDB->currentRepo();
737  if(keepDownloads){ file << "KEEPDOWNLOADS=TRUE"; }
738  else{ file << "KEEPDOWNLOADS=FALSE"; }
739  file << "DOWNLOADDIR="+baseDlDir;
740  //Now save the file
741  bool ok = Extras::writeFile(settingsFile, file);
742  return ok;
743}
744
745bool PBIBackend::loadSettings(){
746  if(!QFile::exists(settingsFile)){ return FALSE; }
747  QStringList file = Extras::readFile(settingsFile);
748  if(file.isEmpty()){ return FALSE; }
749  for(int i=0; i<file.length(); i++){
750    if(file[i].startsWith("POSTINSTALL=")){ autoXDG = file[i].section("=",1,1).split(","); }
751    else if(file[i].startsWith("REPONUMBER=")){ sysDB->setRepo(file[i].section("=",1,1)); }
752    else if(file[i].startsWith("KEEPDOWNLOADS=")){ 
753      if(file[i].section("=",1,1) == "TRUE"){ keepDownloads = TRUE; }
754      else{ keepDownloads = FALSE; }
755    }else if(file[i].startsWith("DOWNLOADDIR=")){ updateDlDirPath(file[i].section("=",1,1)); }
756  }
757  return TRUE;
758}
759 
760 QString PBIBackend::addRootCMD(QString cmd, bool needRoot){
761   //Check for warden and root permissions and adjust the command accordingly
762   if(wardenMode){
763     cmd.prepend("warden chroot "+wardenIP+" \""); cmd.append("\"");
764   }else if( needRoot ){
765     cmd.prepend("pc-su \""); cmd.append("\"");     
766   }
767   return cmd;
768 }
769 
770 QString PBIBackend::generateUpdateCMD(QString pbiID){
771   QString output;
772   if(!PBIHASH.contains(pbiID)){ return output; }
773   output = "pbi_update "+pbiID;
774   output = addRootCMD(output, PBIHASH[pbiID].rootInstall);
775   return output;
776 }
777 
778 QString PBIBackend::generateRemoveCMD(QString pbiID){
779   QString output;
780   if(!PBIHASH.contains(pbiID)){ return output; }
781   output = "pbi_delete "+pbiID;
782   output = addRootCMD(output, PBIHASH[pbiID].rootInstall);
783   return output;       
784 }
785 
786 QString PBIBackend::generateAutoUpdateCMD(QString pbiID, bool enable){
787   QString output;
788   if(!PBIHASH.contains(pbiID)){ return output; }
789   output = "pbi_update";
790   if(enable){ output.append(" --enable-auto"); }
791   else{ output.append(" --disable-auto"); }
792   output.append(" "+pbiID);
793   output = addRootCMD(output, PBIHASH[pbiID].rootInstall);
794   return output;       
795 }
796
797 QString PBIBackend::generateXDGCMD(QString pbiID, QStringList actions, bool allusers){
798   QString output;
799   if(!PBIHASH.contains(pbiID)){ return output; }
800   output = "pbi_icon";
801   if(actions.contains("desktop")){ output.append(" add-desktop"); }
802   if(actions.contains("menu")){ output.append(" add-menu"); }
803   if(actions.contains("paths")){ output.append(" add-pathlnk"); }
804   if(actions.contains("mime")){ output.append(" add-mime"); }
805   if(actions.contains("remove-desktop")){ output.append(" del-desktop"); }
806   if(actions.contains("remove-menu")){ output.append(" del-menu"); }
807   if(actions.contains("remove-paths")){ output.append(" del-pathlnk"); }
808   if(actions.contains("remove-mime")){ output.append(" del-mime"); }
809   //Check command actions
810   if(output == "pbi_icon"){ 
811     output.clear(); //no actions - do nothing
812   }else{
813     output.append(" "+pbiID);
814     output = addRootCMD(output,allusers);
815   }
816   return output;       
817 }
818 
819 QString PBIBackend::generateDownloadCMD(QString appID, QString version){
820   if(!APPHASH.contains(appID)){ return ""; }
821   QString output = "pbi_add -R --repo "+sysDB->currentRepo();
822   if(!version.isEmpty()){ output.append(" --rVer "+version); }
823   output.append(" "+appID);
824   return output;
825 }
826 
827 QString PBIBackend::generateInstallCMD(QString filename){
828   QString output = "pbi_add --licagree "+filename;
829   return output;
830 }
831 
832 QStringList PBIBackend::removePbiCMD(QString pbiID, QStringList list){
833   //Used for removing any pending CMD's for a particular pbiID
834   QStringList output;
835   for(int i=0; i<list.length(); i++){
836     if(!list[i].startsWith(pbiID)){ output << list[i]; }         
837   }
838   return output;
839 }
840 
841 // ===============================
842 // ======   PRIVATE SLOTS   ======
843 // ===============================
844 void PBIBackend::updateDlDirPath(QString newbase){
845   baseDlDir = newbase;
846   if(wardenMode){ 
847     dlDir = wardenDir + baseDlDir;
848   }else{ 
849     dlDir = baseDlDir; 
850   }
851   if(!dlDir.endsWith("/")){ dlDir.append("/"); } 
852 }
853 
854 // Internal Process Management
855 void PBIBackend::checkProcesses(){
856   bool again=FALSE;
857   if( cDownload.isEmpty() && !PENDINGDL.isEmpty() ){
858     //internal management
859     cDownload = PENDINGDL[0].section(":::",0,0); //should be a pbiID -ONLY-
860     QString cmd = PENDINGDL[0].section(":::",1,50);
861     PENDINGDL.removeAt(0);       
862     if( !cmd.isEmpty() && PBIHASH.contains(cDownload) ){
863       //Update the PBI status
864       PBIHASH[cDownload].setStatus(InstalledPBI::DOWNLOADING);
865       emit PBIStatusChange(cDownload);
866       //Start the process
867       PMAN->goToDirectory(ProcessManager::DOWNLOAD,dlDir);
868       PMAN->startProcess(ProcessManager::DOWNLOAD,cmd);
869     }else{
870       cDownload.clear();
871       again=TRUE; //Move to the next pending download
872     }
873   }
874   if( cInstall.isEmpty() && !PENDINGINSTALL.isEmpty() ){
875     //internal management
876     cInstall = PENDINGINSTALL[0].section(":::",0,0); //should be a pbiID -ONLY-
877     QString cmd = PENDINGINSTALL[0].section(":::",1,50);
878     PENDINGINSTALL.removeAt(0);         
879     if( !cmd.isEmpty() && PBIHASH.contains(cInstall) ){
880       //Update the PBI status
881       PBIHASH[cInstall].setStatus(InstalledPBI::INSTALLING);
882       emit PBIStatusChange(cInstall);
883       //Start the process
884       PMAN->goToDirectory(ProcessManager::INSTALL,dlDir);
885       PMAN->startProcess(ProcessManager::INSTALL,cmd);
886     }else{
887       cInstall.clear();
888       again=TRUE; //Move to the next pending install
889     }
890   }
891   if( cRemove.isEmpty() && !PENDINGREMOVAL.isEmpty() ){
892     //internal management
893     cRemove = PENDINGREMOVAL[0].section(":::",0,0); //should be a pbiID -ONLY-
894     QString cmd = PENDINGREMOVAL[0].section(":::",1,50);
895     PENDINGREMOVAL.removeAt(0);         
896     if( !cmd.isEmpty() && PBIHASH.contains(cRemove) ){
897       //Update the PBI status
898       PBIHASH[cRemove].setStatus(InstalledPBI::REMOVING);
899       emit PBIStatusChange(cRemove);
900       //Start the process
901       PMAN->startProcess(ProcessManager::REMOVE,cmd);
902     }else{
903       cRemove.clear();
904       again=TRUE; //Move to the next pending removal
905     }
906   }
907   if( cUpdate.isEmpty() && !PENDINGUPDATE.isEmpty() ){
908     //internal management
909     cUpdate = PENDINGUPDATE[0].section(":::",0,0); //should be a pbiID -ONLY-
910     QString cmd = PENDINGUPDATE[0].section(":::",1,50);
911     PENDINGUPDATE.removeAt(0);   
912     if( !cmd.isEmpty() && PBIHASH.contains(cUpdate) ){
913       //Update the PBI status
914       PBIHASH[cUpdate].setStatus(InstalledPBI::UPDATING);
915       emit PBIStatusChange(cUpdate);
916       //Start the process
917       PMAN->startProcess(ProcessManager::UPDATE,cmd);
918     }else{
919       cUpdate.clear();
920       again=TRUE; //Move to the next pending update
921     }
922   }
923   if( cOther.isEmpty() && !PENDINGOTHER.isEmpty() ){
924     //internal management
925     cOther = PENDINGOTHER[0].section(":::",0,0); //should be a pbiID -ONLY-
926     QString cmd = PENDINGOTHER[0].section(":::",1,50);
927     PENDINGOTHER.removeAt(0);   
928     if( !cmd.isEmpty() && PBIHASH.contains(cOther) ){
929       //Update the PBI status
930       PBIHASH[cOther].setStatus(InstalledPBI::WORKING);
931       emit PBIStatusChange(cOther);
932       //Start the process
933       PMAN->startProcess(ProcessManager::OTHER,cmd);
934     }else{
935       cOther.clear();
936       again=TRUE; //Move to the next pending command
937     }
938   }
939   if(again){ QTimer::singleShot(10,this,SLOT(checkProcesses()) ); }
940 }
941 
942 void PBIBackend::slotProcessFinished(int ID){
943   bool resync = FALSE;
944   if(ID == ProcessManager::UPDATE){
945     cUpdate.clear(); //remove that it is finished
946     sUpdate=FALSE;
947     resync=TRUE;
948   }else if(ID == ProcessManager::REMOVE){
949     sRemove=FALSE;
950     cRemove.clear(); //remove that it is finished         
951   }else if(ID == ProcessManager::INSTALL){
952     //Add XDG commands to the queue
953     if(!sInstall){  // do not continue on if it was cancelled
954       qDebug() << "Installation Finished:" << cInstall;
955       if(!keepDownloads){ QFile::remove(dlDir+PBIHASH[cInstall].downloadfile); }
956       //Generate XDG commands
957       QString cmd = generateXDGCMD(cInstall, autoXDG, FALSE);
958       PENDINGOTHER << cInstall+":::"+cmd;
959     }
960     sInstall = FALSE;
961     cInstall.clear(); //remove that it is finished
962     resync=TRUE; //make sure to reload local files
963   }else if(ID == ProcessManager::DOWNLOAD){
964     //Make sure the download was successful
965     //qDebug() << "dlDir:" << dlDir << "file:" << PBIHASH[cDownload].downloadfile;
966     if(!sDownload){  // do not continue on if it was cancelled
967       if(!QFile::exists(dlDir+PBIHASH[cDownload].downloadfile)){
968         qDebug() << "Download Error:" << cDownload << PBIHASH[cDownload].downloadfile;
969         QString title = QString(tr("%1 Download Error:")).arg(PBIHASH[cDownload].name);
970         QString err = tr("The PBI could not be downloaded, please try again later");
971         emit Error(title,err);
972       }else{
973         //Now Check to see if an alternate version needs to be removed
974         QString otherID = isInstalled( Extras::nameToID(PBIHASH[cDownload].name) );
975         QString cmd;
976         if(!otherID.isEmpty()){
977           cmd = generateRemoveCMD(otherID);
978           PENDINGINSTALL << otherID+":::"+cmd; //make sure it happens before the install, so put it in the same queue
979         }
980         //Now add the installation of this PBI to the queue
981         cmd = generateInstallCMD(PBIHASH[cDownload].downloadfile);
982         PENDINGINSTALL << cDownload+":::"+cmd;
983       }
984     }
985     sDownload = FALSE;
986     cDownload.clear(); //remove that it is finished   
987   }else if(ID == ProcessManager::OTHER){
988     cOther.clear();       
989   }
990   //Get the next processes going
991   slotSyncToDatabase(resync); //update internal database with/without reading local files again
992   QTimer::singleShot(0,this,SLOT(checkProcesses()) ); //look for more processes to start
993 }
994 
995void PBIBackend::slotProcessMessage(int ID, QString dlinfo){
996   if(ID == ProcessManager::UPDATE){
997     PBIHASH[cUpdate].setStatus(InstalledPBI::UPDATING, dlinfo); 
998     emit PBIStatusChange(cUpdate);
999   }else if(ID == ProcessManager::DOWNLOAD){
1000     PBIHASH[cDownload].setStatus(InstalledPBI::DOWNLOADING, dlinfo); 
1001     emit PBIStatusChange(cDownload);
1002   }   
1003}
1004
1005void PBIBackend::slotProcessError(int ID, QString err){
1006   QString title;
1007   QString name;
1008   if(ID == ProcessManager::UPDATE){
1009     if(!sUpdate){ //not stopped manually
1010       if(PBIHASH.contains(cUpdate)){name = PBIHASH[cUpdate].name; }
1011       title = QString(tr("%1 Update Error:")).arg(name); 
1012     }
1013   }
1014   else if(ID == ProcessManager::INSTALL){ 
1015     if(!sInstall){ //not stopped manually
1016       if(APPHASH.contains(cInstall)){name = APPHASH[cInstall].name; }
1017       title = QString(tr("%1 Installation Error:")).arg(name);
1018     }
1019   }
1020   else if(ID == ProcessManager::REMOVE){ 
1021     if(!sRemove){ //not stopped manually
1022       if(PBIHASH.contains(cRemove)){name = PBIHASH[cRemove].name; }
1023       title = QString(tr("%1 Removal Error:")).arg(name);
1024     }
1025   }
1026   else if(ID == ProcessManager::DOWNLOAD){ 
1027     if(!sDownload){ //not stopped manually
1028       if(APPHASH.contains(cDownload)){name = APPHASH[cDownload].name; }
1029       title = QString(tr("%1 Download Error:")).arg(name);
1030     }
1031   }
1032   else if(ID == ProcessManager::OTHER){ 
1033     if(PBIHASH.contains(cOther)){name = PBIHASH[cOther].name; }
1034     title = QString(tr("%1 PBI Error:")).arg(name); 
1035   }
1036   if(!title.isEmpty() && !err.isEmpty()){
1037     qDebug() << "Process Error:" << title << err;
1038     emit Error(title,err); //send error signal
1039   }
1040   slotProcessFinished(ID); //clean up
1041}
1042
1043 // === Database Synchronization ===
1044 void PBIBackend::slotSyncToDatabase(bool localChanges){
1045   //qDebug() << "Sync Database with local changes:" << localChanges;
1046   //Locally Installed PBI Changes
1047   QStringList currInst = installedList();
1048   QStringList sysList = sysDB->installed();
1049   numInstalled = sysList.length();
1050   //All locally installed applications
1051   for(int i=0; i<sysList.length(); i++){
1052     int index = currInst.indexOf(sysList[i]);
1053     if( index == -1){ //New entry
1054       PBIHASH.insert(sysList[i],InstalledPBI()); //add a blank entry
1055       syncPBI(sysList[i],TRUE); //Now update the info
1056       //Add it to the watcher
1057       watcher->addPath(DBDir+"installed/"+sysList[i]);
1058     }else{  //Existing entry - remove it from the currInst list
1059       if(localChanges){ syncPBI(sysList[i],TRUE); } //synchronize the data with local file changes
1060       else{ updateStatus(sysList[i]); } //just update the status
1061       currInst.removeAt(index);
1062     }     
1063   }
1064   //Non-Installed applications
1065   for(int i=0; i<currInst.length(); i++){
1066     updateStatus(currInst[i]); //update status
1067     InstalledPBI::PBISTATUS stat = PBIHASH[currInst[i]].status;
1068     bool actionPending = (stat != InstalledPBI::NONE) && (stat != InstalledPBI::UPDATEAVAILABLE);
1069     if( !actionPending ){ PBIHASH.remove(currInst[i]); }
1070     else{} //do nothing here
1071   }
1072   emit LocalPBIChanges(); //Let others know that the local PBI's have been updated
1073   //Repo Changes
1074   //qDebug() << "noRepo" << noRepo << "Invalid Repo:" << sysDB->currentRepo() << sysDB->currentRepoInvalid();
1075   if( noRepo || sysDB->currentRepoInvalid() ){
1076     //Change to the first repo available
1077     qDebug() << "Try to find an alternate Repo:";
1078     QStringList repos = sysDB->availableRepos();
1079     if(repos.length() > 0){ 
1080       sysDB->setRepo(repos[0]);
1081     }
1082     syncCurrentRepo();
1083   }else if( CATHASH.isEmpty() && APPHASH.isEmpty() ){
1084     qDebug() << "Load Repo Information";
1085     //If successful, the repo data should only be loaded once
1086     syncCurrentRepo(); 
1087     //If the sync was successful, re-run the PBI sync process to update the license info
1088     if( !APPHASH.isEmpty() ){               
1089       QTimer::singleShot(10,this,SLOT(slotSyncToDatabase())); 
1090     }else{
1091       QTimer::singleShot(60000,this,SLOT(slotSyncToDatabase())); //try again in a minute 
1092     }
1093   }
1094 }
1095 
1096 void PBIBackend::syncPBI(QString pbiID, bool useDB){
1097   //This function is mainly used for initializing the PBIHASH entry
1098   //  but is also used for updating the entry if the local installation settings change (useDB=true)
1099         
1100   //useDB: pull info from the locally installed database (pbiID MUST be installed locally)
1101   //Get the PBI structure
1102   if( !PBIHASH.contains(pbiID) ){ return; }
1103   InstalledPBI pbi = PBIHASH[pbiID];
1104   //Get the associated appID
1105   QString appID = pbi.metaID;
1106   QStringList info = sysDB->installedPbiInfo(pbiID); //info[name,version,arch,date,author,web,path,icon]
1107   if(useDB && !info.isEmpty()){
1108     //Now get additional database info
1109     bool autoUp = sysDB->installedPbiAutoUpdate(pbiID);
1110     bool root = sysDB->installedPbiNeedsRoot(pbiID);
1111     bool desktop = sysDB->installedPbiHasXdgDesktop(info[6]);
1112     bool menu = sysDB->installedPbiHasXdgMenu(info[6]);
1113     //Now add this info to the PBI structure
1114     pbi.name    = info[0];
1115     pbi.version = info[1];
1116     pbi.arch    = info[2];
1117     pbi.mdate   = info[3];
1118     pbi.author  = info[4];
1119     pbi.website = info[5];
1120     if(pbi.website.endsWith("/")){ pbi.website.chop(1); }
1121     pbi.path    = info[6];
1122     pbi.icon    = info[7];
1123     if(appID.isEmpty()){ 
1124       appID = Extras::nameToID(pbi.name); 
1125       pbi.metaID = appID;
1126     } //for new item initialization
1127     if(APPHASH.contains(appID)){
1128       pbi.license = APPHASH[appID].license;       
1129     }else{
1130       pbi.license = tr("Unknown");
1131     }
1132     pbi.rootInstall = root;
1133     pbi.autoUpdate  = autoUp;
1134     pbi.desktopIcons= desktop;
1135     pbi.menuIcons   = menu;
1136     
1137   }else{
1138     //Pull basic info from the pre-loaded App database instead
1139     // This is for application entries still in a pending state and not fully installed
1140     if(!APPHASH.contains(appID)){ return; }
1141     pbi.name = APPHASH[appID].name;
1142     //Do not change the version or arch - this is usually why the app is in a pending state
1143     pbi.author = APPHASH[appID].author;
1144     pbi.website = APPHASH[appID].website;
1145     pbi.icon = APPHASH[appID].localIcon;
1146     pbi.license = APPHASH[appID].license;
1147     pbi.rootInstall = APPHASH[appID].requiresroot;
1148     pbi.autoUpdate=FALSE;
1149     pbi.desktopIcons=FALSE;
1150     pbi.menuIcons=FALSE;
1151     if(pbi.metaID.isEmpty()){ pbi.metaID = appID; }
1152   }
1153   //Now add this pbi structure back into the hash
1154   PBIHASH.insert(pbiID, pbi); 
1155   //Now update the status
1156   updateStatus(pbiID);
1157 }
1158 
1159 void PBIBackend::slotUpdateAllStatus(){
1160   QStringList pbiID = PBIHASH.keys();
1161   for(int i=0; i<pbiID.length(); i++){
1162     updateStatus(pbiID[i]);     
1163   }
1164 }
1165 
1166 void PBIBackend::updateStatus(QString pbiID){
1167   if(!PBIHASH.contains(pbiID)){ return; }
1168   QString upgrade = upgradeAvailable(pbiID);
1169   QString chk = pbiID+":::"; //for list checking
1170   QString iIndex = PENDINGINSTALL.filter(chk).join(" "); //special case for install list
1171   if(cDownload == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::DOWNLOADING);}
1172   else if(cInstall == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::INSTALLING);}
1173   else if(cRemove == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::REMOVING);}
1174   else if(cUpdate == pbiID){PBIHASH[pbiID].setStatus(InstalledPBI::UPDATING);}
1175   //Look through the pending lists
1176   else if(PENDINGDL.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGDOWNLOAD);}
1177   else if(!iIndex.isEmpty()){ //install queue can also have special-case removals
1178     if(iIndex.contains("pbi_delete")){ PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGREMOVAL); }
1179     else{ PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGINSTALL); }
1180   }else if(PENDINGREMOVAL.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGREMOVAL);}
1181   else if(PENDINGUPDATE.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::PENDINGUPDATE);}
1182   //else if(PENDINGOTHER.join(" ").contains(chk)){PBIHASH[pbiID].setStatus(InstalledPBI::WORKING);}
1183   else if( !upgrade.isEmpty() ){PBIHASH[pbiID].setStatus(InstalledPBI::UPDATEAVAILABLE, upgrade); }
1184   else{ PBIHASH[pbiID].setStatus(InstalledPBI::NONE); }
1185 }
1186 
1187 void PBIBackend::syncCurrentRepo(){
1188  //Calling this function will automatically clear and re-populate the APPHASH and CATHASH fields
1189  APPHASH.clear(); CATHASH.clear();
1190  QString mFile = sysDB->metaFilePath(); 
1191  QString iFile = sysDB->indexFilePath();
1192  if( QFile::exists(mFile) && QFile::exists(iFile) ){
1193   //First do the meta data (app/cat info)
1194   QStringList metaFile = Extras::readFile(mFile);
1195   QStringList catsUsed, catsAvail;
1196   //qDebug() << "Sync Meta File Info";
1197   for(int i=0; i<metaFile.length(); i++){
1198     if(metaFile[i].startsWith("App=")){
1199       QStringList info = sysDB->parseAppMetaLine(metaFile[i].section("=",1,50,QString::SectionSkipEmpty));
1200       //qDebug() << "App Meta data:" << info;
1201       if(!info.isEmpty()){ //Make sure the info list is valid
1202         //info[name,category,remoteIcon,author,website,license,apptype,tags,description,requiresroot] (5/1/2013)
1203         //Simplify a couple pieces of info
1204         QString metaID = Extras::nameToID(info[0]);
1205         QString localIcon = sysDB->remoteToLocalIcon(info[0],info[2]);
1206         //Create the container and fill it
1207         MetaPBI app;
1208         app.name=info[0]; app.category=info[1]; app.remoteIcon=info[2];
1209         app.localIcon=localIcon; app.author=info[3]; app.website=info[4];
1210         app.license=info[5]; app.appType=info[6]; app.tags=info[7].toLower().split(","); 
1211         app.description=info[8];
1212         if(info[9]=="true"){ app.requiresroot=TRUE; }
1213         else{ app.requiresroot=FALSE; }
1214         //Fix the website if needed
1215         if(app.website.endsWith("/")){ app.website.chop(1); }
1216         //Add it to the hash
1217         APPHASH.insert(metaID,app);
1218       }
1219     }else if(metaFile[i].startsWith("Cat=")){
1220       QStringList info = sysDB->parseCatMetaLine(metaFile[i].section("=",1,50,QString::SectionSkipEmpty));
1221       //qDebug() << "Cat Meta Data:" << info;
1222       if(info.length() > 2){ //Make sure the info list is complete
1223         //info[name,remoteicon,description,?] (5/1/2013)
1224         QString catID = Extras::nameToID(info[0]);
1225         QString localIcon = sysDB->remoteToLocalIcon(info[0],info[1]);
1226         //Create the container and fill it
1227         MetaCategory cat;
1228         cat.name=info[0]; cat.remoteIcon=info[1]; cat.localIcon=localIcon; cat.description=info[2];
1229         //Add it to the available list
1230         catsAvail << catID;
1231         //Add it to the hash
1232         CATHASH.insert(catID,cat);
1233       }
1234     }
1235     //qDebug() << " - done with meta line";
1236   }
1237   //qDebug() << "Sync Index File Info";
1238   //Then do the list of available PBI's
1239   QStringList indexFile = Extras::readFile(iFile);
1240   bool sys64 = (sysArch=="amd64");
1241   for(int i=0; i<indexFile.length(); i++){
1242     QStringList info = sysDB->parseIndexLine(indexFile[i]);
1243       //info[name, arch, version, datetime, size, isLatest(true/false)]
1244     if(!info.isEmpty()){
1245       QString metaID = Extras::nameToID(info[0]);
1246       if(APPHASH.contains(metaID)){
1247         bool islatest = (info[5]=="true");
1248         bool is64 = (info[1] == "amd64");
1249         bool save=FALSE;
1250         //Determine whether to save the data or not
1251         if( !sys64 && is64 ){}   // do not save 64-bit apps on a 32-bit system
1252         else if(info[1] == sysArch){ save=TRUE; }  // high priority for identical architecture
1253         else{ //lower priority for 32-bit App on 64-bit system
1254           if(islatest){ 
1255             if(APPHASH[metaID].latestDatetime.isEmpty()){ save=TRUE; } //nothing saved yet, go ahead
1256             else if(APPHASH[metaID].latestArch==sysArch){} // do not save over an app that is the proper arch
1257             else if(Extras::newerDateTime(info[3], APPHASH[metaID].latestDatetime) ){ save=TRUE; } //save over older app
1258           }else{
1259             if(APPHASH[metaID].backupDatetime.isEmpty()){ save=TRUE; } //nothing saved yet, go ahead
1260             else if(APPHASH[metaID].backupArch==sysArch){} // do not save over an app that is the proper arch
1261             else if(Extras::newerDateTime(info[3], APPHASH[metaID].backupDatetime) ){ save=TRUE; } //save over older app
1262           }
1263         }
1264         //Save the data if appropriate
1265         if(save && islatest){
1266           APPHASH[metaID].latestVersion=info[2];
1267           APPHASH[metaID].latestDatetime=info[3];
1268           APPHASH[metaID].latestArch=info[1]; 
1269           APPHASH[metaID].latestSizeK=info[4];
1270           APPHASH[metaID].latestFilename = info[6];
1271         }else if(save){
1272           APPHASH[metaID].backupVersion=info[2];
1273           APPHASH[metaID].backupDatetime=info[3];
1274           APPHASH[metaID].backupArch=info[1]; 
1275           APPHASH[metaID].backupSizeK=info[4];
1276           APPHASH[metaID].backupFilename=info[6];
1277         } 
1278         //if(save){ qDebug() << "APP:" << metaID << info[1] << info[2] << info[3] << info[4] << info[5]; }
1279       } //end check for ID in hash
1280     } //end check for info available
1281   } //end loop over index file
1282   
1283   //Now clean up the APPHASH to remove any entries that do not have a PBI associated with them
1284   QStringList apps = APPHASH.keys();
1285   for(int i=0; i<apps.length(); i++){
1286     if(APPHASH[apps[i]].latestVersion.isEmpty()){
1287       //Make sure there is not a backup version we can use instead
1288       if(APPHASH[apps[i]].backupVersion.isEmpty()){
1289         APPHASH.remove(apps[i]);           
1290       }else{
1291         //Move over the backup to the latest
1292         APPHASH[apps[i]].latestVersion=  APPHASH[apps[i]].backupVersion;
1293         APPHASH[apps[i]].latestDatetime= APPHASH[apps[i]].backupDatetime;
1294         APPHASH[apps[i]].latestArch=     APPHASH[apps[i]].backupArch; 
1295         APPHASH[apps[i]].latestSizeK=    APPHASH[apps[i]].backupSizeK;
1296         APPHASH[apps[i]].latestFilename= APPHASH[apps[i]].backupFilename;
1297         //Now clear the backup
1298         APPHASH[apps[i]].backupVersion.clear();
1299         APPHASH[apps[i]].backupDatetime.clear();
1300         APPHASH[apps[i]].backupArch.clear(); 
1301         APPHASH[apps[i]].backupSizeK.clear();
1302         APPHASH[apps[i]].backupFilename.clear();
1303       }
1304     }else{
1305       //Category being used, remove this category from the list
1306       int catID = catsAvail.indexOf( Extras::nameToID(APPHASH[apps[i]].category) );
1307       if(catID != -1){ catsAvail.removeAt(catID); }
1308     }
1309   }
1310   //Now remove any empty categories (ones left over in catAvail list)
1311   for(int i=0; i<catsAvail.length(); i++){
1312     if( CATHASH.contains(catsAvail[i]) ){
1313       //qDebug() << " - Empty category:" << catsAvail[i];
1314       CATHASH.remove(catsAvail[i]); 
1315     }
1316   }
1317  } // end check for both files existing
1318 
1319  if(APPHASH.isEmpty() && CATHASH.isEmpty()){
1320    numAvailable = -1;
1321    emit NoRepoAvailable();       
1322  }else{
1323    numAvailable = QStringList(APPHASH.keys()).length(); //update the number of apps available
1324    emit RepositoryInfoReady();
1325  }
1326 }
1327 
1328void PBIBackend::startFileSystemWatcher(){
1329   QStringList watched = watcher->directories();
1330   if(!watched.isEmpty()){ watcher->removePaths(watched); }//clear the watcher first
1331   watcher->addPath( DBDir+"installed" ); //look for installed app changes
1332   watcher->addPath( DBDir+"repos" ); //look for repo changes
1333   watcher->addPath( DBDir+"index" ); //look for index/meta file changes         
1334}
1335
1336void PBIBackend::stopFileSystemWatcher(){
1337   QStringList watched = watcher->directories();
1338   if(!watched.isEmpty()){ watcher->removePaths(watched); }//clear the watcher first     
1339}
Note: See TracBrowser for help on using the repository browser.