File indexing completed on 2024-05-19 03:56:19

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     SPDX-FileCopyrightText: 2006 Jacob R Rideout <kde@jacobrideout.net>
0005     SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shafff@ukr.net>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kautosavefile.h"
0011 
0012 #include <climits> // for NAME_MAX
0013 
0014 #ifdef Q_OS_WIN
0015 #include <stdlib.h> // for _MAX_FNAME
0016 static const int maxNameLength = _MAX_FNAME;
0017 #else
0018 static const int maxNameLength = NAME_MAX;
0019 #endif
0020 
0021 #include "kcoreaddons_debug.h"
0022 #include "krandom.h"
0023 #include <QCoreApplication>
0024 #include <QDir>
0025 #include <QLatin1Char>
0026 #include <QLockFile>
0027 #include <QStandardPaths>
0028 
0029 class KAutoSaveFilePrivate
0030 {
0031 public:
0032     enum {
0033         NamePadding = 8,
0034     };
0035 
0036     QString tempFileName();
0037     QUrl managedFile;
0038     QLockFile *lock = nullptr;
0039     bool managedFileNameChanged = false;
0040 };
0041 
0042 static QStringList findAllStales(const QString &appName)
0043 {
0044     const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0045     QStringList files;
0046 
0047     const QString suffix = QLatin1String("/stalefiles/") + appName;
0048     for (const QString &dir : dirs) {
0049         QDir appDir(dir + suffix);
0050         const QString absPath = appDir.absolutePath() + QLatin1Char('/');
0051         qCDebug(KCOREADDONS_DEBUG) << "Looking in" << appDir.absolutePath();
0052         QStringList listFiles = appDir.entryList(QDir::Files);
0053         for (QString &file : listFiles) {
0054             file.prepend(absPath);
0055         }
0056         files += listFiles;
0057     }
0058     return files;
0059 }
0060 
0061 QString KAutoSaveFilePrivate::tempFileName()
0062 {
0063     // Note: we drop any query string and user/pass info
0064     const QString protocol(managedFile.scheme());
0065     const QByteArray encodedDirectory = QUrl::toPercentEncoding(managedFile.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
0066     const QString directory = QString::fromLatin1(encodedDirectory);
0067     const QByteArray encodedFileName = QUrl::toPercentEncoding(managedFile.fileName());
0068     QString fileName = QString::fromLatin1(encodedFileName);
0069 
0070     // Remove any part of the path to the right if it is longer than the maximum file name length;
0071     // note that "file name" in this context means the file name component only (e.g. test.txt), and
0072     // not the whole path (e.g. /home/simba/text.txt).
0073     // Ensure that the max. file name length takes into account the other parts of the tempFileName
0074     // Subtract 1 for the _ char, 3 for the padding separator, 5 is for the .lock,
0075     // 7 for QLockFile's internal code (adding tmp .rmlock) = 16
0076     const int pathLengthLimit = maxNameLength - NamePadding - fileName.size() - protocol.size() - 16;
0077 
0078     const QString junk = KRandom::randomString(NamePadding);
0079     // This is done so that the separation between the filename and path can be determined
0080     fileName += QStringView(junk).right(3) + protocol + QLatin1Char('_') + QStringView(directory).left(pathLengthLimit) + junk;
0081 
0082     return fileName;
0083 }
0084 
0085 KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent)
0086     : QFile(parent)
0087     , d(new KAutoSaveFilePrivate)
0088 {
0089     setManagedFile(filename);
0090 }
0091 
0092 KAutoSaveFile::KAutoSaveFile(QObject *parent)
0093     : QFile(parent)
0094     , d(new KAutoSaveFilePrivate)
0095 {
0096 }
0097 
0098 KAutoSaveFile::~KAutoSaveFile()
0099 {
0100     releaseLock();
0101     delete d->lock;
0102 }
0103 
0104 QUrl KAutoSaveFile::managedFile() const
0105 {
0106     return d->managedFile;
0107 }
0108 
0109 void KAutoSaveFile::setManagedFile(const QUrl &filename)
0110 {
0111     releaseLock();
0112 
0113     d->managedFile = filename;
0114     d->managedFileNameChanged = true;
0115 }
0116 
0117 void KAutoSaveFile::releaseLock()
0118 {
0119     if (d->lock && d->lock->isLocked()) {
0120         delete d->lock;
0121         d->lock = nullptr;
0122         if (!fileName().isEmpty()) {
0123             remove();
0124         }
0125     }
0126 }
0127 
0128 bool KAutoSaveFile::open(OpenMode openmode)
0129 {
0130     if (d->managedFile.isEmpty()) {
0131         return false;
0132     }
0133 
0134     QString tempFile;
0135     if (d->managedFileNameChanged) {
0136         QString staleFilesDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/stalefiles/")
0137             + QCoreApplication::instance()->applicationName();
0138         if (!QDir().mkpath(staleFilesDir)) {
0139             return false;
0140         }
0141         tempFile = staleFilesDir + QChar::fromLatin1('/') + d->tempFileName();
0142     } else {
0143         tempFile = fileName();
0144     }
0145 
0146     d->managedFileNameChanged = false;
0147 
0148     setFileName(tempFile);
0149 
0150     if (QFile::open(openmode)) {
0151         if (!d->lock) {
0152             d->lock = new QLockFile(tempFile + QLatin1String(".lock"));
0153             d->lock->setStaleLockTime(60 * 1000); // HARDCODE, 1 minute
0154         }
0155 
0156         if (d->lock->isLocked() || d->lock->tryLock()) {
0157             return true;
0158         } else {
0159             qCWarning(KCOREADDONS_DEBUG) << "Could not lock file:" << tempFile;
0160             close();
0161         }
0162     }
0163 
0164     return false;
0165 }
0166 
0167 static QUrl extractManagedFilePath(const QString &staleFileName)
0168 {
0169     const QStringView stale{staleFileName};
0170     // Warning, if we had a long path, it was truncated by tempFileName()
0171     // So in that case, extractManagedFilePath will return an incorrect truncated path for original source
0172     const auto sep = stale.right(3);
0173     const int sepPos = staleFileName.indexOf(sep);
0174     const QByteArray managedFilename = stale.left(sepPos).toLatin1();
0175 
0176     const int pathPos = staleFileName.indexOf(QChar::fromLatin1('_'), sepPos);
0177     QUrl managedFileName;
0178     // name.setScheme(file.mid(sepPos + 3, pathPos - sep.size() - 3));
0179     const QByteArray encodedPath = stale.mid(pathPos + 1, staleFileName.length() - pathPos - 1 - KAutoSaveFilePrivate::NamePadding).toLatin1();
0180     managedFileName.setPath(QUrl::fromPercentEncoding(encodedPath) + QLatin1Char('/') + QFileInfo(QUrl::fromPercentEncoding(managedFilename)).fileName());
0181     return managedFileName;
0182 }
0183 
0184 bool staleMatchesManaged(const QString &staleFileName, const QUrl &managedFile)
0185 {
0186     const QStringView stale{staleFileName};
0187     const auto sep = stale.right(3);
0188     int sepPos = staleFileName.indexOf(sep);
0189     // Check filenames first
0190     if (managedFile.fileName() != QUrl::fromPercentEncoding(stale.left(sepPos).toLatin1())) {
0191         return false;
0192     }
0193     // Check paths
0194     const int pathPos = staleFileName.indexOf(QChar::fromLatin1('_'), sepPos);
0195     const QByteArray encodedPath = stale.mid(pathPos + 1, staleFileName.length() - pathPos - 1 - KAutoSaveFilePrivate::NamePadding).toLatin1();
0196     return QUrl::toPercentEncoding(managedFile.path()).startsWith(encodedPath);
0197 }
0198 
0199 QList<KAutoSaveFile *> KAutoSaveFile::staleFiles(const QUrl &filename, const QString &applicationName)
0200 {
0201     QString appName(applicationName);
0202     if (appName.isEmpty()) {
0203         appName = QCoreApplication::instance()->applicationName();
0204     }
0205 
0206     // get stale files
0207     const QStringList files = findAllStales(appName);
0208 
0209     QList<KAutoSaveFile *> list;
0210 
0211     // contruct a KAutoSaveFile for stale files corresponding given filename
0212     for (const QString &file : files) {
0213         if (file.endsWith(QLatin1String(".lock")) || (!filename.isEmpty() && !staleMatchesManaged(QFileInfo(file).fileName(), filename))) {
0214             continue;
0215         }
0216 
0217         // sets managedFile
0218         KAutoSaveFile *asFile = new KAutoSaveFile(filename.isEmpty() ? extractManagedFilePath(file) : filename);
0219         asFile->setFileName(file);
0220         asFile->d->managedFileNameChanged = false; // do not regenerate tempfile name
0221         list.append(asFile);
0222     }
0223 
0224     return list;
0225 }
0226 
0227 QList<KAutoSaveFile *> KAutoSaveFile::allStaleFiles(const QString &applicationName)
0228 {
0229     return staleFiles(QUrl(), applicationName);
0230 }
0231 
0232 #include "moc_kautosavefile.cpp"