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