File indexing completed on 2024-04-21 05:42: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"