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 |
16 | static const int maxNameLength = _MAX_FNAME; |
17 | #else |
18 | static 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 | |
29 | class KAutoSaveFilePrivate |
30 | { |
31 | public: |
32 | enum { |
33 | NamePadding = 8, |
34 | }; |
35 | |
36 | QString tempFileName(); |
37 | QUrl managedFile; |
38 | QLockFile *lock = nullptr; |
39 | bool managedFileNameChanged = false; |
40 | }; |
41 | |
42 | static 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 | |
61 | QString 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 | |
85 | KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent) |
86 | : QFile(parent) |
87 | , d(new KAutoSaveFilePrivate) |
88 | { |
89 | setManagedFile(filename); |
90 | } |
91 | |
92 | KAutoSaveFile::KAutoSaveFile(QObject *parent) |
93 | : QFile(parent) |
94 | , d(new KAutoSaveFilePrivate) |
95 | { |
96 | } |
97 | |
98 | KAutoSaveFile::~KAutoSaveFile() |
99 | { |
100 | releaseLock(); |
101 | delete d->lock; |
102 | } |
103 | |
104 | QUrl KAutoSaveFile::managedFile() const |
105 | { |
106 | return d->managedFile; |
107 | } |
108 | |
109 | void KAutoSaveFile::setManagedFile(const QUrl &filename) |
110 | { |
111 | releaseLock(); |
112 | |
113 | d->managedFile = filename; |
114 | d->managedFileNameChanged = true; |
115 | } |
116 | |
117 | void 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 | |
128 | bool 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 | |
167 | static QUrl (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 | |
184 | bool 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 | |
199 | QList<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 | |
227 | QList<KAutoSaveFile *> KAutoSaveFile::allStaleFiles(const QString &applicationName) |
228 | { |
229 | return staleFiles(filename: QUrl(), applicationName); |
230 | } |
231 | |
232 | #include "moc_kautosavefile.cpp" |
233 | |