1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2006 Jacob R Rideout <kde@jacobrideout.net>
5 SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shafff@ukr.net>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kautosavefile.h"
11
12#include <climits> // for NAME_MAX
13
14#ifdef Q_OS_WIN
15#include <stdlib.h> // for _MAX_FNAME
16static const int maxNameLength = _MAX_FNAME;
17#else
18static const int maxNameLength = NAME_MAX;
19#endif
20
21#include "kcoreaddons_debug.h"
22#include "krandom.h"
23#include <QCoreApplication>
24#include <QDir>
25#include <QLatin1Char>
26#include <QLockFile>
27#include <QStandardPaths>
28
29class KAutoSaveFilePrivate
30{
31public:
32 enum {
33 NamePadding = 8,
34 };
35
36 QString tempFileName();
37 QUrl managedFile;
38 QLockFile *lock = nullptr;
39 bool managedFileNameChanged = false;
40};
41
42static QStringList findAllStales(const QString &appName)
43{
44 const QStringList dirs = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
45 QStringList files;
46
47 const QString suffix = QLatin1String("/stalefiles/") + appName;
48 for (const QString &dir : dirs) {
49 QDir appDir(dir + suffix);
50 const QString absPath = appDir.absolutePath() + QLatin1Char('/');
51 qCDebug(KCOREADDONS_DEBUG) << "Looking in" << appDir.absolutePath();
52 QStringList listFiles = appDir.entryList(filters: QDir::Files);
53 for (QString &file : listFiles) {
54 file.prepend(s: absPath);
55 }
56 files += listFiles;
57 }
58 return files;
59}
60
61QString KAutoSaveFilePrivate::tempFileName()
62{
63 // Note: we drop any query string and user/pass info
64 const QString protocol(managedFile.scheme());
65 const QByteArray encodedDirectory = QUrl::toPercentEncoding(managedFile.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
66 const QString directory = QString::fromLatin1(ba: encodedDirectory);
67 const QByteArray encodedFileName = QUrl::toPercentEncoding(managedFile.fileName());
68 QString fileName = QString::fromLatin1(ba: encodedFileName);
69
70 // Remove any part of the path to the right if it is longer than the maximum file name length;
71 // note that "file name" in this context means the file name component only (e.g. test.txt), and
72 // not the whole path (e.g. /home/simba/text.txt).
73 // Ensure that the max. file name length takes into account the other parts of the tempFileName
74 // Subtract 1 for the _ char, 3 for the padding separator, 5 is for the .lock,
75 // 7 for QLockFile's internal code (adding tmp .rmlock) = 16
76 const int pathLengthLimit = maxNameLength - NamePadding - fileName.size() - protocol.size() - 16;
77
78 const QString junk = KRandom::randomString(length: NamePadding);
79 // This is done so that the separation between the filename and path can be determined
80 fileName += QStringView(junk).right(n: 3) + protocol + QLatin1Char('_') + QStringView(directory).left(n: pathLengthLimit) + junk;
81
82 return fileName;
83}
84
85KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent)
86 : QFile(parent)
87 , d(new KAutoSaveFilePrivate)
88{
89 setManagedFile(filename);
90}
91
92KAutoSaveFile::KAutoSaveFile(QObject *parent)
93 : QFile(parent)
94 , d(new KAutoSaveFilePrivate)
95{
96}
97
98KAutoSaveFile::~KAutoSaveFile()
99{
100 releaseLock();
101 delete d->lock;
102}
103
104QUrl KAutoSaveFile::managedFile() const
105{
106 return d->managedFile;
107}
108
109void KAutoSaveFile::setManagedFile(const QUrl &filename)
110{
111 releaseLock();
112
113 d->managedFile = filename;
114 d->managedFileNameChanged = true;
115}
116
117void KAutoSaveFile::releaseLock()
118{
119 if (d->lock && d->lock->isLocked()) {
120 delete d->lock;
121 d->lock = nullptr;
122 if (!fileName().isEmpty()) {
123 remove();
124 }
125 }
126}
127
128bool KAutoSaveFile::open(OpenMode openmode)
129{
130 if (d->managedFile.isEmpty()) {
131 return false;
132 }
133
134 QString tempFile;
135 if (d->managedFileNameChanged) {
136 QString staleFilesDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/stalefiles/")
137 + QCoreApplication::instance()->applicationName();
138 if (!QDir().mkpath(dirPath: staleFilesDir)) {
139 return false;
140 }
141 tempFile = staleFilesDir + QChar::fromLatin1(c: '/') + d->tempFileName();
142 } else {
143 tempFile = fileName();
144 }
145
146 d->managedFileNameChanged = false;
147
148 setFileName(tempFile);
149
150 if (QFile::open(flags: openmode)) {
151 if (!d->lock) {
152 d->lock = new QLockFile(tempFile + QLatin1String(".lock"));
153 d->lock->setStaleLockTime(60 * 1000); // HARDCODE, 1 minute
154 }
155
156 if (d->lock->isLocked() || d->lock->tryLock()) {
157 return true;
158 } else {
159 qCWarning(KCOREADDONS_DEBUG) << "Could not lock file:" << tempFile;
160 close();
161 }
162 }
163
164 return false;
165}
166
167static QUrl extractManagedFilePath(const QString &staleFileName)
168{
169 const QStringView stale{staleFileName};
170 // Warning, if we had a long path, it was truncated by tempFileName()
171 // So in that case, extractManagedFilePath will return an incorrect truncated path for original source
172 const auto sep = stale.right(n: 3);
173 const int sepPos = staleFileName.indexOf(s: sep);
174 const QByteArray managedFilename = stale.left(n: sepPos).toLatin1();
175
176 const int pathPos = staleFileName.indexOf(c: QChar::fromLatin1(c: '_'), from: sepPos);
177 QUrl managedFileName;
178 // name.setScheme(file.mid(sepPos + 3, pathPos - sep.size() - 3));
179 const QByteArray encodedPath = stale.mid(pos: pathPos + 1, n: staleFileName.length() - pathPos - 1 - KAutoSaveFilePrivate::NamePadding).toLatin1();
180 managedFileName.setPath(path: QUrl::fromPercentEncoding(encodedPath) + QLatin1Char('/') + QFileInfo(QUrl::fromPercentEncoding(managedFilename)).fileName());
181 return managedFileName;
182}
183
184bool staleMatchesManaged(const QString &staleFileName, const QUrl &managedFile)
185{
186 const QStringView stale{staleFileName};
187 const auto sep = stale.right(n: 3);
188 int sepPos = staleFileName.indexOf(s: sep);
189 // Check filenames first
190 if (managedFile.fileName() != QUrl::fromPercentEncoding(stale.left(n: sepPos).toLatin1())) {
191 return false;
192 }
193 // Check paths
194 const int pathPos = staleFileName.indexOf(c: QChar::fromLatin1(c: '_'), from: sepPos);
195 const QByteArray encodedPath = stale.mid(pos: pathPos + 1, n: staleFileName.length() - pathPos - 1 - KAutoSaveFilePrivate::NamePadding).toLatin1();
196 return QUrl::toPercentEncoding(managedFile.path()).startsWith(bv: encodedPath);
197}
198
199QList<KAutoSaveFile *> KAutoSaveFile::staleFiles(const QUrl &filename, const QString &applicationName)
200{
201 QString appName(applicationName);
202 if (appName.isEmpty()) {
203 appName = QCoreApplication::instance()->applicationName();
204 }
205
206 // get stale files
207 const QStringList files = findAllStales(appName);
208
209 QList<KAutoSaveFile *> list;
210
211 // contruct a KAutoSaveFile for stale files corresponding given filename
212 for (const QString &file : files) {
213 if (file.endsWith(s: QLatin1String(".lock")) || (!filename.isEmpty() && !staleMatchesManaged(staleFileName: QFileInfo(file).fileName(), managedFile: filename))) {
214 continue;
215 }
216
217 // sets managedFile
218 KAutoSaveFile *asFile = new KAutoSaveFile(filename.isEmpty() ? extractManagedFilePath(staleFileName: file) : filename);
219 asFile->setFileName(file);
220 asFile->d->managedFileNameChanged = false; // do not regenerate tempfile name
221 list.append(t: asFile);
222 }
223
224 return list;
225}
226
227QList<KAutoSaveFile *> KAutoSaveFile::allStaleFiles(const QString &applicationName)
228{
229 return staleFiles(filename: QUrl(), applicationName);
230}
231
232#include "moc_kautosavefile.cpp"
233

source code of kcoreaddons/src/lib/io/kautosavefile.cpp