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

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

Make sure that the AppCafe? browser "app" page gets the download button updated if there is a status change for the associated installed version of that app.

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