1 /*****************************************************************************
2 * Copyright (C) 2003 Shie Erlich <erlich@users.sourceforge.net> *
3 * Copyright (C) 2003 Rafi Yanai <yanai@users.sourceforge.net> *
4 * Copyright (C) 2004-2019 Krusader Krew [https://krusader.org] *
5 * *
6 * This file is part of Krusader [https://krusader.org]. *
7 * *
8 * Krusader is free software: you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation, either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * Krusader is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
20 *****************************************************************************/
21
22 #include "virtualfilesystem.h"
23
24 // QtCore
25 #include <QDir>
26 #include <QEventLoop>
27 #include <QUrl>
28 // QtWidgets
29 #include <QApplication>
30
31 #include <KCoreAddons/KUrlMimeData>
32 #include <KI18n/KLocalizedString>
33 #include <KIO/CopyJob>
34 #include <KIO/DeleteJob>
35 #include <KIO/DirectorySizeJob>
36 #include <KIO/StatJob>
37 #include <KIOCore/KFileItem>
38 #include <KWidgetsAddons/KMessageBox>
39
40 #include "fileitem.h"
41 #include "../defaults.h"
42 #include "../krglobal.h"
43 #include "../krservices.h"
44
45 #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db"
46
47 QHash<QString, QList<QUrl> *> VirtualFileSystem::_virtFilesystemDict;
48 QHash<QString, QString> VirtualFileSystem::_metaInfoDict;
49
VirtualFileSystem()50 VirtualFileSystem::VirtualFileSystem() : FileSystem()
51 {
52 if (_virtFilesystemDict.isEmpty()) {
53 restore();
54 }
55
56 _type = FS_VIRTUAL;
57 }
58
copyFiles(const QList<QUrl> & urls,const QUrl & destination,KIO::CopyJob::CopyMode,bool,JobMan::StartMode)59 void VirtualFileSystem::copyFiles(const QList<QUrl> &urls, const QUrl &destination,
60 KIO::CopyJob::CopyMode /*mode*/, bool /*showProgressInfo*/,
61 JobMan::StartMode /*startMode*/)
62 {
63 const QString dir = QDir(destination.path()).absolutePath().remove('/');
64
65 if (dir.isEmpty()) {
66 showError(i18n("You cannot copy files directly to the 'virt:/' folder.\n"
67 "You can create a sub folder and copy your files into it."));
68 return;
69 }
70
71 if (!_virtFilesystemDict.contains(dir)) {
72 mkDirInternal(dir);
73 }
74
75 QList<QUrl> *urlList = _virtFilesystemDict[dir];
76 for (const QUrl &fileUrl : urls) {
77 if (!urlList->contains(fileUrl)) {
78 urlList->push_back(fileUrl);
79 }
80 }
81
82 emit fileSystemChanged(QUrl("virt:///" + dir), false); // may call refresh()
83 }
84
dropFiles(const QUrl & destination,QDropEvent * event)85 void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event)
86 {
87 const QList<QUrl> &urls = KUrlMimeData::urlsFromMimeData(event->mimeData());
88 // dropping on virtual filesystem is always copy operation
89 copyFiles(urls, destination);
90 }
91
addFiles(const QList<QUrl> & fileUrls,KIO::CopyJob::CopyMode,const QString & dir)92 void VirtualFileSystem::addFiles(const QList<QUrl> &fileUrls, KIO::CopyJob::CopyMode /*mode*/,
93 const QString &dir)
94 {
95 QUrl destination(_currentDirectory);
96 if (!dir.isEmpty()) {
97 destination.setPath(QDir::cleanPath(destination.path() + '/' + dir));
98 }
99 copyFiles(fileUrls, destination);
100 }
101
remove(const QStringList & fileNames)102 void VirtualFileSystem::remove(const QStringList &fileNames)
103 {
104 const QString parentDir = currentDir();
105 if (parentDir == "/") { // remove virtual directory
106 for (const QString &filename : fileNames) {
107 _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename));
108 delete _virtFilesystemDict[filename];
109 _virtFilesystemDict.remove(filename);
110 _metaInfoDict.remove(filename);
111 }
112 } else {
113 // remove the URLs from the collection
114 for (const QString name : fileNames) {
115 if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) {
116 QList<QUrl> *urlList = _virtFilesystemDict[parentDir];
117 urlList->removeAll(getUrl(name));
118 }
119 }
120 }
121
122 emit fileSystemChanged(currentDirectory(), true); // will call refresh()
123 }
124
getUrl(const QString & name) const125 QUrl VirtualFileSystem::getUrl(const QString &name) const
126 {
127 FileItem *item = getFileItem(name);
128 if (!item) {
129 return QUrl(); // not found
130 }
131
132 return item->getUrl();
133 }
134
mkDir(const QString & name)135 void VirtualFileSystem::mkDir(const QString &name)
136 {
137 if (currentDir() != "/") {
138 showError(i18n("Creating new folders is allowed only in the 'virt:/' folder."));
139 return;
140 }
141
142 mkDirInternal(name);
143
144 emit fileSystemChanged(currentDirectory(), false); // will call refresh()
145 }
146
rename(const QString & fileName,const QString & newName)147 void VirtualFileSystem::rename(const QString &fileName, const QString &newName)
148 {
149 FileItem *item = getFileItem(fileName);
150 if (!item)
151 return; // not found
152
153 if (currentDir() == "/") { // rename virtual directory
154 _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName));
155 _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName));
156 _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName));
157 refresh();
158 return;
159 }
160
161 // newName can be a (local) path or a full url
162 QUrl dest(newName);
163 if (dest.scheme().isEmpty())
164 dest.setScheme("file");
165
166 // add the new url to the list
167 // the list is refreshed, only existing files remain -
168 // so we don't have to worry if the job was successful
169 _virtFilesystemDict[currentDir()]->append(dest);
170
171 KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo);
172 connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); });
173 connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(currentDirectory(), false); });
174 }
175
canMoveToTrash(const QStringList & fileNames) const176 bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const
177 {
178 if (isRoot())
179 return false;
180
181 for (const QString fileName : fileNames) {
182 if (!getUrl(fileName).isLocalFile()) {
183 return false;
184 }
185 }
186 return true;
187 }
188
setMetaInformation(const QString & info)189 void VirtualFileSystem::setMetaInformation(const QString &info)
190 {
191 _metaInfoDict[currentDir()] = info;
192 }
193
194 // ==== protected ====
195
refreshInternal(const QUrl & directory,bool onlyScan)196 bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool onlyScan)
197 {
198 _currentDirectory = cleanUrl(directory);
199 _currentDirectory.setHost("");
200 // remove invalid subdirectories
201 _currentDirectory.setPath('/' + _currentDirectory.path().remove('/'));
202
203 if (!_virtFilesystemDict.contains(currentDir())) {
204 if (onlyScan) {
205 return false; // virtual dir does not exist
206 } else {
207 // Silently creating non-existing directories here. The search and locate tools
208 // expect this. And the user can enter some directory and it will be created.
209 mkDirInternal(currentDir());
210 save();
211 // infinite loop possible
212 // emit fileSystemChanged(currentDirectory());
213 return true;
214 }
215 }
216
217 QList<QUrl> *urlList = _virtFilesystemDict[currentDir()];
218
219 if (!onlyScan) {
220 const QString metaInfo = _metaInfoDict[currentDir()];
221 emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo,
222 "", 0, 0);
223 }
224
225 QMutableListIterator<QUrl> it(*urlList);
226 while (it.hasNext()) {
227 const QUrl url = it.next();
228 FileItem *item = createFileItem(url);
229 if (!item) { // remove URL from the list for a file that no longer exists
230 it.remove();
231 } else {
232 addFileItem(item);
233 }
234 }
235
236 save();
237 return true;
238 }
239
240 // ==== private ====
241
mkDirInternal(const QString & name)242 void VirtualFileSystem::mkDirInternal(const QString &name)
243 {
244 // clean path, consistent with currentDir()
245 QString dirName = name;
246 dirName = dirName.remove('/');
247 if (dirName.isEmpty())
248 dirName = '/';
249
250 _virtFilesystemDict.insert(dirName, new QList<QUrl>());
251 _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName));
252 }
253
save()254 void VirtualFileSystem::save()
255 {
256 KConfig *db = &VirtualFileSystem::getVirtDB();
257 db->deleteGroup("virt_db");
258 KConfigGroup group(db, "virt_db");
259
260 QHashIterator<QString, QList<QUrl> *> it(_virtFilesystemDict);
261 while (it.hasNext()) {
262 it.next();
263 QList<QUrl> *urlList = it.value();
264
265 QList<QUrl>::iterator url;
266 QStringList entry;
267 for (url = urlList->begin(); url != urlList->end(); ++url) {
268 entry.append((*url).toDisplayString());
269 }
270 // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /)
271 group.writeEntry("Item_" + it.key(), entry);
272 group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]);
273 }
274
275 db->sync();
276 }
277
restore()278 void VirtualFileSystem::restore()
279 {
280 KConfig *db = &VirtualFileSystem::getVirtDB();
281 const KConfigGroup dbGrp(db, "virt_db");
282
283 const QMap<QString, QString> map = db->entryMap("virt_db");
284 QMapIterator<QString, QString> it(map);
285 while (it.hasNext()) {
286 it.next();
287
288 // KDE 4.0 workaround: check and remove 'Item_' prefix
289 if (!it.key().startsWith(QLatin1String("Item_")))
290 continue;
291 const QString key = it.key().mid(5);
292
293 const QList<QUrl> urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList()));
294 _virtFilesystemDict.insert(key, new QList<QUrl>(urlList));
295 _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString()));
296 }
297
298 if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason
299 _virtFilesystemDict.insert("/", new QList<QUrl>());
300 }
301 }
302
createFileItem(const QUrl & url)303 FileItem *VirtualFileSystem::createFileItem(const QUrl &url)
304 {
305 if (url.scheme() == "virt") { // return a virtual directory in root
306 QString path = url.path().mid(1);
307 if (path.isEmpty())
308 path = '/';
309 return FileItem::createVirtualDir(path, url);
310 }
311
312 const QUrl directory = url.adjusted(QUrl::RemoveFilename);
313
314 if (url.isLocalFile()) {
315 QFileInfo file(url.path());
316 return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : 0;
317 }
318
319 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
320 connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult);
321
322 // ugly: we have to wait here until the stat job is finished
323 QEventLoop eventLoop;
324 connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit);
325 eventLoop.exec(); // blocking until quit()
326
327 if (_fileEntry.count() == 0) {
328 return 0; // stat job failed
329 }
330
331 if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) {
332 // TODO this also happens for FTP directories
333 return 0; // file not found
334 }
335
336 return FileSystem::createFileItemFromKIO(_fileEntry, directory, true);
337 }
338
getVirtDB()339 KConfig &VirtualFileSystem::getVirtDB()
340 {
341 //virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals);
342 static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
343 return db;
344 }
345
slotStatResult(KJob * job)346 void VirtualFileSystem::slotStatResult(KJob *job)
347 {
348 _fileEntry = job->error() ? KIO::UDSEntry() : static_cast<KIO::StatJob *>(job)->statResult();
349 }
350
showError(const QString & error)351 void VirtualFileSystem::showError(const QString &error)
352 {
353 QWidget *window = QApplication::activeWindow();
354 KMessageBox::sorry(window, error); // window can be null, is allowed
355 }
356