File indexing completed on 2024-05-05 14:18:33
0001 // clang-format off 0002 /* 0003 * KDiff3 - Text Diff And Merge Tool 0004 * 0005 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de 0006 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 // clang-format on 0010 #include "fileaccess.h" 0011 0012 #include "common.h" 0013 #include "compat.h" 0014 0015 #if HAS_KFKIO && !defined AUTOTEST 0016 #include "DefaultFileAccessJobHandler.h" 0017 #endif 0018 #include "FileAccessJobHandler.h" 0019 #include "IgnoreList.h" 0020 #include "Logging.h" 0021 #include "ProgressProxy.h" 0022 #include "Utils.h" 0023 0024 #include <algorithm> // for min 0025 #include <cstdlib> 0026 #include <sys/stat.h> 0027 0028 #ifndef Q_OS_WIN 0029 #include <unistd.h> 0030 #endif 0031 #include <utility> // for move 0032 0033 #include <QDir> 0034 #include <QFile> 0035 #include <QTemporaryFile> 0036 #include <QtMath> 0037 0038 #if HAS_KFKIO && !defined AUTOTEST 0039 FileAccess::FileAccess() 0040 { 0041 mJobHandler.reset(new DefaultFileAccessJobHandler(this)); 0042 } 0043 #else 0044 FileAccess::FileAccess() = default; 0045 #endif 0046 0047 FileAccess::~FileAccess() = default; 0048 0049 FileAccess::FileAccess(const FileAccess& b): 0050 m_pParent{b.m_pParent}, 0051 m_url{b.m_url}, 0052 m_bValidData{b.m_bValidData}, 0053 m_baseDir{b.m_baseDir}, 0054 m_fileInfo{b.m_fileInfo}, 0055 m_linkTarget{b.m_linkTarget}, 0056 m_name{b.m_name}, 0057 mDisplayName{b.mDisplayName}, 0058 m_localCopy{b.m_localCopy}, 0059 mPhysicalPath{b.mPhysicalPath}, 0060 tmpFile{b.tmpFile}, 0061 realFile{b.realFile}, 0062 m_size{b.m_size}, 0063 m_modificationTime{b.m_modificationTime}, 0064 m_bSymLink{b.m_bSymLink}, 0065 m_bFile{b.m_bFile}, 0066 m_bDir{b.m_bDir}, 0067 m_bExists{b.m_bExists}, 0068 m_bWritable{b.m_bWritable}, 0069 m_bReadable{b.m_bReadable}, 0070 m_bExecutable{b.m_bExecutable}, 0071 m_bHidden{b.m_bHidden} 0072 { 0073 mJobHandler.reset(b.mJobHandler ? b.mJobHandler->copy(this) : nullptr); 0074 } 0075 0076 FileAccess::FileAccess(FileAccess&& b) noexcept: 0077 m_pParent{b.m_pParent}, 0078 m_bValidData{b.m_bValidData}, 0079 m_baseDir{b.m_baseDir}, 0080 m_fileInfo{b.m_fileInfo}, 0081 m_linkTarget{b.m_linkTarget}, 0082 m_name{b.m_name}, 0083 mDisplayName{b.mDisplayName}, 0084 m_localCopy{b.m_localCopy}, 0085 mPhysicalPath{b.mPhysicalPath}, 0086 tmpFile{b.tmpFile}, 0087 realFile{b.realFile}, 0088 m_size{b.m_size}, 0089 m_modificationTime{b.m_modificationTime}, 0090 m_bSymLink{b.m_bSymLink}, 0091 m_bFile{b.m_bFile}, 0092 m_bDir{b.m_bDir}, 0093 m_bExists{b.m_bExists}, 0094 m_bWritable{b.m_bWritable}, 0095 m_bReadable{b.m_bReadable}, 0096 m_bExecutable{b.m_bExecutable}, 0097 m_bHidden{b.m_bHidden} 0098 { 0099 mJobHandler.reset(b.mJobHandler.release()); 0100 if(mJobHandler) mJobHandler->setFileAccess(this); 0101 0102 m_url = std::move(b.m_url); 0103 0104 b.m_pParent = nullptr; 0105 b.m_bValidData = false; 0106 0107 b.m_baseDir = QDir(); 0108 b.m_fileInfo = QFileInfo(); 0109 b.m_linkTarget = QString(); 0110 b.m_name = QString(); 0111 b.mDisplayName = QString(); 0112 b.m_localCopy = QString(); 0113 b.mPhysicalPath = QString(); 0114 b.tmpFile = nullptr; 0115 b.realFile = nullptr; 0116 b.m_size = 0; 0117 b.m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); 0118 b.m_bSymLink = false; 0119 b.m_bFile = false; 0120 b.m_bDir = false; 0121 b.m_bExists = false; 0122 b.m_bWritable = false; 0123 b.m_bReadable = false; 0124 b.m_bExecutable = false; 0125 b.m_bHidden = false; 0126 } 0127 0128 FileAccess& FileAccess::operator=(const FileAccess& b) 0129 { 0130 if(&b == this) return *this; 0131 0132 //mJobHandler defaults to nullptr 0133 0134 mJobHandler.reset(b.mJobHandler ? b.mJobHandler->copy(this) : nullptr); 0135 0136 m_pParent = b.m_pParent; 0137 m_url = b.m_url; 0138 m_bValidData = b.m_bValidData; 0139 m_baseDir = b.m_baseDir; 0140 m_fileInfo = b.m_fileInfo; 0141 m_linkTarget = b.m_linkTarget; 0142 m_name = b.m_name; 0143 mDisplayName = b.mDisplayName; 0144 m_localCopy = b.m_localCopy; 0145 mPhysicalPath = b.mPhysicalPath; 0146 tmpFile = b.tmpFile; 0147 realFile = b.realFile; 0148 m_size = b.m_size; 0149 m_modificationTime = b.m_modificationTime; 0150 m_bSymLink = b.m_bSymLink; 0151 m_bFile = b.m_bFile; 0152 m_bDir = b.m_bDir; 0153 m_bExists = b.m_bExists; 0154 m_bWritable = b.m_bWritable; 0155 m_bReadable = b.m_bReadable; 0156 m_bExecutable = b.m_bExecutable; 0157 m_bHidden = b.m_bHidden; 0158 return *this; 0159 } 0160 0161 FileAccess& FileAccess::operator=(FileAccess&& b) noexcept 0162 { 0163 if(&b == this) return *this; 0164 0165 mJobHandler.reset(b.mJobHandler.release()); 0166 if (mJobHandler) mJobHandler->setFileAccess(this); 0167 0168 m_pParent = b.m_pParent; 0169 m_url = b.m_url; 0170 m_bValidData = b.m_bValidData; 0171 m_baseDir = b.m_baseDir; 0172 m_fileInfo = b.m_fileInfo; 0173 m_linkTarget = b.m_linkTarget; 0174 m_name = b.m_name; 0175 mDisplayName = b.mDisplayName; 0176 m_localCopy = b.m_localCopy; 0177 mPhysicalPath = b.mPhysicalPath; 0178 tmpFile = b.tmpFile; 0179 realFile = b.realFile; 0180 m_size = b.m_size; 0181 m_modificationTime = b.m_modificationTime; 0182 m_bSymLink = b.m_bSymLink; 0183 m_bFile = b.m_bFile; 0184 m_bDir = b.m_bDir; 0185 m_bExists = b.m_bExists; 0186 m_bWritable = b.m_bWritable; 0187 m_bReadable = b.m_bReadable; 0188 m_bExecutable = b.m_bExecutable; 0189 m_bHidden = b.m_bHidden; 0190 0191 b.m_pParent = nullptr; 0192 b.m_url = QUrl(); 0193 b.m_bValidData = false; 0194 0195 b.m_baseDir = QDir(); 0196 b.m_fileInfo = QFileInfo(); 0197 b.m_linkTarget = QString(); 0198 b.m_name = QString(); 0199 b.mDisplayName = QString(); 0200 b.m_localCopy = QString(); 0201 b.mPhysicalPath = QString(); 0202 b.tmpFile = nullptr; 0203 b.realFile = nullptr; 0204 b.m_size = 0; 0205 b.m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); 0206 b.m_bSymLink = false; 0207 b.m_bFile = false; 0208 b.m_bDir = false; 0209 b.m_bExists = false; 0210 b.m_bWritable = false; 0211 b.m_bReadable = false; 0212 b.m_bExecutable = false; 0213 b.m_bHidden = false; 0214 return *this; 0215 } 0216 0217 FileAccess::FileAccess(const QString& name, bool bWantToWrite) 0218 { 0219 setFile(name, bWantToWrite); 0220 } 0221 0222 FileAccess::FileAccess(const QUrl& name, bool bWantToWrite) 0223 { 0224 setFile(name, bWantToWrite); 0225 } 0226 0227 /* 0228 Performs a re-init. This delibratly does not include mJobHandler. 0229 */ 0230 void FileAccess::reset() 0231 { 0232 m_url.clear(); 0233 m_name.clear(); 0234 m_fileInfo = QFileInfo(); 0235 m_bExists = false; 0236 m_bFile = false; 0237 m_bDir = false; 0238 m_bSymLink = false; 0239 m_bWritable = false; 0240 m_bHidden = false; 0241 m_size = 0; 0242 m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); 0243 0244 mDisplayName.clear(); 0245 mPhysicalPath.clear(); 0246 m_linkTarget.clear(); 0247 //Cleanup temp file if any. 0248 tmpFile = QSharedPointer<QTemporaryFile>::create(); 0249 realFile.clear(); 0250 0251 m_pParent = nullptr; 0252 m_bValidData = false; 0253 } 0254 0255 /* 0256 Needed only during directory listing right now. 0257 */ 0258 void FileAccess::setFile(FileAccess* pParent, const QFileInfo& fi) 0259 { 0260 assert(pParent != this); 0261 #if HAS_KFKIO && !defined AUTOTEST 0262 if(mJobHandler == nullptr) mJobHandler.reset(new DefaultFileAccessJobHandler(this)); 0263 #endif 0264 reset(); 0265 0266 m_fileInfo = fi; 0267 m_url = QUrl::fromLocalFile(m_fileInfo.absoluteFilePath()); 0268 0269 m_pParent = pParent; 0270 loadData(); 0271 } 0272 0273 void FileAccess::setFile(const QString& name, bool bWantToWrite) 0274 { 0275 if(name.isEmpty()) 0276 return; 0277 0278 QUrl url = QUrl::fromUserInput(name, QString(), QUrl::AssumeLocalFile); 0279 setFile(url, bWantToWrite); 0280 } 0281 0282 void FileAccess::setFile(const QUrl& url, bool bWantToWrite) 0283 { 0284 if(url.isEmpty()) 0285 return; 0286 0287 #if HAS_KFKIO && !defined AUTOTEST 0288 if(mJobHandler == nullptr) mJobHandler.reset(new DefaultFileAccessJobHandler(this)); 0289 #endif 0290 reset(); 0291 assert(parent() == nullptr || url != parent()->url()); 0292 0293 m_url = url; 0294 0295 if(isLocal()) // Invalid urls are treated as local files. 0296 { 0297 /* 0298 Utils::urlToString handles choosing the right API from QUrl. 0299 */ 0300 m_fileInfo.setFile(Utils::urlToString(url)); 0301 0302 m_pParent = nullptr; 0303 0304 loadData(); 0305 } 0306 else 0307 { 0308 m_name = m_url.fileName(); 0309 0310 if(mJobHandler->stat(bWantToWrite)) 0311 m_bValidData = true; // After running stat() the variables are initialised 0312 // and valid even if the file doesn't exist and the stat 0313 // query failed. 0314 } 0315 } 0316 0317 void FileAccess::loadData() 0318 { 0319 m_fileInfo.setCaching(true); 0320 0321 if(parent() == nullptr) 0322 m_baseDir.setPath(m_fileInfo.absoluteFilePath()); 0323 else 0324 m_baseDir = m_pParent->m_baseDir; 0325 0326 //convert to absolute path that doesn't depend on the current directory. 0327 m_fileInfo.makeAbsolute(); 0328 m_bSymLink = m_fileInfo.isSymLink(); 0329 0330 m_bFile = m_fileInfo.isFile(); 0331 m_bDir = m_fileInfo.isDir(); 0332 m_bExists = m_fileInfo.exists(); 0333 m_size = m_fileInfo.size(); 0334 m_modificationTime = m_fileInfo.lastModified(); 0335 m_bHidden = m_fileInfo.isHidden(); 0336 0337 m_bWritable = m_fileInfo.isWritable(); 0338 m_bReadable = m_fileInfo.isReadable(); 0339 m_bExecutable = m_fileInfo.isExecutable(); 0340 0341 m_name = m_fileInfo.fileName(); 0342 if(isLocal() && m_name.isEmpty()) 0343 { 0344 m_name = m_fileInfo.absoluteDir().dirName(); 0345 } 0346 0347 if(isLocal() && m_bSymLink) 0348 { 0349 m_linkTarget = m_fileInfo.symLinkTarget(); 0350 0351 #ifndef Q_OS_WIN 0352 // Unfortunately Qt5 symLinkTarget/readLink always return an absolute path, even if the link is relative 0353 std::unique_ptr<char[]> s = std::make_unique<char[]>(PATH_MAX + 1); 0354 ssize_t len = readlink(QFile::encodeName(absoluteFilePath()).constData(), s.get(), PATH_MAX); 0355 if(len > 0) 0356 { 0357 s[len] = '\0'; 0358 m_linkTarget = QFile::decodeName(s.get()); 0359 } 0360 #endif 0361 0362 m_bBrokenLink = !QFileInfo::exists(m_linkTarget); 0363 //We want to know if the link itself exists 0364 if(m_bBrokenLink) 0365 m_bExists = true; 0366 0367 if(!m_modificationTime.isValid()) 0368 m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); 0369 } 0370 0371 realFile = QSharedPointer<QFile>::create(absoluteFilePath()); 0372 m_bValidData = true; 0373 } 0374 0375 void FileAccess::addPath(const QString& txt, bool reinit) 0376 { 0377 if(!isLocal()) 0378 { 0379 QUrl url = m_url.adjusted(QUrl::StripTrailingSlash); 0380 url.setPath(url.path() + '/' + txt); 0381 m_url = url; 0382 0383 if(reinit) 0384 setFile(url); // reinitialize 0385 } 0386 else 0387 { 0388 QString slash = (txt.isEmpty() || txt[0] == '/') ? QString() : u8"/"; 0389 setFile(absoluteFilePath() + slash + txt); 0390 } 0391 } 0392 0393 #if HAS_KFKIO && !defined AUTOTEST 0394 /* Filetype: 0395 S_IFMT 0170000 bitmask for the file type bitfields 0396 S_IFSOCK 0140000 socket 0397 S_IFLNK 0120000 symbolic link 0398 S_IFREG 0100000 regular file 0399 S_IFBLK 0060000 block device 0400 S_IFDIR 0040000 directory 0401 S_IFCHR 0020000 character device 0402 S_IFIFO 0010000 fifo 0403 S_ISUID 0004000 set UID bit 0404 S_ISGID 0002000 set GID bit (see below) 0405 S_ISVTX 0001000 sticky bit (see below) 0406 0407 Access: 0408 S_IRWXU 00700 mask for file owner permissions 0409 S_IRUSR 00400 owner has read permission 0410 S_IWUSR 00200 owner has write permission 0411 S_IXUSR 00100 owner has execute permission 0412 S_IRWXG 00070 mask for group permissions 0413 S_IRGRP 00040 group has read permission 0414 S_IWGRP 00020 group has write permission 0415 S_IXGRP 00010 group has execute permission 0416 S_IRWXO 00007 mask for permissions for others (not in group) 0417 S_IROTH 00004 others have read permission 0418 S_IWOTH 00002 others have write permission 0419 S_IXOTH 00001 others have execute permission 0420 */ 0421 //This is what KIO uses on windows so we might as well check it. 0422 #ifdef Q_OS_WIN 0423 #define S_IRUSR 0400 // Read by owner. 0424 #define S_IWUSR 0200 // Write by owner. 0425 #define S_IXUSR 0100 // Execute by owner. 0426 #define S_IROTH 00004 // others have read permission 0427 #define S_IWOTH 00002 // others have write permission 0428 #define S_IXOTH 00001 // others have execute permission 0429 #endif 0430 void FileAccess::setFromUdsEntry(const KIO::UDSEntry& e, FileAccess* parent) 0431 { 0432 long acc = 0; 0433 long fileType = 0; 0434 const QVector<uint> fields = e.fields(); 0435 QString filePath; 0436 0437 assert(this != parent); 0438 m_pParent = parent; 0439 0440 for(const uint fieldId: fields) 0441 { 0442 switch(fieldId) 0443 { 0444 case KIO::UDSEntry::UDS_SIZE: 0445 m_size = e.numberValue(fieldId); 0446 break; 0447 case KIO::UDSEntry::UDS_NAME: 0448 filePath = e.stringValue(fieldId); 0449 qCDebug(kdiffFileAccess) << "filePath = " << filePath; 0450 break; // During listDir the relative path is given here. 0451 case KIO::UDSEntry::UDS_MODIFICATION_TIME: 0452 m_modificationTime = QDateTime::fromMSecsSinceEpoch(e.numberValue(fieldId)); 0453 break; 0454 case KIO::UDSEntry::UDS_LINK_DEST: 0455 m_linkTarget = e.stringValue(fieldId); 0456 break; 0457 case KIO::UDSEntry::UDS_ACCESS: 0458 acc = e.numberValue(fieldId); 0459 m_bReadable = (acc & S_IRUSR) != 0; 0460 m_bWritable = (acc & S_IWUSR) != 0; 0461 m_bExecutable = (acc & S_IXUSR) != 0; 0462 break; 0463 case KIO::UDSEntry::UDS_FILE_TYPE: 0464 /* 0465 According to KIO docs UDS_LINK_DEST not S_ISLNK should be used to determine if the url is a symlink. 0466 UDS_FILE_TYPE is explicitly stated to be the type of the linked file not the link itself. 0467 */ 0468 0469 m_bSymLink = e.isLink(); 0470 if(!m_bSymLink) 0471 { 0472 fileType = e.numberValue(fieldId); 0473 m_bDir = (fileType & QT_STAT_MASK) == QT_STAT_DIR; 0474 m_bFile = (fileType & QT_STAT_MASK) == QT_STAT_REG; 0475 m_bExists = fileType != 0; 0476 } 0477 else 0478 { 0479 m_bDir = false; 0480 m_bFile = false; 0481 m_bExists = true; 0482 } 0483 break; 0484 case KIO::UDSEntry::UDS_URL: 0485 m_url = QUrl(e.stringValue(fieldId)); 0486 qCDebug(kdiffFileAccess) << "Url = " << m_url; 0487 break; 0488 case KIO::UDSEntry::UDS_DISPLAY_NAME: 0489 mDisplayName = e.stringValue(fieldId); 0490 break; 0491 case KIO::UDSEntry::UDS_LOCAL_PATH: 0492 mPhysicalPath = e.stringValue(fieldId); 0493 break; 0494 case KIO::UDSEntry::UDS_MIME_TYPE: 0495 case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE: 0496 case KIO::UDSEntry::UDS_XML_PROPERTIES: 0497 default: 0498 break; 0499 } 0500 } 0501 0502 //Seems to be the norm for fish and possibly other prototcol handlers. 0503 if(m_url.isEmpty()) 0504 { 0505 qCInfo(kdiffFileAccess) << "Url not received from KIO."; 0506 if(Q_UNLIKELY(parent == nullptr)) 0507 { 0508 /* 0509 Invalid entry we don't know the full url because KIO didn't tell us and there is no parent 0510 node supplied. 0511 This is a bug if it happens and should be logged. However it is a recoverable error. 0512 */ 0513 qCCritical(kdiffFileAccess) << i18n("Unable to determine full url. No parent specified."); 0514 return; 0515 } 0516 /* 0517 Don't trust QUrl::resolved it doesn't always do what kdiff3 wants. 0518 */ 0519 m_url = parent->url(); 0520 addPath(filePath, false); 0521 //This too would be a bug somewhere. Don't crash out though. 0522 if(Q_UNLIKELY(m_url == parent->url())) 0523 { 0524 m_url.clear(); 0525 qCCritical(kdiffFileAccess) << "Parent and child could not be distinguished."; 0526 return; 0527 } 0528 0529 qCDebug(kdiffFileAccess) << "Computed url is: " << m_url; 0530 //Verify that the scheme doesn't change. 0531 assert(m_url.scheme() == parent->url().scheme()); 0532 } 0533 0534 //KIO does this when stating a remote folder. 0535 if(filePath.isEmpty()) 0536 { 0537 filePath = m_url.path(); 0538 } 0539 0540 m_fileInfo = QFileInfo(filePath); 0541 m_fileInfo.setCaching(true); 0542 //These functions work on the path string without accessing the file system 0543 m_name = m_fileInfo.fileName(); 0544 if(m_name.isEmpty()) 0545 m_name = m_fileInfo.absoluteDir().dirName(); 0546 0547 if(isLocal()) 0548 { 0549 m_bBrokenLink = !m_fileInfo.exists() && m_fileInfo.isSymLink(); 0550 m_bExists = m_fileInfo.exists() || m_bBrokenLink; 0551 0552 //insure modification time is initialized if it wasn't already. 0553 if(!m_bBrokenLink && m_modificationTime == QDateTime::fromMSecsSinceEpoch(0)) 0554 m_modificationTime = m_fileInfo.lastModified(); 0555 } 0556 0557 m_bValidData = true; 0558 m_bSymLink = !m_linkTarget.isEmpty(); 0559 0560 #ifndef Q_OS_WIN 0561 m_bHidden = m_name[0] == '.'; 0562 #endif 0563 } 0564 #endif 0565 0566 bool FileAccess::isValid() const 0567 { 0568 return m_bValidData; 0569 } 0570 0571 bool FileAccess::isNormal() const 0572 { 0573 static quint32 depth = 0; 0574 /* 0575 Speed is important here isNormal is called for every file during directory 0576 comparison. It can therefor have great impact on overall performance. 0577 0578 We also need to insure that we don't keep looking indefinitely when following 0579 links that point to links. Therefore we hard cap at 15 such links in a chain 0580 and make sure we don't cycle back to something we already saw. 0581 */ 0582 if(!mVisited && depth < 15 && isLocal() && isSymLink()) 0583 { 0584 /* 0585 wierd psudo-name created from commandline input redirection from output of another command. 0586 KIO/Qt does not handle it as a normal file but presents it as such. 0587 */ 0588 if(m_linkTarget.startsWith("pipe:")) 0589 { 0590 return false; 0591 } 0592 0593 FileAccess target(m_linkTarget); 0594 0595 mVisited = true; 0596 ++depth; 0597 /* 0598 Catch local links to special files. '/dev' has many of these. 0599 */ 0600 bool result = target.isNormal(); 0601 // mVisited has done its job and should be reset here. 0602 mVisited = false; 0603 --depth; 0604 0605 return result; 0606 } 0607 0608 return !exists() || isFile() || isDir() || isSymLink(); 0609 } 0610 0611 bool FileAccess::isFile() const 0612 { 0613 if(!isLocal()) 0614 return m_bFile; 0615 else 0616 return m_fileInfo.isFile(); 0617 } 0618 0619 bool FileAccess::isDir() const 0620 { 0621 if(!isLocal()) 0622 return m_bDir; 0623 else 0624 return m_fileInfo.isDir(); 0625 } 0626 0627 bool FileAccess::isSymLink() const 0628 { 0629 if(!isLocal()) 0630 return m_bSymLink; 0631 else 0632 return m_fileInfo.isSymLink(); 0633 } 0634 0635 bool FileAccess::exists() const 0636 { 0637 if(!isLocal()) 0638 return m_bExists; 0639 else 0640 return (m_fileInfo.exists() || isSymLink()) && // QFileInfo.exists returns false for broken links, 0641 absoluteFilePath() != "/dev/null"; // git uses /dev/null as a placeholder meaning does not exist 0642 } 0643 0644 qint64 FileAccess::size() const 0645 { 0646 if(!isLocal()) 0647 return m_size; 0648 else 0649 return m_fileInfo.size(); 0650 } 0651 0652 const QUrl& FileAccess::url() const 0653 { 0654 return m_url; 0655 } 0656 0657 /* 0658 FileAccess::isLocal() should return whether or not the m_url contains what KDiff3 considers 0659 a local i.e. non-KIO path. This is not the necessarily same as what QUrl::isLocalFile thinks. 0660 */ 0661 bool FileAccess::isLocal() const 0662 { 0663 return m_url.isLocalFile() || !m_url.isValid() || m_url.scheme().isEmpty(); 0664 } 0665 0666 bool FileAccess::isReadable() const 0667 { 0668 //This can be very slow in some network setups so use cached value 0669 if(!isLocal()) 0670 return m_bReadable; 0671 else 0672 return m_fileInfo.isReadable(); 0673 } 0674 0675 bool FileAccess::isWritable() const 0676 { 0677 //This can be very slow in some network setups so use cached value 0678 if(!isLocal()) 0679 return m_bWritable; 0680 else 0681 return m_fileInfo.isWritable(); 0682 } 0683 0684 bool FileAccess::isExecutable() const 0685 { 0686 //This can be very slow in some network setups so use cached value 0687 if(!isLocal()) 0688 return m_bExecutable; 0689 else 0690 return m_fileInfo.isExecutable(); 0691 } 0692 0693 bool FileAccess::isHidden() const 0694 { 0695 if(!(isLocal())) 0696 return m_bHidden; 0697 else 0698 return m_fileInfo.isHidden(); 0699 } 0700 0701 const QString& FileAccess::readLink() const 0702 { 0703 return m_linkTarget; 0704 } 0705 0706 QString FileAccess::absoluteFilePath() const 0707 { 0708 if(!isLocal()) 0709 return m_url.url(); // return complete url 0710 0711 return m_fileInfo.absoluteFilePath(); 0712 } // Full abs path 0713 0714 // Just the name-part of the path, without parent directories 0715 const QString& FileAccess::fileName(bool needTmp) const 0716 { 0717 if(!isLocal()) 0718 return (needTmp) ? m_localCopy : m_name; 0719 else 0720 return m_name; 0721 } 0722 0723 QString FileAccess::fileRelPath() const 0724 { 0725 #ifndef AUTOTEST 0726 assert(m_pParent == nullptr || m_baseDir == m_pParent->m_baseDir); 0727 #endif 0728 QString path; 0729 0730 if(isLocal()) 0731 { 0732 path = m_baseDir.relativeFilePath(m_fileInfo.absoluteFilePath()); 0733 0734 return path; 0735 } 0736 else 0737 { 0738 //Stop right before the root directory 0739 if(parent() == nullptr) return QString(); 0740 0741 const FileAccess* curEntry = this; 0742 path = fileName(); 0743 //Avoid recursively calling FileAccess::fileRelPath or we can get very large stacks. 0744 curEntry = curEntry->parent(); 0745 while(curEntry != nullptr) 0746 { 0747 if(curEntry->parent()) 0748 path.prepend(curEntry->fileName() + '/'); 0749 curEntry = curEntry->parent(); 0750 } 0751 return path; 0752 } 0753 } 0754 0755 FileAccess* FileAccess::parent() const 0756 { 0757 assert(m_pParent != this); 0758 return m_pParent; 0759 } 0760 0761 //Workaround for QUrl::toDisplayString/QUrl::toString behavior that does not fit KDiff3's expectations 0762 QString FileAccess::prettyAbsPath() const 0763 { 0764 return isLocal() ? absoluteFilePath() : m_url.toDisplayString(); 0765 } 0766 0767 const QDateTime& FileAccess::lastModified() const 0768 { 0769 return m_modificationTime; 0770 } 0771 0772 bool FileAccess::interruptableReadFile(void* pDestBuffer, qint64 maxLength) 0773 { 0774 ProgressScope pp; 0775 const qint64 maxChunkSize = 100000; 0776 qint64 i = 0; 0777 ProgressProxy::setMaxNofSteps(maxLength / maxChunkSize + 1); 0778 while(i < maxLength) 0779 { 0780 qint64 nextLength = std::min(maxLength - i, maxChunkSize); 0781 qint64 reallyRead = read((char*)pDestBuffer + i, nextLength); 0782 if(reallyRead != nextLength) 0783 { 0784 setStatusText(i18n("Failed to read file: %1", absoluteFilePath())); 0785 return false; 0786 } 0787 i += reallyRead; 0788 0789 ProgressProxy::setCurrent(qFloor(double(i) / maxLength * 100)); 0790 if(ProgressProxy::wasCancelled()) 0791 return false; 0792 } 0793 return true; 0794 } 0795 0796 bool FileAccess::readFile(void* pDestBuffer, qint64 maxLength) 0797 { 0798 bool success = false; 0799 //Avoid hang on linux for special files. 0800 if(!isNormal()) 0801 return true; 0802 0803 if(isLocal() || !m_localCopy.isEmpty()) 0804 { 0805 if(open(QIODevice::ReadOnly))//krazy:exclude=syscalls 0806 { 0807 success = interruptableReadFile(pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength ) 0808 close(); 0809 } 0810 } 0811 else 0812 { 0813 success = mJobHandler->get(pDestBuffer, maxLength); 0814 } 0815 0816 close(); 0817 assert(!realFile->isOpen() && !tmpFile->isOpen()); 0818 return success; 0819 } 0820 0821 bool FileAccess::writeFile(const void* pSrcBuffer, qint64 length) 0822 { 0823 ProgressScope pp; 0824 if(isLocal()) 0825 { 0826 if(realFile->open(QIODevice::WriteOnly)) 0827 { 0828 const qint64 maxChunkSize = 100000; 0829 ProgressProxy::setMaxNofSteps(length / maxChunkSize + 1); 0830 qint64 i = 0; 0831 while(i < length) 0832 { 0833 qint64 nextLength = std::min(length - i, maxChunkSize); 0834 qint64 reallyWritten = realFile->write((char*)pSrcBuffer + i, nextLength); 0835 if(reallyWritten != nextLength) 0836 { 0837 realFile->close(); 0838 return false; 0839 } 0840 i += reallyWritten; 0841 0842 ProgressProxy::step(); 0843 if(ProgressProxy::wasCancelled()) 0844 { 0845 realFile->close(); 0846 return false; 0847 } 0848 } 0849 0850 if(isExecutable()) // value is true if the old file was executable 0851 { 0852 // Preserve attributes 0853 realFile->setPermissions(realFile->permissions() | QFile::ExeUser); 0854 } 0855 0856 realFile->close(); 0857 return true; 0858 } 0859 } 0860 else 0861 { 0862 bool success = mJobHandler->put(pSrcBuffer, length, true /*overwrite*/); 0863 close(); 0864 0865 assert(!realFile->isOpen() && !tmpFile->isOpen()); 0866 0867 return success; 0868 } 0869 close(); 0870 assert(!realFile->isOpen() && !tmpFile->isOpen()); 0871 return false; 0872 } 0873 0874 bool FileAccess::copyFile(const QString& dest) 0875 { 0876 return mJobHandler->copyFile(dest); // Handles local and remote copying. 0877 } 0878 0879 bool FileAccess::rename(const FileAccess& dest) 0880 { 0881 return mJobHandler->rename(dest); 0882 } 0883 0884 bool FileAccess::removeFile() 0885 { 0886 if(isLocal()) 0887 { 0888 return QDir().remove(absoluteFilePath()); 0889 } 0890 else 0891 { 0892 return mJobHandler->removeFile(url()); 0893 } 0894 } 0895 0896 bool FileAccess::listDir(DirectoryList* pDirList, bool bRecursive, bool bFindHidden, 0897 const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, 0898 bool bFollowDirLinks, IgnoreList& ignoreList) const 0899 { 0900 assert(mJobHandler != nullptr); 0901 return mJobHandler->listDir(pDirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, 0902 dirAntiPattern, bFollowDirLinks, ignoreList); 0903 } 0904 0905 QString FileAccess::getTempName() const 0906 { 0907 if(mPhysicalPath.isEmpty()) 0908 return m_localCopy; 0909 else 0910 return mPhysicalPath; 0911 } 0912 0913 const QString& FileAccess::errorString() const 0914 { 0915 return getStatusText(); 0916 } 0917 0918 bool FileAccess::open(const QFile::OpenMode flags) 0919 { 0920 bool result; 0921 result = createLocalCopy(); 0922 if(!result) 0923 { 0924 setStatusText(i18n("Creating temp copy of %1 failed.", absoluteFilePath())); 0925 return result; 0926 } 0927 0928 if(m_localCopy.isEmpty() && realFile != nullptr) 0929 { 0930 bool r = realFile->open(flags); 0931 0932 setStatusText(i18n("Opening %1 failed. %2", absoluteFilePath(), realFile->errorString())); 0933 return r; 0934 } 0935 0936 bool r = tmpFile->open(); 0937 setStatusText(i18n("Opening %1 failed. %2", tmpFile->fileName(), tmpFile->errorString())); 0938 return r; 0939 } 0940 0941 qint64 FileAccess::read(char* data, const qint64 maxlen) 0942 { 0943 if(!isNormal()) 0944 { 0945 //This is not an error special files should be skipped 0946 setStatusText(QString()); 0947 return 0; 0948 } 0949 0950 qint64 len = 0; 0951 if(m_localCopy.isEmpty() && realFile != nullptr) 0952 { 0953 len = realFile->read(data, maxlen); 0954 if(len != maxlen) 0955 { 0956 setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), realFile->errorString())); 0957 } 0958 } 0959 else 0960 { 0961 len = tmpFile->read(data, maxlen); 0962 if(len != maxlen) 0963 { 0964 setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), tmpFile->errorString())); 0965 } 0966 } 0967 0968 return len; 0969 } 0970 0971 void FileAccess::close() 0972 { 0973 if(m_localCopy.isEmpty() && realFile != nullptr) 0974 { 0975 realFile->close(); 0976 } 0977 0978 tmpFile->close(); 0979 } 0980 0981 bool FileAccess::createLocalCopy() 0982 { 0983 if(isLocal() || !m_localCopy.isEmpty() || !mPhysicalPath.isEmpty()) 0984 return true; 0985 0986 tmpFile->setAutoRemove(true); 0987 tmpFile->open(); 0988 tmpFile->close(); 0989 m_localCopy = tmpFile->fileName(); 0990 0991 return copyFile(tmpFile->fileName()); 0992 } 0993 0994 //static tempfile Generator 0995 void FileAccess::createTempFile(QTemporaryFile& tmpFile) 0996 { 0997 tmpFile.setAutoRemove(true); 0998 tmpFile.open(); 0999 tmpFile.close(); 1000 } 1001 1002 #if HAS_KFKIO && !defined AUTOTEST 1003 bool FileAccess::makeDir(const QString& dirName) 1004 { 1005 return DefaultFileAccessJobHandler::mkDir(dirName); 1006 } 1007 1008 bool FileAccess::removeDir(const QString& dirName) 1009 { 1010 return DefaultFileAccessJobHandler::rmDir(dirName); 1011 } 1012 #endif // !AUTOTEST 1013 1014 bool FileAccess::symLink(const QString& linkTarget, const QString& linkLocation) 1015 { 1016 if(linkTarget.isEmpty() || linkLocation.isEmpty()) 1017 return false; 1018 return QFile::link(linkTarget, linkLocation); 1019 //DefaultFileAccessJobHandler fh(0); 1020 //return fh.symLink( linkTarget, linkLocation ); 1021 } 1022 1023 bool FileAccess::exists(const QString& name) 1024 { 1025 const FileAccess fa(name); 1026 return fa.exists(); 1027 } 1028 1029 // If the size couldn't be determined by stat() then the file is copied to a local temp file. 1030 qint64 FileAccess::sizeForReading() 1031 { 1032 if(!isLocal() && m_size == 0 && mPhysicalPath.isEmpty()) 1033 { 1034 // Size couldn't be determined. Copy the file to a local temp place. 1035 if(createLocalCopy()) 1036 { 1037 const QString localCopy = tmpFile->fileName(); 1038 const QFileInfo fi(localCopy); 1039 1040 m_size = fi.size(); 1041 m_localCopy = localCopy; 1042 return m_size; 1043 } 1044 else 1045 { 1046 return 0; 1047 } 1048 } 1049 else 1050 return size(); 1051 } 1052 1053 const QString& FileAccess::getStatusText() const 1054 { 1055 return m_statusText; 1056 } 1057 1058 void FileAccess::setStatusText(const QString& s) 1059 { 1060 m_statusText = s; 1061 } 1062 1063 QString FileAccess::cleanPath(const QString& path) // static 1064 { 1065 /* 1066 Tell Qt to treat the supplied path as user input otherwise it will not make useful decisions 1067 about how to convert from the possibly local or remote "path" string to QUrl. 1068 */ 1069 const QUrl url = QUrl::fromUserInput(path, QString(), QUrl::AssumeLocalFile); 1070 1071 if(FileAccess::isLocal(url)) 1072 { 1073 return QDir::cleanPath(path); 1074 } 1075 else 1076 { 1077 return path; 1078 } 1079 } 1080 1081 bool FileAccess::createBackup(const QString& bakExtension) 1082 { 1083 if(exists()) 1084 { 1085 // First rename the existing file to the bak-file. If a bak-file file exists, delete that. 1086 const QString bakName = absoluteFilePath() + bakExtension; 1087 FileAccess bakFile(bakName, true /*bWantToWrite*/); 1088 if(bakFile.exists()) 1089 { 1090 bool bSuccess = bakFile.removeFile(); 1091 if(!bSuccess) 1092 { 1093 setStatusText(i18n("While trying to make a backup, deleting an older backup failed.\nFilename: %1", bakName)); 1094 return false; 1095 } 1096 } 1097 bool bSuccess = rename(bakFile); // krazy:exclude=syscalls 1098 if(!bSuccess) 1099 { 1100 setStatusText(i18n("While trying to make a backup, renaming failed.\nFilenames: %1 -> %2", 1101 absoluteFilePath(), bakName)); 1102 return false; 1103 } 1104 } 1105 return true; 1106 } 1107 1108 void FileAccess::doError() 1109 { 1110 m_bValidData = true; 1111 m_bExists = false; 1112 } 1113 1114 void FileAccess::filterList(const QString& dir, DirectoryList* pDirList, const QString& filePattern, 1115 const QString& fileAntiPattern, const QString& dirAntiPattern, 1116 const IgnoreList& ignoreList) 1117 { 1118 //TODO: Ask os for this information don't hard code it. 1119 #if defined(Q_OS_WIN) 1120 bool bCaseSensitive = false; 1121 #else 1122 bool bCaseSensitive = true; 1123 #endif 1124 1125 // Now remove all entries that should be ignored: 1126 DirectoryList::const_iterator i; 1127 for(i = pDirList->cbegin(); i != pDirList->cend();) 1128 { 1129 DirectoryList::const_iterator i2 = i; 1130 ++i2; 1131 const QString& fileName = i->fileName(); 1132 1133 if((i->isFile() && 1134 (!Utils::wildcardMultiMatch(filePattern, fileName, bCaseSensitive) || 1135 Utils::wildcardMultiMatch(fileAntiPattern, fileName, bCaseSensitive))) || 1136 (i->isDir() && Utils::wildcardMultiMatch(dirAntiPattern, fileName, bCaseSensitive)) || 1137 (ignoreList.matches(dir, fileName, bCaseSensitive))) 1138 { 1139 // Remove it 1140 pDirList->erase(i); 1141 i = i2; 1142 } 1143 else 1144 { 1145 ++i; 1146 } 1147 } 1148 } 1149 1150 //#include "fileaccess.moc"