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

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

Update the AppCafe? Installed tab to take advantage of the new info available for 10.x PBI's.
Changes Include:
1) Move currently installed app info to a seperate page, that can be opened by either double-clicking on an installed application or clicking the "details" button at the bottom of the main page.
2) Update the "actions" button to be actively enabled/disabled depending on whether any items are currently checked.
3) Add ability to start composing an email (in the DE's default email client) to the port maintainer for an installed PBI. This email template also includes all the important info regarding the PBI in question (build date, architecture, FreeBSD version, version number).
4) Fix a bug with setting/unsetting the "auto-update" status for a PBI.
5) Clean up application descriptions a bit better now (affects both installed and browser tabs).

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