File indexing completed on 2024-04-14 04:52:50

0001 /* This file is part of FSView.
0002     SPDX-FileCopyrightText: 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only
0005 */
0006 
0007 /*
0008  * FSView specialization of TreeMap classes.
0009  */
0010 
0011 #include "fsview.h"
0012 
0013 #include <QDir>
0014 #include <QTimer>
0015 #include <QApplication>
0016 #include <QDebug>
0017 
0018 #include <KLocalizedString>
0019 #include <kconfig.h>
0020 #include <kmessagebox.h>
0021 
0022 #include <kio/job.h>
0023 #include <kauthorized.h>
0024 #include <kurlauthorized.h>
0025 
0026 #include "fsviewdebug.h"
0027 
0028 // FSView
0029 
0030 QMap<QString, MetricEntry> FSView::_dirMetric;
0031 
0032 FSView::FSView(Inode *base, QWidget *parent)
0033     : TreeMapWidget(base, parent)
0034 {
0035     setFieldType(0, i18n("Name"));
0036     setFieldType(1, i18n("Size"));
0037     setFieldType(2, i18n("File Count"));
0038     setFieldType(3, i18n("Directory Count"));
0039     setFieldType(4, i18n("Last Modified"));
0040     setFieldType(5, i18n("Owner"));
0041     setFieldType(6, i18n("Group"));
0042     setFieldType(7, i18n("Mime Type"));
0043 
0044     // defaults
0045     setVisibleWidth(4, true);
0046     setSplitMode(TreeMapItem::Rows);
0047     setFieldForced(0, true); // show directory names
0048     setFieldForced(1, true); // show directory sizes
0049     setSelectionMode(TreeMapWidget::Extended);
0050 
0051     _colorMode = Depth;
0052     _pathDepth = 0;
0053     _allowRefresh = true;
0054 
0055     _progressPhase = 0;
0056     _chunkData1 = 0;
0057     _chunkData2 = 0;
0058     _chunkData3 = 0;
0059     _chunkSize1 = 0;
0060     _chunkSize2 = 0;
0061     _chunkSize3 = 0;
0062     _progressSize = 0;
0063     _progress = 0;
0064     _dirsFinished = 0;
0065     _lastDir = nullptr;
0066 
0067     _config = new KConfig(QStringLiteral("fsviewrc"));
0068 
0069     // restore TreeMap visualization options of last execution
0070     KConfigGroup tmconfig(_config, "TreeMap");
0071     restoreOptions(&tmconfig);
0072     QString str = tmconfig.readEntry("ColorMode");
0073     if (!str.isEmpty()) {
0074         setColorMode(str);
0075     }
0076 
0077     if (_dirMetric.count() == 0) {
0078         // restore metric cache
0079         KConfigGroup cconfig(_config, "MetricCache");
0080         int ccount = cconfig.readEntry("Count", 0);
0081         int i, f, d;
0082         double s;
0083         QString str;
0084         for (i = 1; i <= ccount; i++) {
0085             str = QStringLiteral("Dir%1").arg(i);
0086             if (!cconfig.hasKey(str)) {
0087                 continue;
0088             }
0089             str = cconfig.readPathEntry(str, QString());
0090             s = cconfig.readEntry(QStringLiteral("Size%1").arg(i), 0.0);
0091             f = cconfig.readEntry(QStringLiteral("Files%1").arg(i), 0);
0092             d = cconfig.readEntry(QStringLiteral("Dirs%1").arg(i), 0);
0093             if (s == 0.0 || f == 0 || d == 0) {
0094                 continue;
0095             }
0096             setDirMetric(str, s, f, d);
0097         }
0098     }
0099 
0100     _sm.setListener(this);
0101 }
0102 
0103 FSView::~FSView()
0104 {
0105     delete _config;
0106 }
0107 
0108 void FSView::stop()
0109 {
0110     _sm.stopScan();
0111 }
0112 
0113 void FSView::setPath(const QString &p)
0114 {
0115     Inode *b = (Inode *)base();
0116     if (!b) {
0117         return;
0118     }
0119 
0120     //qCDebug(FSVIEWLOG) << "FSView::setPath " << p;
0121 
0122     // stop any previous updating
0123     stop();
0124 
0125     QFileInfo fi(p);
0126     _path = fi.absoluteFilePath();
0127     if (!fi.isDir()) {
0128         _path = fi.absolutePath();
0129     }
0130     _path = QDir::cleanPath(_path);
0131     _pathDepth = _path.count('/');
0132 
0133     QUrl u = QUrl::fromLocalFile(_path);
0134     if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), u)) {
0135         QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, u.toDisplayString());
0136         KMessageBox::error(this, msg);
0137     }
0138 
0139     ScanDir *d = _sm.setTop(_path);
0140 
0141     b->setPeer(d);
0142 
0143     setWindowTitle(QStringLiteral("%1 - FSView").arg(_path));
0144     requestUpdate(b);
0145 }
0146 
0147 QList<QUrl> FSView::selectedUrls()
0148 {
0149     QList<QUrl> urls;
0150 
0151     for (TreeMapItem *i: selection()) {
0152         QUrl u = QUrl::fromLocalFile(((Inode *)i)->path());
0153         urls.append(u);
0154     }
0155     return urls;
0156 }
0157 
0158 bool FSView::getDirMetric(const QString &k,
0159                           double &s, unsigned int &f, unsigned int &d)
0160 {
0161     QMap<QString, MetricEntry>::iterator it;
0162 
0163     it = _dirMetric.find(k);
0164     if (it == _dirMetric.end()) {
0165         return false;
0166     }
0167 
0168     s = (*it).size;
0169     f = (*it).fileCount;
0170     d = (*it).dirCount;
0171 
0172     if (0) {
0173         qCDebug(FSVIEWLOG) << "getDirMetric " << k;
0174     }
0175     if (0) {
0176         qCDebug(FSVIEWLOG) << " - got size " << s << ", files " << f;
0177     }
0178 
0179     return true;
0180 }
0181 
0182 void FSView::setDirMetric(const QString &k,
0183                           double s, unsigned int f, unsigned int d)
0184 {
0185     if (0) qCDebug(FSVIEWLOG) << "setDirMetric '" << k << "': size "
0186                               << s << ", files " << f << ", dirs " << d;
0187     _dirMetric.insert(k, MetricEntry(s, f, d));
0188 }
0189 
0190 void FSView::requestUpdate(Inode *i)
0191 {
0192     if (0) qCDebug(FSVIEWLOG) << "FSView::requestUpdate(" << i->path()
0193                               << ")";
0194 
0195     ScanDir *peer = i->dirPeer();
0196     if (!peer) {
0197         return;
0198     }
0199 
0200     peer->clear();
0201     i->clear();
0202 
0203     if (!_sm.scanRunning()) {
0204         QTimer::singleShot(0, this, SLOT(doUpdate()));
0205         QTimer::singleShot(100, this, SLOT(doRedraw()));
0206 
0207         /* start new progress chunk */
0208         _progressPhase = 1;
0209         _chunkData1 += 3;
0210         _chunkData2 = _chunkData1 + 1;
0211         _chunkData3 = _chunkData1 + 2;
0212         _chunkSize1 = 0;
0213         _chunkSize2 = 0;
0214         _chunkSize3 = 0;
0215         peer->setData(_chunkData1);
0216 
0217         _progressSize = 0;
0218         _progress = 0;
0219         _dirsFinished = 0;
0220         _lastDir = nullptr;
0221         emit started();
0222     }
0223 
0224     _sm.startScan(peer);
0225 }
0226 
0227 void FSView::scanFinished(ScanDir *d)
0228 {
0229     /* if finished directory was from last progress chunk, increment */
0230     int data = d->data();
0231     switch (_progressPhase) {
0232     case 1:
0233         if (data == _chunkData1) {
0234             _chunkSize1--;
0235         }
0236         break;
0237     case 2:
0238         if (data == _chunkData1) {
0239             _progress++;
0240         }
0241         if (data == _chunkData2) {
0242             _chunkSize2--;
0243         }
0244         break;
0245     case 3:
0246         if ((data == _chunkData1) ||
0247                 (data == _chunkData2)) {
0248             _progress++;
0249         }
0250         if (data == _chunkData3) {
0251             _chunkSize3--;
0252         }
0253         break;
0254     case 4:
0255         if ((data == _chunkData1) ||
0256                 (data == _chunkData2) ||
0257                 (data == _chunkData3)) {
0258             _progress++;
0259         }
0260         break;
0261     default:
0262         break;
0263     }
0264 
0265     _lastDir = d;
0266     _dirsFinished++;
0267 
0268     if (0) qCDebug(FSVIEWLOG) << "FSFiew::scanFinished: " << d->path()
0269                               << ", Data " << data
0270                               << ", Progress " << _progress << "/"
0271                               << _progressSize;
0272 }
0273 
0274 void FSView::selected(TreeMapItem *i)
0275 {
0276     setPath(((Inode *)i)->path());
0277 }
0278 
0279 void FSView::contextMenu(TreeMapItem *i, const QPoint &p)
0280 {
0281     QMenu popup;
0282 
0283     QMenu *spopup = new QMenu(i18n("Go To"));
0284     QMenu *dpopup = new QMenu(i18n("Stop at Depth"));
0285     QMenu *apopup = new QMenu(i18n("Stop at Area"));
0286     QMenu *fpopup = new QMenu(i18n("Stop at Name"));
0287 
0288     // choosing from the selection menu will give a selectionChanged() signal
0289     addSelectionItems(spopup, 901, i);
0290     popup.addMenu(spopup);
0291 
0292     QAction *actionGoUp = popup.addAction(i18n("Go Up"));
0293     popup.addSeparator();
0294     QAction *actionStopRefresh = popup.addAction(i18n("Stop Refresh"));
0295     actionStopRefresh->setEnabled(_sm.scanRunning());
0296     QAction *actionRefresh = popup.addAction(i18n("Refresh"));
0297     actionRefresh->setEnabled(!_sm.scanRunning());
0298 
0299     QAction *actionRefreshSelected = nullptr;
0300     if (i) {
0301         actionRefreshSelected = popup.addAction(i18n("Refresh '%1'", i->text(0)));
0302     }
0303     popup.addSeparator();
0304     addDepthStopItems(dpopup, 1001, i);
0305     popup.addMenu(dpopup);
0306     addAreaStopItems(apopup, 1101, i);
0307     popup.addMenu(apopup);
0308     addFieldStopItems(fpopup, 1201, i);
0309     popup.addMenu(fpopup);
0310 
0311     popup.addSeparator();
0312 
0313     QMenu *cpopup = new QMenu(i18n("Color Mode"));
0314     addColorItems(cpopup, 1401);
0315     popup.addMenu(cpopup);
0316     QMenu *vpopup = new QMenu(i18n("Visualization"));
0317     addVisualizationItems(vpopup, 1301);
0318     popup.addMenu(vpopup);
0319 
0320     _allowRefresh = false;
0321     QAction *action = popup.exec(mapToGlobal(p));
0322     _allowRefresh = true;
0323     if (!action) {
0324         return;
0325     }
0326 
0327     if (action == actionGoUp) {
0328         Inode *i = (Inode *) base();
0329         if (i) {
0330             setPath(i->path() + QLatin1String("/.."));
0331         }
0332     } else if (action == actionStopRefresh) {
0333         stop();
0334     } else if (action == actionRefreshSelected) {
0335         //((Inode*)i)->refresh();
0336         requestUpdate((Inode *)i);
0337     } else if (action == actionRefresh) {
0338         Inode *i = (Inode *) base();
0339         if (i) {
0340             requestUpdate(i);
0341         }
0342     }
0343 }
0344 
0345 void FSView::saveMetric(KConfigGroup *g)
0346 {
0347     QMap<QString, MetricEntry>::iterator it;
0348     int c = 1;
0349     for (it = _dirMetric.begin(); it != _dirMetric.end(); ++it) {
0350         g->writePathEntry(QStringLiteral("Dir%1").arg(c), it.key());
0351         g->writeEntry(QStringLiteral("Size%1").arg(c), (*it).size);
0352         g->writeEntry(QStringLiteral("Files%1").arg(c), (*it).fileCount);
0353         g->writeEntry(QStringLiteral("Dirs%1").arg(c), (*it).dirCount);
0354         c++;
0355     }
0356     g->writeEntry("Count", c - 1);
0357 }
0358 
0359 void FSView::setColorMode(FSView::ColorMode cm)
0360 {
0361     if (_colorMode == cm) {
0362         return;
0363     }
0364 
0365     _colorMode = cm;
0366     redraw();
0367 }
0368 
0369 bool FSView::setColorMode(const QString &mode)
0370 {
0371     if (mode == QLatin1String("None")) {
0372         setColorMode(None);
0373     } else if (mode == QLatin1String("Depth")) {
0374         setColorMode(Depth);
0375     } else if (mode == QLatin1String("Name")) {
0376         setColorMode(Name);
0377     } else if (mode == QLatin1String("Owner")) {
0378         setColorMode(Owner);
0379     } else if (mode == QLatin1String("Group")) {
0380         setColorMode(Group);
0381     } else if (mode == QLatin1String("Mime")) {
0382         setColorMode(Mime);
0383     } else {
0384         return false;
0385     }
0386 
0387     return true;
0388 }
0389 
0390 QString FSView::colorModeString() const
0391 {
0392     QString mode;
0393     switch (_colorMode) {
0394     case None:  mode = QStringLiteral("None"); break;
0395     case Depth: mode = QStringLiteral("Depth"); break;
0396     case Name:  mode = QStringLiteral("Name"); break;
0397     case Owner: mode = QStringLiteral("Owner"); break;
0398     case Group: mode = QStringLiteral("Group"); break;
0399     case Mime:  mode = QStringLiteral("Mime"); break;
0400     default:    mode = QStringLiteral("Unknown"); break;
0401     }
0402     return mode;
0403 }
0404 
0405 void FSView::addColorItems(QMenu *popup, int id)
0406 {
0407     _colorID = id;
0408 
0409     connect(popup, &QMenu::triggered, this, &FSView::colorActivated);
0410 
0411     addPopupItem(popup, i18n("None"),      colorMode() == None,  id++);
0412     addPopupItem(popup, i18n("Depth"),     colorMode() == Depth, id++);
0413     addPopupItem(popup, i18n("Name"),      colorMode() == Name,  id++);
0414     addPopupItem(popup, i18n("Owner"),     colorMode() == Owner, id++);
0415     addPopupItem(popup, i18n("Group"),     colorMode() == Group, id++);
0416     addPopupItem(popup, i18n("Mime Type"), colorMode() == Mime,  id++);
0417 }
0418 
0419 void FSView::colorActivated(QAction *a)
0420 {
0421     const int id = a->data().toInt();
0422     if (id == _colorID) {
0423         setColorMode(None);
0424     } else if (id == _colorID + 1) {
0425         setColorMode(Depth);
0426     } else if (id == _colorID + 2) {
0427         setColorMode(Name);
0428     } else if (id == _colorID + 3) {
0429         setColorMode(Owner);
0430     } else if (id == _colorID + 4) {
0431         setColorMode(Group);
0432     } else if (id == _colorID + 5) {
0433         setColorMode(Mime);
0434     }
0435 }
0436 
0437 void FSView::keyPressEvent(QKeyEvent *e)
0438 {
0439     if (e->key() == Qt::Key_Escape && !_pressed && (selection().size() > 0)) {
0440         // For consistency with Dolphin, deselect all on Escape if we're not dragging.
0441         TreeMapItem *changed = selection().commonParent();
0442         if (changed) {
0443             clearSelection(changed);
0444         }
0445     } else {
0446         TreeMapWidget::keyPressEvent(e);
0447     }
0448 }
0449 
0450 void FSView::saveFSOptions()
0451 {
0452     KConfigGroup tmconfig(_config, "TreeMap");
0453     saveOptions(&tmconfig);
0454     tmconfig.writeEntry("ColorMode", colorModeString());
0455 
0456     KConfigGroup gconfig(_config, "General");
0457     gconfig.writeEntry("Path", _path);
0458 
0459     KConfigGroup cconfig(_config, "MetricCache");
0460     saveMetric(&cconfig);
0461 }
0462 
0463 void FSView::quit()
0464 {
0465     saveFSOptions();
0466     qApp->quit();
0467 }
0468 
0469 void FSView::doRedraw()
0470 {
0471     // we update progress every 1/4 second, and redraw every second
0472     static int redrawCounter = 0;
0473 
0474     bool redo = _sm.scanRunning();
0475     if (!redo) {
0476         redrawCounter = 0;
0477     }
0478 
0479     if ((_progress > 0) && (_progressSize > 0) && _lastDir) {
0480         int percent = _progress * 100 / _progressSize;
0481         if (0) qCDebug(FSVIEWLOG) << "FSView::progress "
0482                                   << _progress << "/" << _progressSize
0483                                   << "= " << percent << "%, "
0484                                   << _dirsFinished << " dirs read, in "
0485                                   << _lastDir->path();
0486         emit progress(percent, _dirsFinished, _lastDir->path());
0487     }
0488 
0489     if (_allowRefresh && ((redrawCounter % 4) == 0)) {
0490         if (0) {
0491             qCDebug(FSVIEWLOG) << "doRedraw " << _sm.scanLength();
0492         }
0493         redraw();
0494     } else {
0495         redo = true;
0496     }
0497 
0498     if (redo) {
0499         QTimer::singleShot(500, this, SLOT(doRedraw()));
0500         redrawCounter++;
0501     }
0502 }
0503 
0504 void FSView::doUpdate()
0505 {
0506     for (int i = 0; i < 5; i++) {
0507         switch (_progressPhase) {
0508         case 1:
0509             _chunkSize1 += _sm.scan(_chunkData1);
0510             if (_chunkSize1 > 100) {
0511                 _progressPhase = 2;
0512 
0513                 /* Go to maximally 33% by scaling with 3 */
0514                 _progressSize = 3 * _chunkSize1;
0515 
0516                 if (1) {
0517                     qCDebug(FSVIEWLOG) << "Phase 2: CSize " << _chunkSize1;
0518                 }
0519             }
0520             break;
0521 
0522         case 2:
0523             /* progress phase 2 */
0524             _chunkSize2 += _sm.scan(_chunkData2);
0525             /* switch to Phase 3 if we reach 80 % of Phase 2 */
0526             if (_progress * 3 > _progressSize * 8 / 10) {
0527                 _progressPhase = 3;
0528 
0529                 /* Goal: Keep percentage equal from phase 2 to 3 */
0530                 double percent = (double)_progress / _progressSize;
0531                 /* We scale by factor 2/3 afterwards */
0532                 percent = percent * 3 / 2;
0533 
0534                 int todo = _chunkSize2 + (_progressSize / 3 - _progress);
0535                 _progressSize = (int)((double)todo / (1.0 - percent));
0536                 _progress = _progressSize - todo;
0537 
0538                 /* Go to maximally 66% by scaling with 1.5 */
0539                 _progressSize = _progressSize * 3 / 2;
0540 
0541                 if (1) qCDebug(FSVIEWLOG) << "Phase 3: CSize " << _chunkSize2
0542                                           << ", Todo " << todo
0543                                           << ", Progress " << _progress
0544                                           << "/" << _progressSize;
0545             }
0546             break;
0547 
0548         case 3:
0549             /* progress phase 3 */
0550             _chunkSize3 += _sm.scan(_chunkData3);
0551             /* switch to Phase 4 if we reach 80 % of Phase 3 */
0552             if (_progress * 3 / 2 > _progressSize * 8 / 10) {
0553                 _progressPhase = 4;
0554 
0555                 /* Goal: Keep percentage equal from phase 2 to 3 */
0556                 double percent = (double)_progress / _progressSize;
0557                 int todo = _chunkSize3 + (_progressSize * 2 / 3 - _progress);
0558                 _progressSize = (int)((double)todo / (1.0 - percent) + .5);
0559                 _progress = _progressSize - todo;
0560 
0561                 if (1) qCDebug(FSVIEWLOG) << "Phase 4: CSize " << _chunkSize3
0562                                           << ", Todo " << todo
0563                                           << ", Progress " << _progress
0564                                           << "/" << _progressSize;
0565             }
0566 
0567         default:
0568             _sm.scan(-1);
0569             break;
0570         }
0571     }
0572 
0573     if (_sm.scanRunning()) {
0574         QTimer::singleShot(0, this, SLOT(doUpdate()));
0575     } else {
0576         emit completed(_dirsFinished);
0577     }
0578 }
0579