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"