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

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

Setup the AppCafe? to be able to start any xdg-menu entry that an installed PBI has. The icon in the lower section for a selected appliction is now a toolbutton with a popup menu of all the available ways to start the application.

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