source: src-qt4/pc-pkgmanager/mainWin.cpp @ aac79d5c

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

Greatly speed up the pkg-pkgmanager at startup, don't need to build a
full reverse dependancy list, we can generate it on the fly when
removing packages.

  • Property mode set to 100644
File size: 37.4 KB
Line 
1/****************************************************************************
2** ui.h extension file, included from the uic-generated form implementation.
3**
4** If you want to add, delete, or rename functions or slots, use
5** Qt Designer to update this file, preserving your code.
6**
7** You should not define a constructor or destructor in this file.
8** Instead, write your code in functions called init() and destroy().
9** These will automatically be called by the form's constructor and
10** destructor.
11*****************************************************************************/
12#include <fcntl.h>
13#include <QDateTime>
14#include <QDebug>
15#include <QDir>
16#include <QProcess>
17#include <QProgressDialog>
18#include <QSocketNotifier>
19#include <QString>
20#include <QTextStream>
21#include <pcbsd-utils.h>
22#include <pcbsd-ui.h>
23#include <QSettings>
24#include "mainWin.h"
25#include "../config.h"
26
27void mainWin::ProgramInit(QString ch)
28{
29  // Set any warden directories
30  lastError="";
31  wDir = ch;
32
33  //Grab the username
34  //username = QString::fromLocal8Bit(getenv("LOGNAME"));
35  connect(pushUpdatePkgs, SIGNAL(clicked()), this, SLOT(slotUpdatePkgsClicked()));
36  connect(pushClose, SIGNAL(clicked()), this, SLOT(slotCloseClicked()));
37  connect(buttonRescanPkgs, SIGNAL(clicked()), this, SLOT(slotRescanPkgsClicked()));
38  connect(pushPkgApply, SIGNAL( clicked() ), this, SLOT( slotApplyClicked() ) );
39  connect(action_Quit, SIGNAL( triggered(bool) ), this, SLOT( slotCloseClicked() ) );
40  connect(action_Basic, SIGNAL( triggered(bool) ), this, SLOT( slotViewChanged() ) );
41  connect(action_Advanced, SIGNAL( triggered(bool) ), this, SLOT( slotViewChanged() ) );
42
43  // Setup the action group
44  viewGroup = new QActionGroup(this);
45  viewGroup->addAction(action_Basic);
46  viewGroup->addAction(action_Advanced);
47
48  treeMetaPkgs->setContextMenuPolicy(Qt::CustomContextMenu);
49  connect(treeMetaPkgs, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(slotMetaRightClick()) );
50
51  QSettings settings("PC-BSD", "PackageManager");
52  QString curMode = settings.value("view/mode").toString();
53  if ( curMode == "Advanced" )
54  {
55     stackedPkgView->setCurrentIndex(1);
56     action_Basic->setChecked(false);
57     action_Advanced->setChecked(true);
58  }
59
60  // If we are running on a chroot, only do advanced mode
61  if ( !wDir.isEmpty() )
62  {
63     stackedPkgView->setCurrentIndex(1);
64     action_Advanced->setChecked(true);
65     menu_View->setEnabled(false);
66     menu_View->setVisible(false);
67  }
68
69  initMetaWidget();
70}
71
72void mainWin::slotViewChanged()
73{
74  QString mode;
75  if ( action_Basic->isChecked() ) {
76    stackedPkgView->setCurrentIndex(0);
77    mode="Basic";
78  } else {
79    mode="Advanced";
80    stackedPkgView->setCurrentIndex(1);
81  }
82
83  // Save the mode as the default at next open
84  QSettings settings("PC-BSD", "PackageManager");
85  settings.setValue("view/mode", mode);
86
87  // Changed view, lets refresh
88  initMetaWidget();
89}
90
91void mainWin::slotRescanPkgsClicked()
92{
93  // Check for pkg updates
94  checkMPKGUpdates();
95}
96
97void mainWin::slotApplyClicked() {
98  // Running in basic mode
99  if ( stackedPkgView->currentIndex() == 0 )
100  {
101     saveMetaPkgs();   
102  } else {
103     // Running in advanced mode
104     applyNGChanges();
105  }
106
107}
108
109void mainWin::checkMPKGUpdates() {
110
111  QString line, tmp, name, pkgname, pkgover, pkgnver;
112  QStringList up, listPkgs;
113  bool haveUpdates = false;
114  int totPkgs=0;
115  buttonRescanPkgs->setEnabled(false);
116  pushUpdatePkgs->setEnabled(false);
117  listViewUpdatesPkgs->clear();
118  groupUpdatesPkgs->setTitle(tr("Checking for updates"));
119
120  QProcess p;
121  if ( wDir.isEmpty() )
122     p.start(QString("pc-updatemanager"), QStringList() << "pkgcheck");
123  else
124     p.start(QString("chroot"), QStringList() << wDir << "pc-updatemanager" << "pkgcheck");
125  while(p.state() == QProcess::Starting || p.state() == QProcess::Running)
126     QCoreApplication::processEvents();
127
128  while (p.canReadLine()) {
129    line = p.readLine().simplified();
130    qDebug() << line;
131    if ( line.indexOf("Upgrading") != 0) {
132       continue;
133    }
134    tmp = line;
135    pkgname = tmp.section(" ", 1, 1);
136    pkgname.replace(":", "");
137    pkgover = tmp.section(" ", 2, 2);
138    pkgnver = tmp.section(" ", 4, 4);
139    QTreeWidgetItem *myItem = new QTreeWidgetItem(QStringList() << pkgname << pkgover << pkgnver);
140    listViewUpdatesPkgs->addTopLevelItem(myItem);
141    haveUpdates = true;
142    totPkgs++;
143  }
144
145  buttonRescanPkgs->setEnabled(true);
146  pushUpdatePkgs->setEnabled(haveUpdates);
147  if ( totPkgs > 0 ) {
148    tabUpdates->setTabText(1, tr("Package Updates (%1)").arg(totPkgs));
149    groupUpdatesPkgs->setTitle(tr("Available updates"));
150  } else {
151    tabUpdates->setTabText(1, tr("Package Updates"));
152    groupUpdatesPkgs->setTitle(tr("No available updates"));
153  }
154 
155}
156
157void mainWin::slotSingleInstance() {
158   this->hide();
159   this->showNormal();
160   this->activateWindow();
161   this->raise();
162}
163
164void mainWin::slotCloseClicked() {
165   close();
166}
167
168void mainWin::slotUpdatePkgsClicked() {
169  dPackages = false;
170  uPackages = false;
171
172  // Init the pkg process
173  prepPkgProcess();
174
175  // Create our runlist of package commands
176  QStringList pCmds;
177
178  if ( wDir.isEmpty() )
179    pCmds << "pc-updatemanager" << "pkgupdate";
180  else
181    pCmds << "chroot" << wDir << "pc-updatemanager" << "pkgupdate";
182
183  // Setup our runList
184  pkgCmdList << pCmds;
185
186  // Start the updating now
187  startPkgProcess();
188
189  textStatus->setText(tr("Starting package updates..."));
190
191}
192
193QString mainWin::getConflictDetailText() {
194
195  QStringList ConList = ConflictList.split(" ");
196  QStringList tmpDeps;
197  QString retText;
198
199  for (int i = 0; i < ConList.size(); ++i) {
200    QProcess p;
201    tmpDeps.clear();
202
203    if ( wDir.isEmpty() )
204      p.start("pkg", QStringList() << "rquery" << "%rn-%rv" << ConList.at(i));
205    else
206      p.start("chroot", QStringList() << wDir << "pkg" "rquery" << "%rn-%rv" << ConList.at(i) );
207
208    if(p.waitForFinished()) {
209      while (p.canReadLine()) {
210        tmpDeps << p.readLine().simplified();
211      }
212    }
213    retText+= ConList.at(i) + " " + tr("required by:") + "\n" + tmpDeps.join(" ");
214  }
215
216  return retText;
217}
218
219void mainWin::prepPkgProcess() {
220  pkgCmdList.clear();
221  textDisplayOut->clear();
222  pkgHasFailed=false;
223}
224
225void mainWin::startPkgProcess() {
226
227  if ( pkgCmdList.isEmpty() )
228    return;
229  if ( pkgCmdList.at(0).at(0).isEmpty() )
230     return; 
231
232  // Get the command name
233  QString cmd;
234  cmd = pkgCmdList.at(0).at(0);
235
236  // Get any optional flags
237  QStringList flags;
238  for (int i = 0; i < pkgCmdList.at(0).size(); ++i) {
239     if ( i == 0 )
240       continue;
241     flags << pkgCmdList.at(0).at(i);
242  } 
243
244  qDebug() << cmd + " " + flags.join(" ");
245 
246  // Setup the first process
247  uProc = new QProcess();
248  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
249  env.insert("PCFETCHGUI", "YES");
250  uProc->setProcessEnvironment(env);
251  uProc->setProcessChannelMode(QProcess::MergedChannels);
252
253  // Connect the slots
254  connect( uProc, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReadPkgOutput()) );
255  connect( uProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotPkgDone()) );
256
257  uProc->start(cmd, flags);
258
259  stackedTop->setCurrentIndex(1);
260
261  progressUpdate->setRange(0, 0 );
262  progressUpdate->setValue(0);
263
264}
265
266void mainWin::slotReadPkgOutput() {
267   QString line, tmp, cur, tot, fname;
268   int curItem, totItem;
269   bool ok;
270
271   while (uProc->canReadLine()) {
272     line = uProc->readLine().simplified();
273     qDebug() << line;
274
275     tmp = line;
276     tmp.truncate(50);
277
278     // Flags we can parse out and not show the user
279
280     // Check if we have crashed into a conflict and ask the user what to do
281     if ( line.indexOf("PKGCONFLICTS: ") == 0 ) {
282        tmp = line; 
283        tmp.replace("PKGCONFLICTS: ", "");
284        ConflictList = tmp;
285        continue;
286     }
287     if ( line.indexOf("PKGREPLY: ") == 0 ) {
288        QString ans;
289        tmp = line; 
290        tmp.replace("PKGREPLY: ", "");
291        QMessageBox msgBox;
292        msgBox.setText(tr("The following packages are causing conflicts with the selected changes and can be automatically removed. Continue?") + "\n" + ConflictList);
293        msgBox.setStandardButtons(QMessageBox::Yes|QMessageBox::No);
294        msgBox.setDetailedText(getConflictDetailText());
295        msgBox.setDefaultButton(QMessageBox::No);
296        if ( msgBox.exec() == QMessageBox::Yes) {
297          // We will try to fix conflicts
298          ans="yes";
299        } else {
300          // We will fail :(
301          QMessageBox::warning(this, tr("Package Conflicts"),
302          tr("You may need to manually fix the conflicts before trying again."),
303          QMessageBox::Ok,
304          QMessageBox::Ok);
305          ans="no";
306        }
307
308        QFile pkgTrig( tmp );
309        if ( pkgTrig.open( QIODevice::WriteOnly ) ) {
310           QTextStream streamTrig( &pkgTrig );
311           streamTrig << ans;
312           pkgTrig.close();
313        }
314        continue;
315     }
316
317     if ( line.indexOf("FETCH: ") == 0 ) { 
318        progressUpdate->setValue(progressUpdate->value() + 1); 
319        tmp = line; 
320        tmp = tmp.remove(0, tmp.lastIndexOf("/") + 1); 
321        progressUpdate->setRange(0, 0);
322        progressUpdate->setValue(0);
323        curFileText = tr("Downloading: %1").arg(tmp); 
324        textStatus->setText(tr("Downloading: %1").arg(tmp)); 
325        continue;
326     } 
327     if ( line.indexOf("FETCHDONE") == 0 )
328        continue;
329
330     if ( line.indexOf("SIZE: ") == 0 ) {
331          bool ok, ok2;
332          line.replace("SIZE: ", "");
333          line.replace("DOWNLOADED: ", "");
334          line.replace("SPEED: ", "");
335          line.section(" ", 0, 0).toInt(&ok);
336          line.section(" ", 1, 1).toInt(&ok2);
337   
338          if ( ok && ok2 ) {
339            QString unit;
340            int tot = line.section(" ", 0, 0).toInt(&ok);
341            int cur = line.section(" ", 1, 1).toInt(&ok2);
342            QString percent = QString::number( (float)(cur * 100)/tot );
343            QString speed = line.section(" ", 2, 3);
344
345            // Get the MB downloaded / total
346            if ( tot > 2048 ) {
347              unit="MB";
348              tot = tot / 1024;
349              cur = cur / 1024;
350            } else {
351              unit="KB";
352            }
353
354            QString ProgressString=QString("(%1" + unit + " of %2" + unit + " at %3)").arg(cur).arg(tot).arg(speed);
355            progressUpdate->setRange(0, tot);
356            progressUpdate->setValue(cur);
357            textStatus->setText(curFileText + " " + ProgressString); 
358         }
359         continue;
360     }
361
362
363     // Now show output on GUI
364     textDisplayOut->insertPlainText(line + "\n");
365     textDisplayOut->moveCursor(QTextCursor::End);
366
367
368     // Any other flags to look for?
369     /////////////////////////////////////////////////////
370     if ( line.indexOf("to be downloaded") != -1 ) {
371       textStatus->setText(tr("Downloading packages..."));
372       curUpdate = 0;
373       progressUpdate->setValue(0);
374       continue;
375     }
376     if ( line.indexOf("Checking integrity") == 0 ) {
377       textStatus->setText(line);
378       uPackages = true;
379       dPackages = false;
380       curUpdate = 0;
381       progressUpdate->setValue(0);
382       progressUpdate->setRange(0, 0);
383       progressUpdate->setValue(0);
384       continue;
385     }
386     
387     if ( uPackages ) {
388       if ( line.indexOf("[") == 0 ) {
389         tmp=line.section("]", 1, 1);
390         textStatus->setText(tmp);
391         tmp=line.section("/", 0, 0).replace("[", "");
392         tmp.toInt(&ok);
393         if (ok)  {
394           curItem=tmp.toInt(&ok);
395           tmp=line.section("/", 1, 1).section("]", 0, 0);
396           tmp.toInt(&ok);
397           if (ok)  {
398             totItem=tmp.toInt(&ok);
399             progressUpdate->setRange(0, totItem);
400             progressUpdate->setValue(curItem);
401           }
402
403         }
404       }
405       continue;
406     }
407
408   } // end of while
409}
410
411void mainWin::slotPkgDone() {
412
413  if ( uProc->exitCode() != 0 )
414    pkgHasFailed=true;
415
416  // Run the next command on the stack if necessary
417  if (  pkgCmdList.size() > 1 ) {
418        pkgCmdList.removeAt(0); 
419        startPkgProcess();     
420        return;
421  }
422
423  // Nothing left to run! Lets wrap up
424  QFile sysTrig( SYSTRIGGER );
425  if ( sysTrig.open( QIODevice::WriteOnly ) ) {
426    QTextStream streamTrig( &sysTrig );
427     streamTrig << "INSTALLFINISHED: ";
428  }
429
430  if ( pkgHasFailed ) {
431    QFile file( "/tmp/pkg-output.log" );
432    if ( file.open( QIODevice::WriteOnly ) ) {
433       QTextStream stream( &file );
434       stream << textDisplayOut->toPlainText();
435       file.close();
436    }
437    QMessageBox::warning(this, tr("Failed!"), tr("The package commands failed. A copy of the output was saved to /tmp/pkg-output.log"));
438  } else
439    QMessageBox::warning(this, tr("Finished!"), tr("Package changes complete!" ));
440
441  // Clear out the old commands
442  pkgCmdList.clear();
443
444  // Switch back to our main display
445  stackedTop->setCurrentIndex(0);
446 
447  // Re-init the meta-widget
448  initMetaWidget();
449
450}
451
452/*****************************************
453Code for package stuff
454******************************************/
455
456void mainWin::initMetaWidget()
457{
458  qDebug() << "Starting metaWidget...";
459  groupInfo->setVisible(false);
460
461  // Running in basic mode
462  if ( stackedPkgView->currentIndex() == 0 )
463  {
464    populateMetaPkgs();
465    // Connect our slots
466  } else {
467    // Running in advanced mode
468    populateNGPkgs();
469  }
470}
471
472void mainWin::populateNGPkgs()
473{
474  pushPkgApply->setEnabled(false);
475  treeNGPkgs->clear();
476  tmpPkgList.clear();
477  new QTreeWidgetItem(treeNGPkgs, QStringList() << tr("Loading... Please wait...") );
478
479  if ( ! pkgList.isEmpty() ) {
480        disconnect(treeNGPkgs, SIGNAL(itemChanged(QTreeWidgetItem *, int)), 0, 0);
481        disconnect(treeNGPkgs, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), 0, 0);
482  }
483  pkgList.clear();
484  selPkgList.clear();
485
486  // Start the process to get meta-pkg info
487  getNGProc = new QProcess();
488  qDebug() << "Searching for pkgs...";
489  connect( getNGProc, SIGNAL(readyReadStandardOutput()), this, SLOT(slotGetNGPackageDataOutput()) );
490  connect( getNGProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotGetNGInstalledPkgs()) );
491  getNGProc->setProcessChannelMode(QProcess::MergedChannels);
492  if ( wDir.isEmpty() )
493    getNGProc->start(QString("pkg"), QStringList() << "rquery" << "-a" << "%o:::%n-%v:::%c:::%sh:::%m:::%w");
494  else
495    getNGProc->start(QString("chroot"), QStringList() << wDir << "pkg" << "rquery" << "-a" << "%o:::%n-%v:::%c:::%sh:::%m:::%w");
496
497}
498
499void mainWin::slotGetNGPackageDataOutput()
500{
501   while (getNGProc->canReadLine())
502     tmpPkgList << getNGProc->readLine().simplified();
503}
504
505void mainWin::slotGetNGInstalledDataOutput()
506{
507   while (getNGProc->canReadLine())
508     pkgList << getNGProc->readLine().simplified();
509}
510
511void mainWin::slotGetNGInstalledPkgs() {
512
513  qDebug() << "Building dependancy lists...";
514  QProcess p;
515  pkgDepList.clear();
516  if ( wDir.isEmpty() )
517    p.start("pkg", QStringList() << "rquery" << "-a" << "%n-%v:::%dn-%dv");
518  else
519    p.start("chroot", QStringList() << wDir << "pkg" << "rquery" << "-a" << "%n-%v:::%dn-%dv" );
520  while(p.state() == QProcess::Starting || p.state() == QProcess::Running) {
521      p.waitForFinished(200);
522      QCoreApplication::processEvents();
523  }
524  while (p.canReadLine()) {
525    pkgDepList << p.readLine().simplified();
526  }
527
528  getNGProc = new QProcess();
529  qDebug() << "Searching for installed pkgs...";
530  connect( getNGProc, SIGNAL(readyReadStandardOutput()), this, SLOT(slotGetNGInstalledDataOutput()) );
531  connect( getNGProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotFinishLoadingNGPkgs()) );
532  getNGProc->setProcessChannelMode(QProcess::MergedChannels);
533  if ( wDir.isEmpty() )
534    getNGProc->start(QString("pkg"), QStringList() << "info" << "-aq" );
535  else
536    getNGProc->start(QString("chroot"), QStringList() << wDir << "pkg" << "info" << "-aq");
537
538}
539
540void mainWin::slotFinishLoadingNGPkgs()
541{
542  treeNGPkgs->clear();
543
544  addNGItems();
545
546  pushPkgApply->setEnabled(false);
547
548  connect(treeNGPkgs, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(slotEnableApply()));
549  connect(treeNGPkgs, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(slotNGItemChanged()));
550
551  // Now we can look for updates safely
552  slotRescanPkgsClicked();
553}
554
555void mainWin::slotNGItemChanged()
556{
557  if ( ! treeNGPkgs->currentItem() ) {
558     groupInfo->setVisible(false);
559     return;
560  }
561  QString desc, size, maint, weburl;
562
563  QTreeWidgetItem *cItem = treeNGPkgs->currentItem();
564  QString pName = cItem->text(0).section("(", 1, 1).section(")", 0, 0);
565  if ( pName.isEmpty() ) {
566     groupInfo->setVisible(false);
567     return;
568  }
569  qDebug() << "Checking: " + pName;
570
571  QRegExp rx("*:::" + pName + ":::*");
572  rx.setPatternSyntax(QRegExp::Wildcard);
573  int pAt = tmpPkgList.indexOf(rx);
574  if (pAt == -1 ) {
575     qDebug() << "Unable to find package: " + pName;
576     groupInfo->setVisible(false);
577     return;
578  }
579
580  desc = tmpPkgList.at(pAt).section(":::", 2,2);
581  size = tmpPkgList.at(pAt).section(":::", 3,3);
582  maint = tmpPkgList.at(pAt).section(":::", 4,4);
583  weburl = tmpPkgList.at(pAt).section(":::", 5,5);
584  labelPkgNameVer->setText(pName);
585  labelSize->setText(size);
586  labelWeb->setText(weburl);
587  textDesc->setText(desc);
588  textOptions->clear();
589
590  QCoreApplication::processEvents();
591
592  // Display the depends
593  QString depTxt;
594  QRegExp rxd( pName + ":::*");
595  rxd.setPatternSyntax(QRegExp::Wildcard);
596  QStringList aDeps = pkgDepList.filter(rxd);
597  for ( int r=0; r < aDeps.size(); ++r) {
598      QString dName = aDeps.at(r).section(":::", 1, 1);
599      // Is this package installed?
600      if ( pkgList.indexOf(dName) != -1 )
601         depTxt+= dName + " (Installed)\n";
602      else
603         depTxt+= dName + "\n";
604  }
605
606  textDeps->setText(depTxt);
607
608  groupInfo->setVisible(true);
609
610  getNGInfo = new QProcess();
611  qDebug() << "Getting Info for " + pName;
612  connect( getNGInfo, SIGNAL(readyReadStandardOutput()), this, SLOT(slotNGReadInfo()) );
613  getNGInfo->setProcessChannelMode(QProcess::MergedChannels);
614  if ( wDir.isEmpty() )
615    getNGInfo->start(QString("pkg"), QStringList() << "rquery" << "%Ok=%Ov" << pName );
616  else
617    getNGInfo->start(QString("chroot"), QStringList() << wDir << "pkg" << "rquery" << "%Ok=%Ov" << pName);
618}
619
620void mainWin::slotNGReadInfo()
621{
622  while (getNGInfo->canReadLine())
623     textOptions->append(getNGInfo->readLine().simplified() );
624
625  textOptions->moveCursor(QTextCursor::Start);
626}
627
628void mainWin::slotEnableApply()
629{
630  pushPkgApply->setEnabled(true);
631}
632
633void mainWin::addNGItems()
634{
635   QString curCat, cat, name, pkgname, desc, size, maint, weburl;
636
637   // We like to add alphabetically
638   tmpPkgList.sort();
639
640   QTreeWidgetItem *catItem = new QTreeWidgetItem;
641
642   // Lets start adding packages to the tree widget
643   for (int i = 0; i < tmpPkgList.size(); ++i) {
644        name = cat = tmpPkgList.at(i).section(":::", 0,0);
645        cat=cat.section("/", 0, 0);
646        name=name.section("/", 1, 1);
647        pkgname = tmpPkgList.at(i).section(":::", 1,1);
648        desc = tmpPkgList.at(i).section(":::", 2,2);
649        size = tmpPkgList.at(i).section(":::", 3,3);
650        maint = tmpPkgList.at(i).section(":::", 4,4);
651        weburl = tmpPkgList.at(i).section(":::", 5,5);
652
653        // Check if we need to add a top-level category
654        if ( cat != curCat )
655        {
656           qDebug() << "Adding cat: " + cat;
657           catItem = new QTreeWidgetItem(QStringList() << cat);
658           treeNGPkgs->addTopLevelItem(catItem);
659           curCat = cat;
660        }
661 
662        // Now lets create the item and attach to the category
663        QTreeWidgetItem *pkgItem = new QTreeWidgetItem();
664        pkgItem->setText(0, name + " (" + pkgname + ") - " + size );
665        pkgItem->setToolTip(0, desc);
666
667        if ( pkgList.indexOf(pkgname) != -1 ) {
668          pkgItem->setCheckState(0, Qt::Checked);
669          selPkgList << pkgname;
670        } else
671          pkgItem->setCheckState(0, Qt::Unchecked);
672 
673        catItem->addChild(pkgItem);
674   }
675
676}
677
678// Lets prompt user, and do it!
679void mainWin::applyNGChanges()
680{
681   QString tmp;
682   QStringList curPkgChecked;
683   QStringList newPkgs;
684   QStringList rmPkgs;
685
686   QTreeWidgetItemIterator it(treeNGPkgs);
687   while (*it) {
688         if ((*it)->checkState(0) == Qt::Checked) {
689           tmp = (*it)->text(0).section("(", 1, 1).section(")", 0, 0);
690           curPkgChecked << tmp;
691           if (pkgList.indexOf(tmp) == -1 ) 
692              newPkgs << tmp;
693         }
694         ++it;
695   }
696
697   for ( int i=0; i < pkgList.size(); ++i)
698      // Has this package been unchecked?
699      if (curPkgChecked.indexOf(pkgList.at(i)) == -1 )  {
700         // Make sure this is a package in the repo
701         // This filters out any custom packages the user may have loaded which may not exist in our repo
702         QRegExp rx("*" + pkgList.at(i) + "*");
703         rx.setPatternSyntax(QRegExp::Wildcard);
704         if ( tmpPkgList.indexOf(rx) != -1 )
705           rmPkgs << pkgList.at(i);
706      }
707
708   if ( rmPkgs.isEmpty() && newPkgs.isEmpty() ) {
709      QMessageBox::warning(this, tr("No changes"),
710        tr("No changes to make!"),
711        QMessageBox::Ok,
712        QMessageBox::Ok);
713      return;
714   }
715
716   qDebug() << "Added packages" << newPkgs;
717   qDebug() << "Removed packages" << rmPkgs;
718   pkgRemoveList = rmPkgs;
719   pkgAddList = newPkgs;
720
721   QString confirmText;
722
723   // Lets start creating our confirmation text
724   if ( ! rmPkgs.isEmpty() ) {
725      confirmText+=tr("The following packages will be removed:") + "\n"; 
726      confirmText+= "------------------------------------------\n";
727      confirmText+=rmPkgs.join("\n"); 
728      confirmText+= "\n\n" + tr("The following packages that require the above packages will also removed:") + "\n"; 
729      confirmText+= "------------------------------------------\n";
730      for ( int i=0; i < rmPkgs.size(); ++i) {
731       
732         // Get rdeps for this pkg
733         qDebug() << "Building reverse dependancy lists...";
734         pkgRDepList.clear();
735         QProcess p;
736         if ( wDir.isEmpty() )
737           p.start("pkg", QStringList() << "rquery" << "%n-%v:::%rn-%rv" << rmPkgs.at(i));
738         else
739           p.start("chroot", QStringList() << wDir << "pkg" << "rquery" << "%n-%v:::%rn-%rv" << rmPkgs.at(i) );
740         while(p.state() == QProcess::Starting || p.state() == QProcess::Running) {
741           p.waitForFinished(200);
742           QCoreApplication::processEvents();
743         }
744         while (p.canReadLine()) {
745           pkgRDepList << p.readLine().simplified();
746         }
747
748         QRegExp rx(rmPkgs.at(i) + ":::*");
749         rx.setPatternSyntax(QRegExp::Wildcard);
750         QStringList rDeps = pkgRDepList.filter(rx);
751         for ( int r=0; r < rDeps.size(); ++r) {
752             QString pName = rDeps.at(r).section(":::", 1, 1); 
753             // Is this package installed?
754             if ( pkgList.indexOf(pName) != -1 )
755               confirmText+= pName + " ";
756         }
757      }
758   }
759
760   if ( ! newPkgs.isEmpty() ) {
761      if ( ! rmPkgs.isEmpty() )
762        confirmText+= "\n\n";
763      confirmText+=tr("The following packages will be installed:") + "\n"; 
764      confirmText+= "------------------------------------------\n";
765      confirmText+=newPkgs.join("\n"); 
766      confirmText+= "\n\n" + tr("The following dependances will also be installed:") + "\n"; 
767      confirmText+= "------------------------------------------\n";
768      for ( int i=0; i < newPkgs.size(); ++i) {
769         QRegExp rx(newPkgs.at(i) + ":::*");
770         rx.setPatternSyntax(QRegExp::Wildcard);
771         QStringList aDeps = pkgDepList.filter(rx);
772         for ( int r=0; r < aDeps.size(); ++r) {
773             QString pName = aDeps.at(r).section(":::", 1, 1); 
774             // Is this package installed?
775             if ( pkgList.indexOf(pName) == -1 )
776               confirmText+= pName + " ";
777         }
778      }
779   }
780
781   // Launch our AddPartitionDialog to add a new device
782   askUserConfirm = new dialogConfirm();
783   connect(askUserConfirm, SIGNAL(ok()),this, SLOT(slotStartNGChanges()) );
784   askUserConfirm->programInit(tr("Confirm package changes"));
785   askUserConfirm->setInfoText(QString(confirmText));
786   askUserConfirm->exec();
787
788}
789
790
791// Time to start doing our NG changes!
792void mainWin::slotStartNGChanges()
793{
794  // Init the pkg process
795  prepPkgProcess();
796
797  // Create our runlist of package commands
798  QStringList pCmds;
799 
800  if ( ! pkgRemoveList.isEmpty() ) {
801    if ( wDir.isEmpty() )
802      pCmds << "pkg" << "delete" << "-R" << "-y" << pkgRemoveList.join(" ");
803    else
804      pCmds << "chroot" << wDir << "pkg" << "delete" << "-R" << "-y" << pkgRemoveList.join(" ");
805    pkgCmdList << pCmds;
806  }
807         
808  pCmds.clear();
809
810  if ( ! pkgAddList.isEmpty() ) {
811    if ( wDir.isEmpty() )
812      pCmds << "pc-pkg" << "install" << "-y" << pkgAddList.join(" ");
813    else
814      pCmds << "chroot" << wDir << "pc-pkg" << "install" << "-y" << pkgAddList.join(" ");
815    pkgCmdList << pCmds;
816  }
817
818  // Lets kick it off now
819  startPkgProcess();
820}
821
822// Display found meta-pkg data
823void mainWin::populateMetaPkgs()
824{
825  pushPkgApply->setEnabled(false);
826  treeMetaPkgs->clear();
827  tmpMetaPkgList.clear();
828  new QTreeWidgetItem(treeMetaPkgs, QStringList() << tr("Loading... Please wait...") );
829
830  if ( ! metaPkgList.isEmpty() )
831        disconnect(treeMetaPkgs, SIGNAL(itemChanged(QTreeWidgetItem *, int)), 0, 0);
832  metaPkgList.clear();
833
834  // Start the process to get meta-pkg info
835  getMetaProc = new QProcess();
836  qDebug() << "Searching for meta-pkgs...";
837  connect( getMetaProc, SIGNAL(readyReadStandardOutput()), this, SLOT(slotGetPackageDataOutput()) );
838  connect( getMetaProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotFinishLoadingMetaPkgs()) );
839  getMetaProc->setProcessChannelMode(QProcess::MergedChannels);
840  getMetaProc->start(QString("pc-metapkgmanager"), QStringList() << "list");
841
842}
843
844// Display found meta-pkg data
845void mainWin::slotFinishLoadingMetaPkgs()
846{
847
848  // Populate the metaPkgList
849  parseTmpMetaList();
850
851  treeMetaPkgs->clear();
852
853  addTreeItems(QString()); 
854
855  pushPkgApply->setEnabled(false);
856
857  connect(treeMetaPkgs, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(slotDeskPkgsChanged(QTreeWidgetItem *, int)));
858
859  // Now we can look for updates safely
860  slotRescanPkgsClicked();
861}
862
863void mainWin::addTreeItems(QString parent)
864{
865  for (int z=0; z < metaPkgList.count(); ++z) {
866    if ( metaPkgList.at(z).at(3) != parent )
867      continue;
868
869    // Hide the "base-system" package, since we are installing it anyway
870    if (metaPkgList.at(z).at(0) == "base-system" )
871      return;
872
873    QTreeWidgetItem *deskItem = new QTreeWidgetItem(QStringList() << metaPkgList.at(z).at(0) );
874    deskItem->setIcon(0, QIcon(metaPkgList.at(z).at(2)));
875    deskItem->setToolTip(0, metaPkgList.at(z).at(1));
876    deskItem->setCheckState(0, Qt::Unchecked);
877
878    if ( metaPkgList.at(z).at(5) == "YES" )
879      deskItem->setCheckState(0, Qt::Checked);
880
881    if ( parent.isEmpty() ) {
882      treeMetaPkgs->addTopLevelItem(deskItem);
883    } else {
884      // Locate the parent to attach to
885      QTreeWidgetItemIterator it(treeMetaPkgs);
886      while (*it) {
887        if ((*it)->text(0) == parent ) {
888          (*it)->addChild(deskItem);
889          if ( metaPkgList.at(z).at(5) == "YES" && (*it)->checkState(0) == Qt::Unchecked)
890            (*it)->setCheckState(0, Qt::PartiallyChecked);
891          if ( metaPkgList.at(z).at(5) == "NO" && (*it)->checkState(0) == Qt::Checked)
892            (*it)->setCheckState(0, Qt::PartiallyChecked);
893          break;
894        }
895        it++;
896      }
897    }
898
899    // Now look for any possible children
900    addTreeItems(metaPkgList.at(z).at(0));   
901  }
902}
903
904// Check if a meta-pkg is installed
905bool mainWin::isMetaPkgInstalled(QString mPkg)
906{
907  QString tmp;
908  QProcess pcmp;
909  pcmp.start(QString("pc-metapkgmanager"), QStringList() << chrootArg1 << chrootArg2 << "status" << mPkg);
910  while ( pcmp.state() != QProcess::NotRunning ) {
911     pcmp.waitForFinished(50);
912     QCoreApplication::processEvents();
913  }
914
915  while (pcmp.canReadLine()) {
916     tmp = pcmp.readLine().simplified();
917     if ( tmp.indexOf("is installed") != -1 )
918     return true;
919  }
920
921  return false;
922}
923
924// Function which checks for our GUI package schema data
925void mainWin::slotGetPackageDataOutput()
926{
927  while (getMetaProc->canReadLine())
928        tmpMetaPkgList << getMetaProc->readLine().simplified();
929}
930
931// Parse the pc-metapkg saved output
932void mainWin::parseTmpMetaList()
933{
934  QString tmp, mName, mDesc, mIcon, mParent, mDesktop, mInstalled, mPkgFileList;
935  QStringList package;
936
937  for ( int i = 0 ; i < tmpMetaPkgList.size(); i++ )
938  {
939        QApplication::processEvents();
940
941        tmp = tmpMetaPkgList.at(i);
942
943        if ( tmp.indexOf("Meta Package: ") == 0) {
944                mName = tmp.replace("Meta Package: ", "");
945                continue;
946        }
947        if ( tmp.indexOf("Description: ") == 0) {
948                mDesc = tmp.replace("Description: ", "");
949                continue;
950        }
951        if ( tmp.indexOf("Icon: ") == 0) {
952                mIcon = tmp.replace("Icon: ", "");
953                mPkgFileList = mIcon;
954                mPkgFileList.replace("pkg-icon.png", "pkg-list");
955                continue;
956        }
957        if ( tmp.indexOf("Parent: ") == 0) {
958                mParent = tmp.replace("Parent: ", "");
959                continue;
960        }
961        if ( tmp.indexOf("Desktop: ") == 0) {
962                mDesktop = tmp.replace("Desktop: ", "");
963                continue;
964        }
965
966        // This is an empty category
967        if ( tmp.indexOf("Category Entry") == 0) {
968                // Now add this category to the string list
969                package.clear();
970                qDebug() << "Found Package" << mName << mDesc << mIcon << mParent << mDesktop;
971                mInstalled = "CATEGORY";
972                package << mName << mDesc << mIcon << mParent << mDesktop << mInstalled;
973                metaPkgList.append(package);
974                mName=""; mDesc=""; mIcon=""; mParent=""; mDesktop=""; mInstalled=""; mPkgFileList="";
975        }
976
977        // We have a Meta-Pkg
978        if ( tmp.indexOf("Required Packages:") == 0) {
979                // Now add this meta-pkg to the string list
980                package.clear();
981                qDebug() << "Found Package" << mName << mDesc << mIcon << mParent << mDesktop << mPkgFileList;
982
983                if ( isMetaPkgInstalled(mName) )
984                        mInstalled = "YES";
985                else
986                        mInstalled = "NO";
987
988                package << mName << mDesc << mIcon << mParent << mDesktop << mInstalled << mPkgFileList;
989                metaPkgList.append(package);
990                mName=""; mDesc=""; mIcon=""; mParent=""; mDesktop=""; mInstalled=""; mPkgFileList="";
991        }
992
993    }
994
995}
996
997void mainWin::saveMetaPkgs()
998{
999        if ( ! haveMetaPkgChanges() )
1000                return;
1001
1002        addPkgs = getAddPkgs();
1003        delPkgs = getDelPkgs(); 
1004
1005        startMetaChanges();
1006
1007}
1008
1009void mainWin::startMetaChanges()
1010{
1011
1012  // Init the pkg process
1013  prepPkgProcess();
1014  // Create our runlist of package commands
1015  QStringList pCmds;
1016
1017  if ( ! delPkgs.isEmpty() ) {
1018    if ( wDir.isEmpty() )
1019      pCmds << "pc-metapkgmanager" << "del" << delPkgs;
1020    else 
1021      pCmds << "chroot" << wDir << "pc-metapkgmanager" << "del" << delPkgs;
1022    pkgCmdList << pCmds;
1023  }
1024 
1025  pCmds.clear();
1026
1027  if ( ! addPkgs.isEmpty() ) {
1028    if ( wDir.isEmpty() )
1029      pCmds << "pc-metapkgmanager" << "add" << addPkgs;
1030    else 
1031      pCmds << "chroot" << wDir << "pc-metapkgmanager" << "add" << addPkgs;
1032    pkgCmdList << pCmds;
1033  }
1034
1035  // Lets kick it off now
1036  startPkgProcess();
1037}
1038
1039bool mainWin::haveAMetaDesktop()
1040{
1041        // If running in a chroot we can skip this check
1042        if ( ! chrootArg1.isEmpty() )
1043          return true;
1044       
1045        QTreeWidgetItemIterator it(treeMetaPkgs);
1046        while (*it) {
1047         if ( ((*it)->checkState(0) == Qt::Checked) || ((*it)->checkState(0) == Qt::PartiallyChecked) )
1048           for (int z=0; z < metaPkgList.count(); ++z)
1049             if ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(4) == "YES" )
1050                return true;
1051         ++it;
1052        }
1053
1054        QMessageBox::warning(this, tr("No Desktop"),
1055          tr("No desktops have been selected! Please choose at least one desktop before saving."),
1056          QMessageBox::Ok,
1057          QMessageBox::Ok);
1058
1059        return false;
1060}
1061
1062bool mainWin::haveMetaPkgChanges()
1063{
1064        QTreeWidgetItemIterator it(treeMetaPkgs);
1065        while (*it) {
1066          for (int z=0; z < metaPkgList.count(); ++z)
1067            // See if any packages status have changed
1068            if ( ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(5) == "YES" && (*it)->checkState(0) == Qt::Unchecked ) \
1069              || ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(5) == "YES" && (*it)->checkState(0) == Qt::PartiallyChecked ) \
1070              || ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(5) == "NO" && (*it)->checkState(0) == Qt::Checked ) )
1071                return true;
1072         ++it;
1073        }
1074
1075        return false;
1076}
1077
1078QString mainWin::getAddPkgs()
1079{
1080        QString tmp;
1081        QTreeWidgetItemIterator it(treeMetaPkgs);
1082        while (*it) {
1083          for (int z=0; z < metaPkgList.count(); ++z)
1084            // See if any packages status have changed
1085            if ( ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(5) == "NO" && (*it)->checkState(0) == Qt::Checked ) || \
1086                 ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(5) == "NO" && (*it)->checkState(0) == Qt::PartiallyChecked ) )
1087                if ( tmp.isEmpty() )
1088                        tmp = (*it)->text(0);
1089                else
1090                        tmp = tmp + "," + (*it)->text(0);
1091         ++it;
1092        }
1093
1094        return tmp;
1095}
1096
1097QString mainWin::getDelPkgs()
1098{
1099        QString tmp;
1100        QTreeWidgetItemIterator it(treeMetaPkgs);
1101        while (*it) {
1102          for (int z=0; z < metaPkgList.count(); ++z)
1103            // See if any packages status have changed
1104            if ( (*it)->text(0) == metaPkgList.at(z).at(0) && metaPkgList.at(z).at(5) == "YES" && (*it)->checkState(0) == Qt::Unchecked )
1105                if ( tmp.isEmpty() )
1106                        tmp = (*it)->text(0);
1107                else
1108                        tmp = tmp + "," + (*it)->text(0);
1109         ++it;
1110        }
1111
1112        return tmp;
1113}
1114
1115
1116// Time to save meta-pkgs
1117void mainWin::slotApplyMetaChanges() {
1118    saveMetaPkgs();
1119}
1120
1121
1122
1123// The User changed the tree widget checked / unchecked stuff sanity check
1124void mainWin::slotDeskPkgsChanged(QTreeWidgetItem *aItem, int __unused)
1125{
1126        if (!aItem)
1127          return;
1128
1129        disconnect(treeMetaPkgs, SIGNAL(itemChanged(QTreeWidgetItem *, int)), 0, 0);
1130
1131        if (aItem->childCount() == 0) {
1132                if (aItem->checkState(0) == Qt::Checked && aItem->parent() )
1133                        if ( allChildrenPkgsChecked(aItem->parent()->text(0)))
1134                                aItem->parent()->setCheckState(0, Qt::Checked); 
1135                        else
1136                                aItem->parent()->setCheckState(0, Qt::PartiallyChecked);       
1137                if (aItem->checkState(0) == Qt::Unchecked && aItem->parent() )
1138                        if ( ! allChildrenPkgsUnchecked(aItem->parent()->text(0)))
1139                                aItem->parent()->setCheckState(0, Qt::PartiallyChecked);       
1140
1141
1142        } else {
1143                if (aItem->checkState(0) == Qt::Checked )
1144                        checkAllChildrenPkgs(aItem->text(0));
1145                else
1146                        uncheckAllChildrenPkgs(aItem->text(0));
1147        }
1148       
1149
1150    connect(treeMetaPkgs, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(slotDeskPkgsChanged(QTreeWidgetItem *, int)));
1151
1152        if ( haveMetaPkgChanges() )
1153                pushPkgApply->setEnabled(true);
1154        else
1155                pushPkgApply->setEnabled(false);
1156}
1157
1158// Check the "parent" app to see if all its children are checked or not
1159bool mainWin::allChildrenPkgsChecked(QString parent)
1160{
1161        QTreeWidgetItemIterator it(treeMetaPkgs);
1162        while (*it) {
1163         if ((*it)->text(0) == parent ) {
1164           if ( (*it)->childCount() <= 0)
1165             return true;
1166
1167           for ( int i = 0; i < (*it)->childCount() ; ++i) {
1168             if ( ! allChildrenPkgsChecked((*it)->child(i)->text(0)))
1169               return false;
1170
1171             if ((*it)->child(i)->checkState(0) != Qt::Checked ) 
1172               return false;
1173           }
1174         }
1175         ++it;
1176        }
1177        return true;
1178}
1179
1180// Check the "parent" app to see if all its children are unchecked or not
1181bool mainWin::allChildrenPkgsUnchecked(QString parent)
1182{
1183        QTreeWidgetItemIterator it(treeMetaPkgs);
1184        while (*it) {
1185         if ((*it)->text(0) == parent ) {
1186           if ( (*it)->childCount() <= 0)
1187             return true;
1188
1189           for ( int i = 0; i < (*it)->childCount() ; ++i) {
1190             if ( ! allChildrenPkgsUnchecked((*it)->child(i)->text(0)))
1191               return false;
1192
1193             if ((*it)->child(i)->checkState(0) != Qt::Unchecked ) 
1194               return false;
1195           }
1196         }
1197         ++it;
1198        }
1199        return true;
1200}
1201
1202// Check all children of parent
1203void mainWin::checkAllChildrenPkgs(QString parent)
1204{
1205        QTreeWidgetItemIterator it(treeMetaPkgs);
1206        while (*it) {
1207         if (! (*it)->parent()) {
1208           ++it;
1209           continue;
1210         } 
1211
1212         // Lets walk the tree see what pops up
1213         bool pFound=false;
1214         QTreeWidgetItem *itP = (*it)->parent();
1215         do {
1216           pFound=false;
1217           if (itP->text(0) == parent) {
1218             (*it)->setCheckState(0, Qt::Checked);
1219             break;
1220           }
1221           if ( itP->parent() ) {
1222             itP = itP->parent();
1223             pFound=true;
1224           }
1225         } while (pFound);
1226
1227         ++it;
1228       }
1229}
1230
1231// UnCheck all children of parent
1232void mainWin::uncheckAllChildrenPkgs(QString parent)
1233{
1234        QTreeWidgetItemIterator it(treeMetaPkgs);
1235        while (*it) {
1236         if (! (*it)->parent()) {
1237           ++it;
1238           continue;
1239         } 
1240
1241         // Lets walk the tree see what pops up
1242         bool pFound=false;
1243         QTreeWidgetItem *itP = (*it)->parent();
1244         do {
1245           pFound=false;
1246           if (itP->text(0) == parent) {
1247             (*it)->setCheckState(0, Qt::Unchecked);
1248             break;
1249           }
1250           if ( itP->parent() ) {
1251             itP = itP->parent();
1252             pFound=true;
1253           }
1254         } while (pFound);
1255
1256         ++it;
1257       }
1258}
1259
1260void mainWin::slotMetaRightClick()
1261{
1262        QTreeWidgetItemIterator it(treeMetaPkgs);
1263        while (*it) {
1264          for (int z=0; z < metaPkgList.count(); ++z) {
1265            if ( (*it)->isSelected() && (*it)->text(0) == metaPkgList.at(z).at(0) ) {
1266              if (metaPkgList.at(z).at(5) == "CATEGORY")
1267                return;
1268              popup = new QMenu;
1269              popup->setTitle((*it)->text(0));
1270              popup->addAction(tr("View Packages"), this, SLOT(slotMetaViewPkgs()));
1271              popup->exec( QCursor::pos() );
1272            }
1273          }
1274         ++it;
1275        }
1276}
1277
1278void mainWin::slotMetaViewPkgs()
1279{
1280        QStringList packageList;
1281        QTreeWidgetItemIterator it(treeMetaPkgs);
1282        while (*it) {
1283          for (int z=0; z < metaPkgList.count(); ++z) {
1284            if ( (*it)->isSelected() && (*it)->text(0) == metaPkgList.at(z).at(0) ) {
1285 
1286                QFile pList(metaPkgList.at(z).at(6));
1287                if ( ! pList.exists() )
1288                  return;
1289               
1290                if ( ! pList.open(QIODevice::ReadOnly | QIODevice::Text))
1291                  return;
1292
1293                while ( !pList.atEnd() )
1294                  packageList << pList.readLine().simplified();
1295
1296                pList.close();
1297                packageList.sort();
1298                       
1299                dIB = new dialogInfo();
1300                dIB->programInit(tr("Package Listing for:") + " " + (*it)->text(0));
1301                dIB->setInfoText(packageList.join("\n"));
1302                dIB->show();
1303            }
1304          }
1305         ++it;
1306        }
1307}
1308
1309QString mainWin::getLineFromCommandOutput( QString cmd )
1310{
1311        FILE *file = popen(cmd.toLatin1(),"r");
1312 
1313        char buffer[100];
1314 
1315        QString line = "";
1316        char firstChar;
1317
1318        if ((firstChar = fgetc(file)) != -1){
1319                line += firstChar;
1320                line += fgets(buffer,100,file);
1321        }
1322        pclose(file);
1323        return line.simplified();
1324}
Note: See TracBrowser for help on using the repository browser.