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

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

Prevent duplicate error messages if a download failes in the AppCafe?.

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