File indexing completed on 2024-05-12 16:21:31

0001 /**
0002  * SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "storagemanager.h"
0008 #include "storagemanagerlogging.h"
0009 
0010 #include <KLocalizedString>
0011 #include <QCryptographicHash>
0012 #include <QDateTime>
0013 #include <QDebug>
0014 #include <QDir>
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QRegularExpression>
0018 #include <QStandardPaths>
0019 
0020 #include "enclosure.h"
0021 #include "models/errorlogmodel.h"
0022 #include "settingsmanager.h"
0023 #include "storagemovejob.h"
0024 
0025 StorageManager::StorageManager()
0026 {
0027     connect(this, &StorageManager::error, &ErrorLogModel::instance(), &ErrorLogModel::monitorErrorMessages);
0028 }
0029 
0030 QString StorageManager::storagePath() const
0031 {
0032     QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0033 
0034     if (!SettingsManager::self()->storagePath().isEmpty()) {
0035         path = SettingsManager::self()->storagePath().toLocalFile();
0036     }
0037 
0038     // Create path if it doesn't exist yet
0039     QFileInfo().absoluteDir().mkpath(path);
0040 
0041     qCDebug(kastsStorageManager) << "Current storage path is" << path;
0042 
0043     return path;
0044 }
0045 
0046 void StorageManager::setStoragePath(QUrl url)
0047 {
0048     qCDebug(kastsStorageManager) << "New storage path url:" << url;
0049     QUrl oldUrl = SettingsManager::self()->storagePath();
0050     QString oldPath = storagePath();
0051     QString newPath = oldPath;
0052 
0053     if (url.isEmpty()) {
0054         qCDebug(kastsStorageManager) << "(Re)set storage path to default location";
0055         SettingsManager::self()->setStoragePath(url);
0056         newPath = storagePath(); // retrieve default storage path, since url is empty
0057     } else if (url.isLocalFile()) {
0058         SettingsManager::self()->setStoragePath(url);
0059         newPath = url.toLocalFile();
0060     } else {
0061         qCDebug(kastsStorageManager) << "Cannot set storage path; path is not on local filesystem:" << url;
0062         return;
0063     }
0064 
0065     qCDebug(kastsStorageManager) << "Current storage path in settings:" << SettingsManager::self()->storagePath();
0066     qCDebug(kastsStorageManager) << "New storage path will be:" << newPath;
0067 
0068     if (oldPath != newPath) {
0069         // make list of dirs to be moved (images/ and enclosures/*/)
0070         QStringList list = {QStringLiteral("images")};
0071         for (const QString &subdir : QDir(oldPath + QStringLiteral("/enclosures/")).entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
0072             list << QStringLiteral("enclosures/") + subdir;
0073         }
0074         list << QStringLiteral("enclosures");
0075 
0076         StorageMoveJob *moveJob = new StorageMoveJob(oldPath, newPath, list);
0077         connect(moveJob, &KJob::processedAmountChanged, this, [this, moveJob]() {
0078             m_storageMoveProgress = moveJob->processedAmount(KJob::Files);
0079             Q_EMIT storageMoveProgressChanged(m_storageMoveProgress);
0080         });
0081         connect(moveJob, &KJob::totalAmountChanged, this, [this, moveJob]() {
0082             m_storageMoveTotal = moveJob->totalAmount(KJob::Files);
0083             Q_EMIT storageMoveTotalChanged(m_storageMoveTotal);
0084         });
0085         connect(moveJob, &KJob::result, this, [=]() {
0086             if (moveJob->error() != 0) {
0087                 // Go back to previous old path
0088                 SettingsManager::self()->setStoragePath(oldUrl);
0089                 QString title =
0090                     i18n("Old location:") + QStringLiteral(" ") + oldPath + QStringLiteral("; ") + i18n("New location:") + QStringLiteral(" ") + newPath;
0091                 Q_EMIT error(Error::Type::StorageMoveError, QString(), QString(), moveJob->error(), moveJob->errorString(), title);
0092             }
0093             Q_EMIT storageMoveFinished();
0094             Q_EMIT storagePathChanged(newPath);
0095 
0096             // save settings now to avoid getting into an inconsistent app state
0097             SettingsManager::self()->save();
0098             disconnect(this, &StorageManager::cancelStorageMove, this, nullptr);
0099         });
0100         connect(this, &StorageManager::cancelStorageMove, this, [moveJob]() {
0101             moveJob->doKill();
0102         });
0103         Q_EMIT storageMoveStarted();
0104         moveJob->start();
0105     }
0106 }
0107 
0108 QString StorageManager::imageDirPath() const
0109 {
0110     QString path = storagePath() + QStringLiteral("/images/");
0111     // Create path if it doesn't exist yet
0112     QFileInfo().absoluteDir().mkpath(path);
0113     return path;
0114 }
0115 
0116 QString StorageManager::imagePath(const QString &url) const
0117 {
0118     return imageDirPath() + QString::fromStdString(QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5).toHex().toStdString());
0119 }
0120 
0121 QString StorageManager::enclosureDirPath() const
0122 {
0123     return enclosureDirPath(QStringLiteral(""));
0124 }
0125 
0126 QString StorageManager::enclosureDirPath(const QString &feedname) const
0127 {
0128     QString path = storagePath() + QStringLiteral("/enclosures/");
0129 
0130     if (!feedname.isEmpty()) {
0131         path += feedname + QStringLiteral("/");
0132     }
0133 
0134     // Create path if it doesn't exist yet
0135     QFileInfo().absoluteDir().mkpath(path);
0136     return path;
0137 }
0138 
0139 QString StorageManager::enclosurePath(const QString &name, const QString &url, const QString &feedname) const
0140 {
0141     // Generate filename based on episode name and url hash with feedname as subdirectory
0142     QString enclosureFilenameBase = sanitizedFilePath(name) + QStringLiteral(".")
0143         + QString::fromStdString(QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5).toHex().toStdString()).left(6);
0144 
0145     QString enclosureFilenameExt = QFileInfo(QUrl::fromUserInput(url).fileName()).suffix();
0146 
0147     QString enclosureFilename = !enclosureFilenameExt.isEmpty() ? enclosureFilenameBase + QStringLiteral(".") + enclosureFilenameExt : enclosureFilenameBase;
0148 
0149     return enclosureDirPath(feedname) + enclosureFilename;
0150 }
0151 
0152 qint64 StorageManager::dirSize(const QString &path) const
0153 {
0154     qint64 size = 0;
0155     QFileInfoList files = QDir(path).entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
0156 
0157     for (QFileInfo info : files) {
0158         if (info.isDir()) {
0159             size += dirSize(info.filePath());
0160         }
0161         size += info.size();
0162     }
0163 
0164     return size;
0165 }
0166 
0167 void StorageManager::removeImage(const QString &url)
0168 {
0169     qCDebug(kastsStorageManager) << "Removing image" << imagePath(url);
0170     QFile(imagePath(url)).remove();
0171     Q_EMIT imageDirSizeChanged();
0172 }
0173 
0174 void StorageManager::clearImageCache()
0175 {
0176     qCDebug(kastsStorageManager) << imageDirPath();
0177     QStringList images = QDir(imageDirPath()).entryList(QDir::Files);
0178     qCDebug(kastsStorageManager) << images;
0179     for (QString image : images) {
0180         qCDebug(kastsStorageManager) << image;
0181         QFile(QDir(imageDirPath()).absoluteFilePath(image)).remove();
0182     }
0183     Q_EMIT imageDirSizeChanged();
0184 }
0185 
0186 qint64 StorageManager::enclosureDirSize() const
0187 {
0188     return dirSize(enclosureDirPath());
0189 }
0190 
0191 qint64 StorageManager::imageDirSize() const
0192 {
0193     return dirSize(imageDirPath());
0194 }
0195 
0196 QString StorageManager::formattedEnclosureDirSize() const
0197 {
0198     return m_kformat.formatByteSize(enclosureDirSize());
0199 }
0200 
0201 QString StorageManager::formattedImageDirSize() const
0202 {
0203     return m_kformat.formatByteSize(imageDirSize());
0204 }
0205 
0206 QString StorageManager::passwordFilePath(const QString &username) const
0207 {
0208     return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/") + username;
0209 }
0210 
0211 QString StorageManager::sanitizedFilePath(const QString &path) const
0212 {
0213     // NOTE: Any changes here require a database migration!
0214 
0215     // Only keep alphanumeric ascii characters; this avoid any kind of issues
0216     // with the many types of filesystems out there.  Then remove excess whitespace
0217     // and limit the length of the string.
0218     QString newPath = path;
0219     newPath = newPath.remove(QRegularExpression(QStringLiteral("[^a-zA-Z0-9 ._()-]"))).simplified().left(maxFilenameLength);
0220 
0221     return newPath.isEmpty() ? QStringLiteral("Noname") : newPath;
0222 }