File indexing completed on 2024-05-05 17:56:57
0001 /* 0002 SPDX-FileCopyrightText: 2003 Shie Erlich <erlich@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2003 Rafi Yanai <yanai@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "virtualfilesystem.h" 0010 0011 // QtCore 0012 #include <QDir> 0013 #include <QEventLoop> 0014 #include <QUrl> 0015 // QtWidgets 0016 #include <QApplication> 0017 0018 #include <KCoreAddons/KUrlMimeData> 0019 #include <KI18n/KLocalizedString> 0020 #include <KIO/CopyJob> 0021 #include <KIO/DeleteJob> 0022 #include <KIO/DirectorySizeJob> 0023 #include <KIO/StatJob> 0024 #include <KIOCore/KFileItem> 0025 #include <KWidgetsAddons/KMessageBox> 0026 0027 #include "../defaults.h" 0028 #include "../krglobal.h" 0029 #include "../krservices.h" 0030 #include "fileitem.h" 0031 0032 #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db" 0033 0034 QHash<QString, QList<QUrl> *> VirtualFileSystem::_virtFilesystemDict; 0035 QHash<QString, QString> VirtualFileSystem::_metaInfoDict; 0036 0037 VirtualFileSystem::VirtualFileSystem() 0038 { 0039 if (_virtFilesystemDict.isEmpty()) { 0040 restore(); 0041 } 0042 0043 _type = FS_VIRTUAL; 0044 } 0045 0046 void VirtualFileSystem::copyFiles(const QList<QUrl> &urls, 0047 const QUrl &destination, 0048 KIO::CopyJob::CopyMode /*mode*/, 0049 bool /*showProgressInfo*/, 0050 JobMan::StartMode /*startMode*/) 0051 { 0052 const QString dir = QDir(destination.path()).absolutePath().remove('/'); 0053 0054 if (dir.isEmpty()) { 0055 showError( 0056 i18n("You cannot copy files directly to the 'virt:/' folder.\n" 0057 "You can create a sub folder and copy your files into it.")); 0058 return; 0059 } 0060 0061 if (!_virtFilesystemDict.contains(dir)) { 0062 mkDirInternal(dir); 0063 } 0064 0065 QList<QUrl> *urlList = _virtFilesystemDict[dir]; 0066 for (const QUrl &fileUrl : urls) { 0067 if (!urlList->contains(fileUrl)) { 0068 urlList->push_back(fileUrl); 0069 } 0070 } 0071 0072 emit fileSystemChanged(QUrl("virt:///" + dir), false); // may call refresh() 0073 } 0074 0075 void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) 0076 { 0077 const QList<QUrl> &urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); 0078 // dropping on virtual filesystem is always copy operation 0079 copyFiles(urls, destination); 0080 } 0081 0082 void VirtualFileSystem::addFiles(const QList<QUrl> &fileUrls, KIO::CopyJob::CopyMode /*mode*/, const QString &dir) 0083 { 0084 QUrl destination(_currentDirectory); 0085 if (!dir.isEmpty()) { 0086 destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); 0087 } 0088 copyFiles(fileUrls, destination); 0089 } 0090 0091 void VirtualFileSystem::remove(const QStringList &fileNames) 0092 { 0093 const QString parentDir = currentDir(); 0094 if (parentDir == "/") { // remove virtual directory 0095 for (const QString &filename : fileNames) { 0096 _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename)); 0097 delete _virtFilesystemDict[filename]; 0098 _virtFilesystemDict.remove(filename); 0099 _metaInfoDict.remove(filename); 0100 } 0101 } else { 0102 // remove the URLs from the collection 0103 for (const QString &name : fileNames) { 0104 if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) { 0105 QList<QUrl> *urlList = _virtFilesystemDict[parentDir]; 0106 urlList->removeAll(getUrl(name)); 0107 } 0108 } 0109 } 0110 0111 emit fileSystemChanged(currentDirectory(), true); // will call refresh() 0112 } 0113 0114 QUrl VirtualFileSystem::getUrl(const QString &name) const 0115 { 0116 FileItem *item = getFileItem(name); 0117 if (!item) { 0118 return QUrl(); // not found 0119 } 0120 0121 return item->getUrl(); 0122 } 0123 0124 void VirtualFileSystem::mkDir(const QString &name) 0125 { 0126 if (currentDir() != "/") { 0127 showError(i18n("Creating new folders is allowed only in the 'virt:/' folder.")); 0128 return; 0129 } 0130 0131 mkDirInternal(name); 0132 0133 emit fileSystemChanged(currentDirectory(), false); // will call refresh() 0134 } 0135 0136 void VirtualFileSystem::rename(const QString &fileName, const QString &newName) 0137 { 0138 FileItem *item = getFileItem(fileName); 0139 if (!item) 0140 return; // not found 0141 0142 if (currentDir() == "/") { // rename virtual directory 0143 _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName)); 0144 _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName)); 0145 _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName)); 0146 refresh(); 0147 return; 0148 } 0149 0150 // newName can be a (local) path or a full url 0151 QUrl dest(newName); 0152 if (dest.scheme().isEmpty()) 0153 dest.setScheme("file"); 0154 0155 // add the new url to the list 0156 // the list is refreshed, only existing files remain - 0157 // so we don't have to worry if the job was successful 0158 _virtFilesystemDict[currentDir()]->append(dest); 0159 0160 KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo); 0161 connect(job, &KIO::Job::result, this, [=](KJob *job) { 0162 slotJobResult(job, false); 0163 }); 0164 connect(job, &KIO::Job::result, this, [=]() { 0165 emit fileSystemChanged(currentDirectory(), false); 0166 }); 0167 } 0168 0169 bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const 0170 { 0171 if (isRoot()) 0172 return false; 0173 0174 for (const QString &fileName : fileNames) { 0175 if (!getUrl(fileName).isLocalFile()) { 0176 return false; 0177 } 0178 } 0179 return true; 0180 } 0181 0182 void VirtualFileSystem::setMetaInformation(const QString &info) 0183 { 0184 _metaInfoDict[currentDir()] = info; 0185 } 0186 0187 // ==== protected ==== 0188 0189 bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) 0190 { 0191 _currentDirectory = cleanUrl(directory); 0192 _currentDirectory.setHost(""); 0193 // remove invalid subdirectories 0194 _currentDirectory.setPath('/' + _currentDirectory.path().remove('/')); 0195 0196 if (!_virtFilesystemDict.contains(currentDir())) { 0197 if (onlyScan) { 0198 return false; // virtual dir does not exist 0199 } else { 0200 // Silently creating non-existing directories here. The search and locate tools 0201 // expect this. And the user can enter some directory and it will be created. 0202 mkDirInternal(currentDir()); 0203 save(); 0204 // infinite loop possible 0205 // emit fileSystemChanged(currentDirectory()); 0206 return true; 0207 } 0208 } 0209 0210 QList<QUrl> *urlList = _virtFilesystemDict[currentDir()]; 0211 0212 if (!onlyScan) { 0213 const QString metaInfo = _metaInfoDict[currentDir()]; 0214 emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, "", 0, 0); 0215 } 0216 0217 QMutableListIterator<QUrl> it(*urlList); 0218 while (it.hasNext()) { 0219 const QUrl url = it.next(); 0220 FileItem *item = createFileItem(url); 0221 if (!item) { // remove URL from the list for a file that no longer exists 0222 it.remove(); 0223 } else { 0224 addFileItem(item); 0225 } 0226 } 0227 0228 save(); 0229 return true; 0230 } 0231 0232 // ==== private ==== 0233 0234 void VirtualFileSystem::mkDirInternal(const QString &name) 0235 { 0236 // clean path, consistent with currentDir() 0237 QString dirName = name; 0238 dirName = dirName.remove('/'); 0239 if (dirName.isEmpty()) 0240 dirName = '/'; 0241 0242 _virtFilesystemDict.insert(dirName, new QList<QUrl>()); 0243 _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName)); 0244 } 0245 0246 void VirtualFileSystem::save() 0247 { 0248 KConfig *db = &VirtualFileSystem::getVirtDB(); 0249 db->deleteGroup("virt_db"); 0250 KConfigGroup group(db, "virt_db"); 0251 0252 QHashIterator<QString, QList<QUrl> *> it(_virtFilesystemDict); 0253 while (it.hasNext()) { 0254 it.next(); 0255 QList<QUrl> *urlList = it.value(); 0256 0257 QList<QUrl>::iterator url; 0258 QStringList entry; 0259 for (url = urlList->begin(); url != urlList->end(); ++url) { 0260 entry.append((*url).toDisplayString()); 0261 } 0262 // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /) 0263 group.writeEntry("Item_" + it.key(), entry); 0264 group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]); 0265 } 0266 0267 db->sync(); 0268 } 0269 0270 void VirtualFileSystem::restore() 0271 { 0272 KConfig *db = &VirtualFileSystem::getVirtDB(); 0273 const KConfigGroup dbGrp(db, "virt_db"); 0274 0275 const QMap<QString, QString> map = db->entryMap("virt_db"); 0276 QMapIterator<QString, QString> it(map); 0277 while (it.hasNext()) { 0278 it.next(); 0279 0280 // KDE 4.0 workaround: check and remove 'Item_' prefix 0281 if (!it.key().startsWith(QLatin1String("Item_"))) 0282 continue; 0283 const QString key = it.key().mid(5); 0284 0285 const QList<QUrl> urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList())); 0286 _virtFilesystemDict.insert(key, new QList<QUrl>(urlList)); 0287 _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString())); 0288 } 0289 0290 if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason 0291 _virtFilesystemDict.insert("/", new QList<QUrl>()); 0292 } 0293 } 0294 0295 FileItem *VirtualFileSystem::createFileItem(const QUrl &url) 0296 { 0297 if (url.scheme() == "virt") { // return a virtual directory in root 0298 QString path = url.path().mid(1); 0299 if (path.isEmpty()) 0300 path = '/'; 0301 return FileItem::createVirtualDir(path, url); 0302 } 0303 0304 const QUrl directory = url.adjusted(QUrl::RemoveFilename); 0305 0306 if (url.isLocalFile()) { 0307 QFileInfo file(url.path()); 0308 return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : nullptr; 0309 } 0310 0311 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 0312 connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult); 0313 0314 // ugly: we have to wait here until the stat job is finished 0315 QEventLoop eventLoop; 0316 connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit); 0317 eventLoop.exec(); // blocking until quit() 0318 0319 if (_fileEntry.count() == 0) { 0320 return nullptr; // stat job failed 0321 } 0322 0323 if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) { 0324 // TODO this also happens for FTP directories 0325 return nullptr; // file not found 0326 } 0327 0328 return FileSystem::createFileItemFromKIO(_fileEntry, directory, true); 0329 } 0330 0331 KConfig &VirtualFileSystem::getVirtDB() 0332 { 0333 // virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals); 0334 static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); 0335 return db; 0336 } 0337 0338 void VirtualFileSystem::slotStatResult(KJob *job) 0339 { 0340 _fileEntry = job->error() ? KIO::UDSEntry() : dynamic_cast<KIO::StatJob *>(job)->statResult(); 0341 } 0342 0343 void VirtualFileSystem::showError(const QString &error) 0344 { 0345 QWidget *window = QApplication::activeWindow(); 0346 KMessageBox::error(window, error); // window can be null, is allowed 0347 }