File indexing completed on 2024-05-19 09:48:15
0001 /* 0002 SPDX-FileCopyrightText: 2000 Shie Erlich <krusader@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@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 "filesystem.h" 0010 0011 #include <memory> 0012 0013 // QtCore 0014 #include <QDebug> 0015 #include <QDir> 0016 #include <QList> 0017 // QtWidgets 0018 #include <qplatformdefs.h> 0019 0020 #include <KConfigCore/KSharedConfig> 0021 #include <KI18n/KLocalizedString> 0022 #include <KIO/JobUiDelegate> 0023 0024 #include "../JobMan/jobman.h" 0025 #include "../JobMan/krjob.h" 0026 #include "../defaults.h" 0027 #include "../krglobal.h" 0028 #include "fileitem.h" 0029 #include "krpermhandler.h" 0030 0031 FileSystem::FileSystem() 0032 : DirListerInterface(nullptr) 0033 , _isRefreshing(false) 0034 { 0035 } 0036 0037 FileSystem::~FileSystem() 0038 { 0039 clear(_fileItems); 0040 // please don't remove this line. This informs the view about deleting the file items. 0041 emit cleared(); 0042 } 0043 0044 QList<QUrl> FileSystem::getUrls(const QStringList &names) const 0045 { 0046 QList<QUrl> urls; 0047 for (const QString &name : names) { 0048 urls.append(getUrl(name)); 0049 } 0050 return urls; 0051 } 0052 0053 FileItem *FileSystem::getFileItem(const QString &name) const 0054 { 0055 return _fileItems.contains(name) ? _fileItems.value(name) : nullptr; 0056 } 0057 0058 KIO::filesize_t FileSystem::totalSize() const 0059 { 0060 KIO::filesize_t temp = 0; 0061 for (FileItem *item : _fileItems.values()) { 0062 if (!item->isDir() && item->getName() != "." && item->getName() != "..") { 0063 temp += item->getSize(); 0064 } 0065 } 0066 0067 return temp; 0068 } 0069 0070 QUrl FileSystem::ensureTrailingSlash(const QUrl &url) 0071 { 0072 if (url.path().endsWith('/')) { 0073 return url; 0074 } 0075 0076 QUrl adjustedUrl(url); 0077 adjustedUrl.setPath(adjustedUrl.path() + '/'); 0078 return adjustedUrl; 0079 } 0080 0081 QUrl FileSystem::preferLocalUrl(const QUrl &url) 0082 { 0083 if (url.isEmpty() || !url.scheme().isEmpty()) 0084 return url; 0085 0086 QUrl adjustedUrl = url; 0087 adjustedUrl.setScheme("file"); 0088 return adjustedUrl; 0089 } 0090 0091 bool FileSystem::scanOrRefresh(const QUrl &directory, bool onlyScan) 0092 { 0093 qDebug() << "from current dir=" << _currentDirectory.toDisplayString() << "; to=" << directory.toDisplayString(); 0094 if (_isRefreshing) { 0095 // NOTE: this does not happen (unless async)"; 0096 return false; 0097 } 0098 0099 // workaround for krarc: find out if transition to local fs is wanted and adjust URL manually 0100 QUrl url = directory; 0101 if (_currentDirectory.scheme() == "krarc" && url.scheme() == "krarc" && QDir(url.path()).exists()) { 0102 url.setScheme("file"); 0103 } 0104 0105 const bool dirChange = !url.isEmpty() && cleanUrl(url) != _currentDirectory; 0106 0107 const QUrl toRefresh = dirChange ? url.adjusted(QUrl::NormalizePathSegments) : _currentDirectory; 0108 if (!toRefresh.isValid()) { 0109 emit error(i18n("Malformed URL:\n%1", toRefresh.toDisplayString())); 0110 return false; 0111 } 0112 0113 _isRefreshing = true; 0114 0115 FileItemDict tempFileItems(_fileItems); // old file items are still used during refresh 0116 _fileItems.clear(); 0117 if (dirChange) 0118 // show an empty directory while loading the new one and clear selection 0119 emit cleared(); 0120 0121 const bool refreshed = refreshInternal(toRefresh, onlyScan); 0122 _isRefreshing = false; 0123 0124 if (!refreshed) { 0125 // cleanup and abort 0126 if (!dirChange) 0127 emit cleared(); 0128 clear(tempFileItems); 0129 return false; 0130 } 0131 0132 emit scanDone(dirChange); 0133 0134 clear(tempFileItems); 0135 0136 updateFilesystemInfo(); 0137 0138 return true; 0139 } 0140 0141 void FileSystem::deleteFiles(const QList<QUrl> &urls, bool moveToTrash) 0142 { 0143 KrJob *krJob = KrJob::createDeleteJob(urls, moveToTrash); 0144 connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { 0145 connectJobToSources(job, urls); 0146 }); 0147 0148 if (moveToTrash) { 0149 // update destination: the trash bin (in case a panel/tab is showing it) 0150 connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { 0151 // Note: the "trash" protocol should always have only one "/" after the "scheme:" part 0152 connect(job, &KIO::Job::result, this, [=]() { 0153 emit fileSystemChanged(QUrl("trash:/"), false); 0154 }); 0155 }); 0156 } 0157 0158 krJobMan->manageJob(krJob); 0159 } 0160 0161 void FileSystem::connectJobToSources(KJob *job, const QList<QUrl> &urls) 0162 { 0163 if (!urls.isEmpty()) { 0164 // TODO we assume that all files were in the same directory and only emit one signal for 0165 // the directory of the first file URL (all subdirectories of parent are notified) 0166 const QUrl url = urls.first().adjusted(QUrl::RemoveFilename); 0167 connect(job, &KIO::Job::result, this, [=]() { 0168 emit fileSystemChanged(url, true); 0169 }); 0170 } 0171 } 0172 0173 void FileSystem::connectJobToDestination(KJob *job, const QUrl &destination) 0174 { 0175 connect(job, &KIO::Job::result, this, [=]() { 0176 emit fileSystemChanged(destination, false); 0177 }); 0178 // (additional) direct refresh if on local fs because watcher is too slow 0179 const bool refresh = cleanUrl(destination) == _currentDirectory && isLocal(); 0180 connect(job, &KIO::Job::result, this, [=](KJob *job) { 0181 slotJobResult(job, refresh); 0182 }); 0183 } 0184 0185 bool FileSystem::showHiddenFiles() 0186 { 0187 const KConfigGroup gl(krConfig, "Look&Feel"); 0188 return gl.readEntry("Show Hidden", _ShowHidden); 0189 } 0190 0191 void FileSystem::addFileItem(FileItem *item) 0192 { 0193 _fileItems.insert(item->getName(), item); 0194 } 0195 0196 FileItem *FileSystem::createLocalFileItem(const QString &name, const QString &directory, bool virt) 0197 { 0198 const QDir dir = QDir(directory); 0199 const QString path = dir.filePath(name); 0200 const QByteArray pathByteArray = path.toLocal8Bit(); 0201 const QString fileItemName = virt ? path : name; 0202 const QUrl fileItemUrl = QUrl::fromLocalFile(path); 0203 0204 // read file status; in case of error create a "broken" file item 0205 QT_STATBUF stat_p; 0206 memset(&stat_p, 0, sizeof(stat_p)); 0207 if (QT_LSTAT(pathByteArray.data(), &stat_p) < 0) 0208 return FileItem::createBroken(fileItemName, fileItemUrl); 0209 0210 const KIO::filesize_t size = stat_p.st_size; 0211 bool isDir = S_ISDIR(stat_p.st_mode); 0212 const bool isLink = S_ISLNK(stat_p.st_mode); 0213 0214 // for links, read link destination and determine whether it's broken or not 0215 QString linkDestination; 0216 bool brokenLink = false; 0217 if (isLink) { 0218 linkDestination = readLinkSafely(pathByteArray.data()); 0219 0220 if (linkDestination.isNull()) { 0221 brokenLink = true; 0222 } else { 0223 const QFileInfo linkFile(dir, linkDestination); 0224 if (!linkFile.exists()) 0225 brokenLink = true; 0226 else if (linkFile.isDir()) 0227 isDir = true; 0228 } 0229 } 0230 0231 // TODO use statx available in glibc >= 2.28 supporting creation time (btime) and more 0232 0233 // create normal file item 0234 return new FileItem(fileItemName, 0235 fileItemUrl, 0236 isDir, 0237 size, 0238 stat_p.st_mode, 0239 stat_p.st_mtime, 0240 stat_p.st_ctime, 0241 stat_p.st_atime, 0242 -1, 0243 stat_p.st_uid, 0244 stat_p.st_gid, 0245 QString(), 0246 QString(), 0247 isLink, 0248 linkDestination, 0249 brokenLink); 0250 } 0251 0252 QString FileSystem::readLinkSafely(const char *path) 0253 { 0254 // inspired by the areadlink_with_size function from gnulib, which is used for coreutils 0255 // idea: start with a small buffer and gradually increase it as we discover it wasn't enough 0256 0257 QT_OFF_T bufferSize = 1024; // start with 1 KiB 0258 QT_OFF_T maxBufferSize = std::numeric_limits<QT_OFF_T>::max(); 0259 0260 while (true) { 0261 // try to read the link 0262 std::unique_ptr<char[]> buffer(new char[bufferSize]); 0263 auto nBytesRead = readlink(path, buffer.get(), bufferSize); 0264 0265 // should never happen, asserted by the readlink 0266 if (nBytesRead > bufferSize) { 0267 return QString(); 0268 } 0269 0270 // read failure 0271 if (nBytesRead < 0) { 0272 qDebug() << "Failed to read the link " << path; 0273 return QString(); 0274 } 0275 0276 // read success 0277 if (nBytesRead < bufferSize || nBytesRead == maxBufferSize) { 0278 return QString::fromLocal8Bit(buffer.get(), static_cast<int>(nBytesRead)); 0279 } 0280 0281 // increase the buffer and retry again 0282 // bufferSize < maxBufferSize is implied from previous checks 0283 if (bufferSize <= maxBufferSize / 2) { 0284 bufferSize *= 2; 0285 } else { 0286 bufferSize = maxBufferSize; 0287 } 0288 } 0289 } 0290 0291 FileItem *FileSystem::createFileItemFromKIO(const KIO::UDSEntry &entry, const QUrl &directory, bool virt) 0292 { 0293 const KFileItem kfi(entry, directory, true, true); 0294 0295 const QString name = kfi.name(); 0296 // ignore un-needed entries 0297 if (name.isEmpty() || name == "." || name == "..") { 0298 return nullptr; 0299 } 0300 0301 const QString localPath = kfi.localPath(); 0302 const QUrl url = !localPath.isEmpty() ? QUrl::fromLocalFile(localPath) : kfi.url(); 0303 const QString fname = virt ? url.toDisplayString() : name; 0304 0305 // get file statistics... 0306 const time_t mtime = kfi.time(KFileItem::ModificationTime).toTime_t(); 0307 const time_t atime = kfi.time(KFileItem::AccessTime).toTime_t(); 0308 const mode_t mode = kfi.mode() | kfi.permissions(); 0309 const QDateTime creationTime = kfi.time(KFileItem::CreationTime); 0310 const time_t btime = creationTime.isValid() ? creationTime.toTime_t() : (time_t)-1; 0311 0312 // NOTE: we could get the mimetype (and file icon) from the kfileitem here but this is very 0313 // slow. Instead, the file item class has it's own (faster) way to determine the file type. 0314 0315 // NOTE: "broken link" flag is always false, checking link destination existence is 0316 // considered to be too expensive 0317 return new FileItem(fname, 0318 url, 0319 kfi.isDir(), 0320 kfi.size(), 0321 mode, 0322 mtime, 0323 -1, 0324 atime, 0325 btime, 0326 (uid_t)-1, 0327 (gid_t)-1, 0328 kfi.user(), 0329 kfi.group(), 0330 kfi.isLink(), 0331 kfi.linkDest(), 0332 false, 0333 kfi.ACL().asString(), 0334 kfi.defaultACL().asString()); 0335 } 0336 0337 void FileSystem::slotJobResult(KJob *job, bool refresh) 0338 { 0339 if (job->error() && job->uiDelegate()) { 0340 // show errors for modifying operations as popup (works always) 0341 job->uiDelegate()->showErrorMessage(); 0342 } 0343 0344 if (refresh) { 0345 FileSystem::refresh(); 0346 } 0347 } 0348 0349 void FileSystem::clear(FileItemDict &fileItems) 0350 { 0351 QHashIterator<QString, FileItem *> lit(fileItems); 0352 while (lit.hasNext()) { 0353 delete lit.next().value(); 0354 } 0355 fileItems.clear(); 0356 }