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

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

Make sure that when an upgrade fails in the AppCafe?, it defaults to trying to download the latest PBI manually before throwing an error. This catches the situations where the user is actually "upgrading" to an older version of an application because of an un-approval of the latest PBI for some reason.

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