File indexing completed on 2025-01-05 04:29:55
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 }