File indexing completed on 2024-04-28 04:52:16

0001 /*
0002 SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "temporarydata.h"
0009 #include "bin/bin.h"
0010 #include "core.h"
0011 #include "doc/kdenlivedoc.h"
0012 #include "kdenlivesettings.h"
0013 
0014 #include <KLocalizedString>
0015 #include <KMessageBox>
0016 #include <QDesktopServices>
0017 #include <QFontMetrics>
0018 #include <QGridLayout>
0019 #include <QLabel>
0020 #include <QPaintEvent>
0021 #include <QPainter>
0022 #include <QProgressBar>
0023 #include <QPushButton>
0024 #include <QSpinBox>
0025 #include <QStandardPaths>
0026 #include <QTabWidget>
0027 #include <QToolButton>
0028 #include <QToolTip>
0029 #include <QTreeWidget>
0030 #include <QVBoxLayout>
0031 
0032 ChartWidget::ChartWidget(QWidget *parent)
0033     : QWidget(parent)
0034 {
0035     QFontMetrics ft(font());
0036     int minHeight = ft.height() * 6;
0037     setMinimumSize(minHeight, minHeight);
0038     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0039     m_segments = QList<int>();
0040 }
0041 
0042 void ChartWidget::setSegments(const QList<int> &segments)
0043 {
0044     m_segments = segments;
0045     update();
0046 }
0047 
0048 void ChartWidget::paintEvent(QPaintEvent *event)
0049 {
0050     QPainter painter(this);
0051     painter.setRenderHints(QPainter::Antialiasing);
0052     const QRectF clipRect = event->rect();
0053     painter.setClipRect(clipRect);
0054     painter.setPen(palette().midlight().color());
0055     int pieWidth = qMin(width(), height()) - 10;
0056     const QRectF pieRect(5, 5, pieWidth, pieWidth);
0057     int ix = 0;
0058     int previous = 0;
0059     for (int val : qAsConst(m_segments)) {
0060         if (val == 0) {
0061             ix++;
0062             continue;
0063         }
0064         painter.setBrush(colorAt(ix));
0065         painter.drawPie(pieRect, previous, val);
0066         previous += val;
0067         ix++;
0068     }
0069 }
0070 
0071 TemporaryData::TemporaryData(KdenliveDoc *doc, bool currentProjectOnly, QWidget *parent)
0072     : QDialog(parent)
0073     , m_doc(doc)
0074     , m_currentProjectOnly(currentProjectOnly)
0075 {
0076     setAttribute(Qt::WA_DeleteOnClose);
0077     setupUi(this);
0078     m_currentSizes = {0, 0, 0, 0, 0};
0079 
0080     // Setup page for current project
0081     m_currentPie = new ChartWidget(this);
0082     currentChartBox->addWidget(m_currentPie);
0083 
0084     QPalette pal(palette());
0085     QFontMetrics ft(font());
0086     int minHeight = ft.height() / 2;
0087 
0088     // Timeline preview data
0089     previewColor->setFixedSize(minHeight, minHeight);
0090     pal.setColor(QPalette::Window, m_currentPie->colorAt(0));
0091     previewColor->setPalette(pal);
0092     connect(delPreview, &QToolButton::clicked, this, &TemporaryData::deletePreview);
0093 
0094     // Proxy clips
0095     proxyColor->setFixedSize(minHeight, minHeight);
0096     pal.setColor(QPalette::Window, m_currentPie->colorAt(1));
0097     proxyColor->setPalette(pal);
0098     connect(delProxy, &QToolButton::clicked, this, &TemporaryData::deleteProjectProxy);
0099 
0100     // Proxy clips
0101     sequenceColor->setFixedSize(minHeight, minHeight);
0102     pal.setColor(QPalette::Window, m_currentPie->colorAt(4));
0103     sequenceColor->setPalette(pal);
0104 
0105     // Audio Thumbs
0106     audioColor->setFixedSize(minHeight, minHeight);
0107     pal.setColor(QPalette::Window, m_currentPie->colorAt(2));
0108     audioColor->setPalette(pal);
0109     connect(delAudio, &QToolButton::clicked, this, &TemporaryData::deleteAudio);
0110 
0111     // Video Thumbs
0112     thumbColor->setFixedSize(minHeight, minHeight);
0113     pal.setColor(QPalette::Window, m_currentPie->colorAt(3));
0114     thumbColor->setPalette(pal);
0115     connect(delThumb, &QToolButton::clicked, this, &TemporaryData::deleteThumbs);
0116 
0117     // Current total
0118     bool ok;
0119     QDir dir = m_doc->getCacheDir(CacheBase, &ok);
0120     currentPath->setText(QStringLiteral("<a href='#'>") + dir.absolutePath() + QStringLiteral("</a>"));
0121     connect(currentPath, &QLabel::linkActivated, this, &TemporaryData::openCacheFolder);
0122     connect(delCurrent, &QToolButton::clicked, this, &TemporaryData::deleteCurrentCacheData);
0123 
0124     m_proxies = m_doc->getProxyHashList();
0125     for (int i = 0; i < m_proxies.count(); i++) {
0126         m_proxies[i].append(QLatin1Char('*'));
0127     }
0128 
0129     // Setup global page
0130     m_globalPie = new ChartWidget(this);
0131     gChartLayout->addWidget(m_globalPie);
0132     gChartLayout->setStretch(0, 15);
0133     gChartLayout->setStretch(1, 5);
0134 
0135     connect(listWidget, &QTreeWidget::itemDoubleClicked, this, [&](QTreeWidgetItem *item, int) {
0136         QDesktopServices::openUrl(QUrl::fromLocalFile(m_globalDir.absoluteFilePath(item->data(0, Qt::UserRole).toString())));
0137     });
0138 
0139     // Total Cache data
0140     pal = palette();
0141     gTotalColor->setFixedSize(minHeight, minHeight);
0142     pal.setColor(QPalette::Window, m_currentPie->colorAt(0));
0143     gTotalColor->setPalette(pal);
0144     connect(gClean, &QToolButton::clicked, this, &TemporaryData::cleanCache);
0145 
0146     // Selection
0147     gSelectedColor->setFixedSize(minHeight, minHeight);
0148     pal.setColor(QPalette::Window, m_currentPie->colorAt(1));
0149     gSelectedColor->setPalette(pal);
0150     connect(gDelete, &QToolButton::clicked, this, &TemporaryData::deleteSelected);
0151 
0152     // Proxy data
0153     connect(gProxyClean, &QToolButton::clicked, this, &TemporaryData::cleanProxy);
0154     connect(gProxyDelete, &QToolButton::clicked, this, &TemporaryData::deleteProxy);
0155     ok = false;
0156     QDir global = m_doc->getCacheDir(SystemCacheRoot, &ok);
0157     QDir proxyFolder(global.absoluteFilePath(QStringLiteral("proxy")));
0158     gProxyPath->setText(QString("<a href='#'>%1</a>").arg(proxyFolder.absolutePath()));
0159     connect(gProxyPath, &QLabel::linkActivated, [proxyFolder]() { QDesktopServices::openUrl(QUrl::fromLocalFile(proxyFolder.absolutePath())); });
0160 
0161     // Backup data
0162     connect(gBackupClean, &QToolButton::clicked, this, &TemporaryData::cleanBackup);
0163     connect(gBackupDelete, &QToolButton::clicked, this, &TemporaryData::deleteBackup);
0164     QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup"));
0165     gBackupPath->setText(QString("<a href='#'>%1</a>").arg(backupFolder.absolutePath()));
0166     connect(gBackupPath, &QLabel::linkActivated, [backupFolder]() { QDesktopServices::openUrl(QUrl::fromLocalFile(backupFolder.absolutePath())); });
0167 
0168     // Config cleanup age
0169     gCleanupSpin->setSuffix(i18np(" month", " months", KdenliveSettings::cleanCacheMonths()));
0170     gCleanupSpin->setValue(KdenliveSettings::cleanCacheMonths());
0171     connect(gCleanupSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [&](int value) {
0172         KdenliveSettings::setCleanCacheMonths(value);
0173         gCleanupSpin->setSuffix(i18np(" month", " months", KdenliveSettings::cleanCacheMonths()));
0174     });
0175 
0176     // Setup help text
0177     help_cached->setToolTip(i18n("<b>Cached data</b> is composed of clip thumbnails and timeline preview videos. Deleting is safe, all data can be recreated "
0178                                  "on project opening.<br/><b>Backup data</b> is an archive of previous versions of your project files. Useful if you need to "
0179                                  "recover a previous version of a project. This data cannot be recovered.<br/><b>Proxy clips</b> are lower resolution video "
0180                                  "files used for faster editing. Deleting is safe, proxy clips can be recreated if you have the original source clips."));
0181     connect(help_cached, &QToolButton::clicked, this, [this]() { QToolTip::showText(QCursor::pos(), help_cached->toolTip()); });
0182 
0183     // Cache info message
0184     cache_info->hide();
0185     QAction *a = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("Cleanup"), this);
0186     cache_info->setText(i18n("Your cache and backup data exceeds %1, cleanup is recommended.", KIO::convertSize(1048576 * KdenliveSettings::maxcachesize())));
0187     cache_info->addAction(a);
0188     connect(a, &QAction::triggered, this, &TemporaryData::slotCleanUp);
0189 
0190     processBackupDirectories();
0191 
0192     connect(listWidget, &QTreeWidget::itemSelectionChanged, this, &TemporaryData::refreshGlobalPie);
0193 
0194     bool globalOnly = !pCore->bin()->hasUserClip();
0195     if (currentProjectOnly) {
0196         tabWidget->removeTab(1);
0197     } else if (globalOnly) {
0198         tabWidget->removeTab(0);
0199     }
0200 
0201     if (globalOnly && !currentProjectOnly) {
0202         updateGlobalInfo();
0203     } else {
0204         updateDataInfo();
0205     }
0206 }
0207 
0208 void TemporaryData::updateDataInfo()
0209 {
0210     m_totalCurrent = 0;
0211     bool ok = false;
0212     QDir preview = m_doc->getCacheDir(CacheBase, &ok);
0213     if (!ok) {
0214         projectPage->setEnabled(false);
0215         return;
0216     }
0217     preview = m_doc->getCacheDir(CachePreview, &ok);
0218     if (ok) {
0219         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(preview.absolutePath()));
0220         connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotPreviewSize);
0221     }
0222 
0223     preview = m_doc->getCacheDir(CacheProxy, &ok);
0224     if (ok) {
0225         if (m_proxies.isEmpty()) {
0226             // No proxies for this project
0227             gotProxySize(0);
0228         } else {
0229             preview.setNameFilters(m_proxies);
0230             const QFileInfoList fList = preview.entryInfoList();
0231             size_t size = 0;
0232             for (const QFileInfo &info : fList) {
0233                 size += size_t(info.size());
0234             }
0235             gotProxySize(size);
0236         }
0237     }
0238 
0239     preview = m_doc->getCacheDir(CacheAudio, &ok);
0240     if (ok) {
0241         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(preview.absolutePath()));
0242         connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotAudioSize);
0243     }
0244     preview = m_doc->getCacheDir(CacheSequence, &ok);
0245     if (ok) {
0246         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(preview.absolutePath()));
0247         connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotSequenceSize);
0248     }
0249     preview = m_doc->getCacheDir(CacheTmpWorkFiles, &ok);
0250     if (ok) {
0251         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(preview.absolutePath()));
0252         connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotSequenceSize);
0253     }
0254     preview = m_doc->getCacheDir(CacheThumbs, &ok);
0255     if (ok) {
0256         KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(preview.absolutePath()));
0257         connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotThumbSize);
0258     }
0259     if (!m_currentProjectOnly) {
0260         updateGlobalInfo();
0261     }
0262 }
0263 
0264 void TemporaryData::gotPreviewSize(KJob *job)
0265 {
0266     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0267     KIO::filesize_t total = sourceJob->totalSize();
0268     if (sourceJob->totalFiles() == 0) {
0269         total = 0;
0270     }
0271     delPreview->setEnabled(total > 0);
0272     m_totalCurrent += total;
0273     m_currentSizes[0] = total;
0274     previewSize->setText(KIO::convertSize(total));
0275     updateTotal();
0276 }
0277 
0278 void TemporaryData::gotProxySize(KIO::filesize_t total)
0279 {
0280     delProxy->setEnabled(total > 0);
0281     m_totalCurrent += total;
0282     m_currentSizes[1] = total;
0283     proxySize->setText(KIO::convertSize(total));
0284     updateTotal();
0285 }
0286 
0287 void TemporaryData::gotAudioSize(KJob *job)
0288 {
0289     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0290     KIO::filesize_t total = sourceJob->totalSize();
0291     if (sourceJob->totalFiles() == 0) {
0292         total = 0;
0293     }
0294     delAudio->setEnabled(total > 0);
0295     m_totalCurrent += total;
0296     m_currentSizes[2] = total;
0297     audioSize->setText(KIO::convertSize(total));
0298     updateTotal();
0299 }
0300 
0301 void TemporaryData::gotThumbSize(KJob *job)
0302 {
0303     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0304     KIO::filesize_t total = sourceJob->totalSize();
0305     if (sourceJob->totalFiles() == 0) {
0306         total = 0;
0307     }
0308     delThumb->setEnabled(total > 0);
0309     m_totalCurrent += total;
0310     m_currentSizes[3] = total;
0311     thumbSize->setText(KIO::convertSize(total));
0312     updateTotal();
0313 }
0314 
0315 void TemporaryData::gotSequenceSize(KJob *job)
0316 {
0317     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0318     KIO::filesize_t total = sourceJob->totalSize();
0319     if (sourceJob->totalFiles() == 0) {
0320         total = 0;
0321     }
0322     m_totalCurrent += total;
0323     m_currentSizes[4] += total;
0324     sequenceSize->setText(KIO::convertSize(m_currentSizes.at(4)));
0325     updateTotal();
0326 }
0327 
0328 void TemporaryData::updateTotal()
0329 {
0330     currentSize->setText(KIO::convertSize(m_totalCurrent));
0331     delCurrent->setEnabled(m_totalCurrent > 0);
0332     QList<int> segments;
0333     for (KIO::filesize_t size : qAsConst(m_currentSizes)) {
0334         if (m_totalCurrent == 0) {
0335             segments << 0;
0336         } else {
0337             segments << static_cast<int>(16 * size * 360 / m_totalCurrent);
0338         }
0339     }
0340     m_currentPie->setSegments(segments);
0341 }
0342 
0343 void TemporaryData::deletePreview()
0344 {
0345     bool ok = false;
0346     QDir dir = m_doc->getCacheDir(CachePreview, &ok);
0347     if (!ok) {
0348         return;
0349     }
0350     if (KMessageBox::warningContinueCancel(
0351             this,
0352             i18n("Delete all data in the preview folder:\n%1\nPreview folder contains the timeline previews, and can be recreated with the source project.",
0353                  dir.absolutePath())) != KMessageBox::Continue) {
0354         return;
0355     }
0356     if (dir.dirName() == QLatin1String("preview")) {
0357         dir.removeRecursively();
0358         dir.mkpath(QStringLiteral("."));
0359         Q_EMIT disablePreview();
0360         updateDataInfo();
0361     }
0362 }
0363 
0364 void TemporaryData::deleteBackup()
0365 {
0366     QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup"));
0367     if (KMessageBox::warningContinueCancel(
0368             this, i18n("Delete all data in the backup folder:\n%1\nA copy of all your project files is kept in this folder for recovery in case of corruption.",
0369                        backupFolder.absolutePath())) != KMessageBox::Continue) {
0370         return;
0371     }
0372     if (backupFolder.dirName() == QLatin1String(".backup")) {
0373         backupFolder.removeRecursively();
0374         backupFolder.mkpath(QStringLiteral("."));
0375         processBackupDirectories();
0376     }
0377 }
0378 
0379 void TemporaryData::cleanBackup()
0380 {
0381     QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup"));
0382     QFileInfoList files = backupFolder.entryInfoList(QDir::Files, QDir::Time);
0383     QStringList oldFiles;
0384     QDateTime current = QDateTime::currentDateTime();
0385     KIO::filesize_t totalSize = 0;
0386     for (const QFileInfo &f : qAsConst(files)) {
0387         if (f.lastModified().addMonths(KdenliveSettings::cleanCacheMonths()) < current) {
0388             oldFiles << f.fileName();
0389             totalSize += f.size();
0390         }
0391     }
0392     if (oldFiles.isEmpty()) {
0393         KMessageBox::information(this, i18n("No backup data older than %1 months was found.", KdenliveSettings::cleanCacheMonths()));
0394         return;
0395     }
0396     if (KMessageBox::warningContinueCancelList(
0397             this,
0398             i18n("This will delete backup data (%1) for projects older than %2 months.", KIO::convertSize(totalSize), KdenliveSettings::cleanCacheMonths()),
0399             oldFiles) != KMessageBox::Continue) {
0400         return;
0401     }
0402     if (backupFolder.dirName() == QLatin1String(".backup")) {
0403         for (const QString &f : qAsConst(oldFiles)) {
0404             backupFolder.remove(f);
0405         }
0406         processBackupDirectories();
0407     }
0408 }
0409 
0410 void TemporaryData::cleanCache()
0411 {
0412     // Find empty dirs
0413     QList<QTreeWidgetItem *> emptyDirs = listWidget->findItems(KIO::convertSize(0), Qt::MatchExactly, 2);
0414     // Find old dirs
0415     QTreeWidgetItem *root = listWidget->invisibleRootItem();
0416     if (!root) {
0417         return;
0418     }
0419     // Find old backup data ( older than x months ), or very small (that can be quickly recreated)
0420     size_t total = 0;
0421     QDateTime current = QDateTime::currentDateTime();
0422     int max = root->childCount();
0423     for (int i = 0; i < max; i++) {
0424         QTreeWidgetItem *child = root->child(i);
0425         if (emptyDirs.contains(child)) {
0426             continue;
0427         }
0428         size_t childSize = size_t(child->data(1, Qt::UserRole).toLongLong());
0429         // Check temporary folders with content less than 200kB or old data
0430         if (childSize < 200000 || child->data(2, Qt::UserRole).toDateTime().addMonths(KdenliveSettings::cleanCacheMonths()) < current) {
0431             emptyDirs << child;
0432             total += childSize;
0433         }
0434     }
0435     QStringList folders;
0436     for (auto *item : qAsConst(emptyDirs)) {
0437         folders << item->data(0, Qt::UserRole).toString();
0438     }
0439     if (folders.isEmpty()) {
0440         KMessageBox::information(this, i18n("No cache data older than %1 months was found.", KdenliveSettings::cleanCacheMonths()));
0441         return;
0442     }
0443 
0444     if (KMessageBox::warningContinueCancelList(this,
0445                                                i18n("This will delete cache data (%1) for projects that were deleted, have very few cached data or older than "
0446                                                     "%2 months. All cached data can be recreated from the source files on project opening.",
0447                                                     KIO::convertSize(total), KdenliveSettings::cleanCacheMonths()),
0448                                                folders) != KMessageBox::Continue) {
0449         return;
0450     }
0451     deleteCache(folders);
0452 }
0453 
0454 void TemporaryData::deleteProjectProxy()
0455 {
0456     if (m_proxies.isEmpty()) {
0457         KMessageBox::information(this, i18n("No proxies found in the current project."));
0458         return;
0459     }
0460     bool ok = false;
0461     QDir dir = m_doc->getCacheDir(CacheProxy, &ok);
0462     if (!ok || dir.dirName() != QLatin1String("proxy")) {
0463         return;
0464     }
0465     dir.setNameFilters(m_proxies);
0466     QStringList files = dir.entryList(QDir::Files);
0467     if (KMessageBox::warningContinueCancelList(this,
0468                                                i18n("Delete all project data in the proxy folder:\n%1\nProxy folder contains the proxy clips for all your "
0469                                                     "projects. This proxies can be recreated from the source clips.",
0470                                                     dir.absolutePath()),
0471                                                files) != KMessageBox::Continue) {
0472         return;
0473     }
0474     for (const QString &file : qAsConst(files)) {
0475         dir.remove(file);
0476     }
0477     Q_EMIT disableProxies();
0478     updateDataInfo();
0479 }
0480 
0481 void TemporaryData::deleteAudio()
0482 {
0483     bool ok = false;
0484     QDir dir = m_doc->getCacheDir(CacheAudio, &ok);
0485     if (!ok) {
0486         return;
0487     }
0488     if (KMessageBox::warningContinueCancel(
0489             this, i18n("Delete all data in the cache audio folder:\n%1\nThis folder contains the data for audio thumbnails in this project.",
0490                        dir.absolutePath())) != KMessageBox::Continue) {
0491         return;
0492     }
0493     if (dir.dirName() == QLatin1String("audiothumbs")) {
0494         dir.removeRecursively();
0495         dir.mkpath(QStringLiteral("."));
0496         updateDataInfo();
0497     }
0498 }
0499 
0500 void TemporaryData::deleteThumbs()
0501 {
0502     bool ok = false;
0503     QDir dir = m_doc->getCacheDir(CacheThumbs, &ok);
0504     if (!ok) {
0505         return;
0506     }
0507     if (KMessageBox::warningContinueCancel(
0508             this, i18n("Delete all data in the cache thumbnail folder:\n%1\nThis folder contains the data for video thumbnails in this project.",
0509                        dir.absolutePath())) != KMessageBox::Continue) {
0510         return;
0511     }
0512     if (dir.dirName() == QLatin1String("videothumbs")) {
0513         dir.removeRecursively();
0514         dir.mkpath(QStringLiteral("."));
0515         updateDataInfo();
0516     }
0517 }
0518 
0519 void TemporaryData::deleteCurrentCacheData(bool warn)
0520 {
0521     bool ok = false;
0522     QDir dir = m_doc->getCacheDir(CacheBase, &ok);
0523     if (!ok) {
0524         return;
0525     }
0526     if (warn && KMessageBox::warningContinueCancel(this, i18n("Delete all data in the cache folder:\n%1\nCache folder contains the audio and video thumbnails, "
0527                                                               "as well as timeline previews. All this data will be recreated on project opening.",
0528                                                               dir.absolutePath())) != KMessageBox::Continue) {
0529         return;
0530     }
0531     if (dir.dirName() == m_doc->getDocumentProperty(QStringLiteral("documentid"))) {
0532         Q_EMIT disablePreview();
0533         Q_EMIT disableProxies();
0534         dir.removeRecursively();
0535         m_doc->initCacheDirs();
0536         if (warn) {
0537             updateDataInfo();
0538         }
0539     }
0540 }
0541 
0542 void TemporaryData::openCacheFolder()
0543 {
0544     bool ok = false;
0545     QDir dir = m_doc->getCacheDir(CacheBase, &ok);
0546     if (!ok) {
0547         return;
0548     }
0549     QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
0550 }
0551 
0552 void TemporaryData::processBackupDirectories()
0553 {
0554     QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup"));
0555     KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(backupFolder.absolutePath()));
0556     connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotBackupSize);
0557 }
0558 
0559 void TemporaryData::processProxyDirectory()
0560 {
0561     KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(m_globalDir.absoluteFilePath(QStringLiteral("proxy"))));
0562     connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotProjectProxySize);
0563 }
0564 
0565 void TemporaryData::gotProjectProxySize(KJob *job)
0566 {
0567     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0568     KIO::filesize_t total = sourceJob->totalSize();
0569     m_totalProxy = total;
0570     refreshWarningMessage();
0571     gProxySize->setText(KIO::convertSize(total));
0572 }
0573 
0574 void TemporaryData::gotBackupSize(KJob *job)
0575 {
0576     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0577     KIO::filesize_t total = sourceJob->totalSize();
0578     if (sourceJob->totalFiles() == 0) {
0579         total = 0;
0580     }
0581     m_totalBackup = total;
0582     refreshWarningMessage();
0583     gBackupSize->setText(KIO::convertSize(total));
0584 }
0585 
0586 void TemporaryData::updateGlobalInfo()
0587 {
0588     listWidget->blockSignals(true);
0589     bool ok = false;
0590     QDir preview = m_doc->getCacheDir(SystemCacheRoot, &ok);
0591     if (!ok) {
0592         globalPage->setEnabled(false);
0593         return;
0594     }
0595     m_globalDir = preview;
0596     m_globalDirectories.clear();
0597     m_processingDirectory.clear();
0598     m_totalGlobal = 0;
0599     m_totalProxy = 0;
0600     m_totalBackup = 0;
0601     listWidget->clear();
0602     m_globalDirectories = m_globalDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0603     // These are some KDE cache dirs related to Kdenlive that don't manage ourselves
0604     m_globalDirectories.removeAll(QStringLiteral("knewstuff"));
0605     m_globalDirectories.removeAll(QStringLiteral("attica"));
0606     m_globalDirectories.removeAll(QStringLiteral("proxy"));
0607     gDelete->setEnabled(!m_globalDirectories.isEmpty());
0608     processProxyDirectory();
0609     processglobalDirectories();
0610     listWidget->blockSignals(false);
0611 }
0612 
0613 void TemporaryData::processglobalDirectories()
0614 {
0615     if (m_globalDirectories.isEmpty()) {
0616         return;
0617     }
0618     m_processingDirectory = m_globalDirectories.takeFirst();
0619     KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(m_globalDir.absoluteFilePath(m_processingDirectory)));
0620     connect(job, &KIO::DirectorySizeJob::result, this, &TemporaryData::gotFolderSize);
0621 }
0622 
0623 void TemporaryData::gotFolderSize(KJob *job)
0624 {
0625     if (m_processingDirectory.isEmpty()) {
0626         return;
0627     }
0628     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0629     KIO::filesize_t total = sourceJob->totalSize();
0630     if (sourceJob->totalFiles() == 0) {
0631         total = 0;
0632     }
0633     m_totalGlobal += total;
0634     auto *item = new TreeWidgetItem(listWidget);
0635     // Check last save path for this cache folder
0636     QDir dir(m_globalDir.absoluteFilePath(m_processingDirectory));
0637     QStringList filters;
0638     filters << QStringLiteral("*.kdenlive");
0639     QStringList str = dir.entryList(filters, QDir::Files | QDir::Hidden, QDir::Time);
0640     if (!str.isEmpty()) {
0641         QString path = QUrl::fromPercentEncoding(str.at(0).toUtf8());
0642         // Remove leading dot
0643         path.remove(0, 1);
0644         item->setText(0, m_processingDirectory + QStringLiteral(" (%1)").arg(QUrl::fromLocalFile(path).fileName()));
0645         if (QFile::exists(path)) {
0646             item->setIcon(0, QIcon::fromTheme(QStringLiteral("kdenlive")));
0647         } else {
0648             item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close")));
0649         }
0650     } else {
0651         item->setText(0, m_processingDirectory);
0652         if (m_processingDirectory == QLatin1String("proxy")) {
0653             item->setIcon(0, QIcon::fromTheme(QStringLiteral("kdenlive-show-video")));
0654         }
0655     }
0656     item->setData(0, Qt::UserRole, m_processingDirectory);
0657     item->setText(1, KIO::convertSize(total));
0658     QDateTime date = QFileInfo(dir.absolutePath()).lastModified();
0659     QLocale locale;
0660     item->setText(2, locale.toString(date, QLocale::ShortFormat));
0661     item->setData(1, Qt::UserRole, total);
0662     item->setData(2, Qt::UserRole, date);
0663     listWidget->addTopLevelItem(item);
0664     listWidget->resizeColumnToContents(0);
0665     listWidget->resizeColumnToContents(1);
0666     if (m_globalDirectories.isEmpty()) {
0667         // Processing done, check total size
0668         refreshWarningMessage();
0669         gTotalSize->setText(KIO::convertSize(m_totalGlobal));
0670         listWidget->setCurrentItem(listWidget->topLevelItem(0));
0671     } else {
0672         processglobalDirectories();
0673     }
0674 }
0675 
0676 void TemporaryData::refreshWarningMessage()
0677 {
0678     if (KdenliveSettings::maxcachesize() > 0 &&
0679         ((m_totalGlobal + m_totalBackup + m_totalProxy) > KIO::filesize_t(1048576) * KdenliveSettings::maxcachesize())) {
0680         // Cache larger than x MB, warn
0681         cache_info->animatedShow();
0682     } else {
0683         cache_info->animatedHide();
0684     }
0685 }
0686 
0687 void TemporaryData::refreshGlobalPie()
0688 {
0689     QList<QTreeWidgetItem *> list = listWidget->selectedItems();
0690     KIO::filesize_t currentSize = 0;
0691     for (QTreeWidgetItem *current : qAsConst(list)) {
0692         if (current) {
0693             currentSize += current->data(1, Qt::UserRole).toULongLong();
0694         }
0695     }
0696     gSelectedSize->setText(KIO::convertSize(currentSize));
0697     int percent = m_totalGlobal <= 0 ? 0 : int(16 * currentSize * 360 / m_totalGlobal);
0698     m_globalPie->setSegments(QList<int>() << 16 * 360 << percent);
0699     if (list.size() == 1 && list.at(0)->text(0) == m_doc->getDocumentProperty(QStringLiteral("documentid"))) {
0700         gDelete->setToolTip(i18n("Clear current cache"));
0701     } else {
0702         gDelete->setToolTip(i18n("Delete selected cache"));
0703     }
0704 }
0705 
0706 void TemporaryData::deleteSelected()
0707 {
0708     QList<QTreeWidgetItem *> list = listWidget->selectedItems();
0709     QStringList folders;
0710     for (QTreeWidgetItem *current : qAsConst(list)) {
0711         if (current) {
0712             folders << current->data(0, Qt::UserRole).toString();
0713         }
0714     }
0715     if (KMessageBox::warningContinueCancelList(this,
0716                                                i18n("Delete the following cache folders from\n%1\nCache folders contains the audio and video thumbnails, as "
0717                                                     "well as timeline previews. All this data will be recreated on project opening.",
0718                                                     m_globalDir.absolutePath()),
0719                                                folders) != KMessageBox::Continue) {
0720         return;
0721     }
0722     deleteCache(folders);
0723 }
0724 
0725 void TemporaryData::deleteCache(QStringList &folders)
0726 {
0727     const QString currentId = m_doc->getDocumentProperty(QStringLiteral("documentid"));
0728     for (const QString &folder : qAsConst(folders)) {
0729         if (folder == currentId) {
0730             // Trying to delete current project's tmp folder. Do not delete, but clear it
0731             deleteCurrentCacheData(false);
0732             continue;
0733         }
0734         QDir toRemove(m_globalDir.filePath(folder));
0735         toRemove.removeRecursively();
0736     }
0737     updateGlobalInfo();
0738 }
0739 
0740 void TemporaryData::deleteProxy()
0741 {
0742     QDir proxies(m_globalDir.absoluteFilePath(QStringLiteral("proxy")));
0743     if (proxies.dirName() != QLatin1String("proxy")) {
0744         return;
0745     }
0746     if (KMessageBox::warningContinueCancel(this, i18n("Delete the proxy folder\n%1\nContains proxy clips for all your projects.", proxies.absolutePath())) !=
0747         KMessageBox::Continue) {
0748         return;
0749     }
0750     QDir toRemove(m_globalDir.filePath(QStringLiteral("proxy")));
0751     toRemove.removeRecursively();
0752     // We deleted proxy folder, recreate it
0753     toRemove.mkpath(QStringLiteral("."));
0754     processProxyDirectory();
0755 }
0756 
0757 void TemporaryData::cleanProxy()
0758 {
0759     QDir proxies(m_globalDir.absoluteFilePath(QStringLiteral("proxy")));
0760     if (proxies.dirName() != QLatin1String("proxy")) {
0761         return;
0762     }
0763     QFileInfoList files = proxies.entryInfoList(QDir::Files, QDir::Time);
0764     QStringList oldFiles;
0765     QDateTime current = QDateTime::currentDateTime();
0766     size_t size = 0;
0767     for (const QFileInfo &f : qAsConst(files)) {
0768         if (f.lastModified().addMonths(KdenliveSettings::cleanCacheMonths()) < current) {
0769             oldFiles << f.fileName();
0770             size += size_t(f.size());
0771         }
0772     }
0773     if (oldFiles.isEmpty()) {
0774         KMessageBox::information(this, i18n("No proxy clip older than %1 months found.", KdenliveSettings::cleanCacheMonths()));
0775         return;
0776     }
0777     if (KMessageBox::warningContinueCancelList(
0778             this, i18n("Delete the following proxy clips (%1)\nProxy clips can be recreated on project opening.", KIO::convertSize(size)), oldFiles) !=
0779         KMessageBox::Continue) {
0780         return;
0781     }
0782     for (const QString &f : qAsConst(oldFiles)) {
0783         proxies.remove(f);
0784     }
0785     processProxyDirectory();
0786 }
0787 
0788 void TemporaryData::slotCleanUp()
0789 {
0790     cleanCache();
0791     cleanBackup();
0792 }