From f70d1c10793e59638058c96f1b06a48c5d51262c Mon Sep 17 00:00:00 2001 From: alex-z Date: Wed, 10 Apr 2024 21:54:37 +0200 Subject: [PATCH 1/3] Support Windows .lnk files with VFS Cf API. Signed-off-by: alex-z --- src/common/filesystembase.cpp | 187 ++++++++++++++++++++++- src/common/filesystembase.h | 37 +++++ src/csync/csync_exclude.cpp | 10 +- src/gui/caseclashfilenamedialog.cpp | 5 +- src/gui/conflictsolver.cpp | 14 +- src/gui/folder.cpp | 13 +- src/gui/folderman.cpp | 14 +- src/gui/folderwatcher.cpp | 5 +- src/gui/folderwatcher_win.cpp | 2 +- src/gui/owncloudgui.cpp | 3 +- src/gui/socketapi/socketapi.cpp | 12 +- src/gui/syncrunfilelog.cpp | 7 +- src/gui/tray/usermodel.cpp | 9 +- src/libsync/caseclashconflictsolver.cpp | 2 +- src/libsync/discovery.cpp | 3 +- src/libsync/filesystem.cpp | 2 +- src/libsync/filesystem.h | 5 +- src/libsync/owncloudpropagator.cpp | 9 +- src/libsync/propagatedownload.cpp | 12 +- src/libsync/propagateremotemove.cpp | 5 +- src/libsync/propagateuploadencrypted.cpp | 3 +- src/libsync/propagatorjobs.cpp | 7 +- src/libsync/syncengine.cpp | 8 +- src/libsync/vfs/cfapi/cfapiwrapper.cpp | 7 +- src/libsync/vfs/cfapi/vfs_cfapi.cpp | 22 +-- src/libsync/vfs/suffix/vfs_suffix.cpp | 3 +- test/testsynccfapi.cpp | 8 +- 27 files changed, 311 insertions(+), 103 deletions(-) diff --git a/src/common/filesystembase.cpp b/src/common/filesystembase.cpp index ef9ba1efd6778..42dc8dab06d49 100644 --- a/src/common/filesystembase.cpp +++ b/src/common/filesystembase.cpp @@ -52,8 +52,11 @@ QString FileSystem::longWinPath(const QString &inpath) void FileSystem::setFileHidden(const QString &filename, bool hidden) { + if (filename.isEmpty()) { + return; + } #ifdef _WIN32 - QString fName = longWinPath(filename); + const QString fName = longWinPath(filename); DWORD dwAttrs = 0; dwAttrs = GetFileAttributesW((wchar_t *)fName.utf16()); @@ -71,6 +74,24 @@ void FileSystem::setFileHidden(const QString &filename, bool hidden) #endif } +bool FileSystem::isFileHidden(const QString &filename) +{ +#ifdef _WIN32 + if (isLnkFile(filename)) { + const QString fName = longWinPath(filename); + DWORD dwAttrs = 0; + + dwAttrs = GetFileAttributesW((wchar_t *)fName.utf16()); + + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return false; + } + return dwAttrs & FILE_ATTRIBUTE_HIDDEN; + } +#endif + return QFileInfo(filename).isHidden(); +} + static QFile::Permissions getDefaultWritePermissions() { QFile::Permissions result = QFile::WriteUser; @@ -89,11 +110,27 @@ static QFile::Permissions getDefaultWritePermissions() void FileSystem::setFileReadOnly(const QString &filename, bool readonly) { +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + if (!fileExists(filename)) { + return; + } + const auto permissions = filePermissionsWin(filename); + + std::filesystem::perms allWritePermissions = std::filesystem::perms::_All_write; + static std::filesystem::perms defaultWritePermissions = std::filesystem::perms::others_write; + + std::filesystem::permissions(filename.toStdString(), allWritePermissions, std::filesystem::perm_options::remove); + + if (!readonly) { + std::filesystem::permissions(filename.toStdString(), defaultWritePermissions, std::filesystem::perm_options::add); + } + } +#endif QFile file(filename); QFile::Permissions permissions = file.permissions(); - QFile::Permissions allWritePermissions = - QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther | QFile::WriteOwner; + QFile::Permissions allWritePermissions = QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther | QFile::WriteOwner; static QFile::Permissions defaultWritePermissions = getDefaultWritePermissions(); permissions &= ~allWritePermissions; @@ -116,6 +153,18 @@ void FileSystem::setFolderMinimumPermissions(const QString &filename) bool FileSystem::setFileReadOnlyWeak(const QString &filename, bool readonly) { +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + const auto permissions = filePermissionsWin(filename); + + if (!readonly && static_cast((permissions & std::filesystem::perms::owner_write))) { + return false; // already writable enough + } + + setFileReadOnly(filename, readonly); + return true; + } +#endif QFile file(filename); QFile::Permissions permissions = file.permissions(); @@ -193,7 +242,7 @@ bool FileSystem::uncheckedRenameReplace(const QString &originFileName, #else //Q_OS_WIN // You can not overwrite a read-only file on windows. - if (!QFileInfo(destinationFileName).isWritable()) { + if (!isWritable(destinationFileName)) { setFileReadOnly(destinationFileName, false); } @@ -289,11 +338,24 @@ bool FileSystem::openAndSeekFileSharedRead(QFile *file, QString *errorOrNull, qi } #ifdef Q_OS_WIN +std::filesystem::perms FileSystem::filePermissionsWin(const QString &filename) +{ + return std::filesystem::status(filename.toStdString()).permissions(); +} + +void FileSystem::setFilePermissionsWin(const QString &filename, const std::filesystem::perms &perms) +{ + if (!fileExists(filename)) { + return; + } + std::filesystem::permissions(filename.toStdString(), perms); +} + static bool fileExistsWin(const QString &filename) { WIN32_FIND_DATA FindFileData; HANDLE hFind = nullptr; - QString fName = FileSystem::longWinPath(filename); + const QString fName = FileSystem::longWinPath(filename); hFind = FindFirstFileW((wchar_t *)fName.utf16(), &FindFileData); if (hFind == INVALID_HANDLE_VALUE) { @@ -302,6 +364,25 @@ static bool fileExistsWin(const QString &filename) FindClose(hFind); return true; } + +static bool isDirWin(const QString &filename) +{ + WIN32_FIND_DATA FindFileData; + HANDLE hFind = nullptr; + const QString fName = FileSystem::longWinPath(filename); + + hFind = FindFirstFileW((wchar_t *)fName.utf16(), &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) { + return false; + } + FindClose(hFind); + return FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; +} + +static bool isFileWin(const QString &filename) +{ + return !isDirWin(filename); +} #endif bool FileSystem::fileExists(const QString &filename, const QFileInfo &fileInfo) @@ -323,6 +404,100 @@ bool FileSystem::fileExists(const QString &filename, const QFileInfo &fileInfo) return re; } +bool FileSystem::isDir(const QString &filename, const QFileInfo &fileInfo) +{ +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + // Use a native check. + return isDirWin(filename); + } +#endif + bool re = fileInfo.isDir(); + // if the filename is different from the filename in fileInfo, the fileInfo is + // not valid. There needs to be one initialised here. Otherwise the incoming + // fileInfo is re-used. + if (fileInfo.filePath() != filename) { + QFileInfo myFI(filename); + re = myFI.isDir(); + } + return re; +} + +bool FileSystem::isFile(const QString &filename, const QFileInfo &fileInfo) +{ +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + // Use a native check. + return isFileWin(filename); + } +#endif + bool re = fileInfo.isDir(); + // if the filename is different from the filename in fileInfo, the fileInfo is + // not valid. There needs to be one initialised here. Otherwise the incoming + // fileInfo is re-used. + if (fileInfo.filePath() != filename) { + QFileInfo myFI(filename); + re = myFI.isFile(); + } + return re; +} + +bool FileSystem::isWritable(const QString &filename, const QFileInfo &fileInfo) +{ +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + const auto permissions = filePermissionsWin(filename); + return static_cast((permissions & std::filesystem::perms::owner_write)); + } +#endif + bool re = fileInfo.isWritable(); + // if the filename is different from the filename in fileInfo, the fileInfo is + // not valid. There needs to be one initialised here. Otherwise the incoming + // fileInfo is re-used. + if (fileInfo.filePath() != filename) { + QFileInfo myFI(filename); + re = myFI.isWritable(); + } + return re; +} + +bool FileSystem::isReadable(const QString &filename, const QFileInfo &fileInfo) +{ +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + const auto permissions = filePermissionsWin(filename); + return static_cast((permissions & std::filesystem::perms::owner_read)); + } +#endif + bool re = fileInfo.isReadable(); + // if the filename is different from the filename in fileInfo, the fileInfo is + // not valid. There needs to be one initialised here. Otherwise the incoming + // fileInfo is re-used. + if (fileInfo.filePath() != filename) { + QFileInfo myFI(filename); + re = myFI.isReadable(); + } + return re; +} + +bool FileSystem::isSymLink(const QString &filename, const QFileInfo &fileInfo) +{ +#ifdef Q_OS_WIN + if (isLnkFile(filename)) { + return isJunction(filename); + } +#endif + bool re = fileInfo.isSymLink(); + // if the filename is different from the filename in fileInfo, the fileInfo is + // not valid. There needs to be one initialised here. Otherwise the incoming + // fileInfo is re-used. + if (fileInfo.filePath() != filename) { + QFileInfo myFI(filename); + re = myFI.isSymLink(); + } + return re; +} + #ifdef Q_OS_WIN QString FileSystem::fileSystemForPath(const QString &path) { @@ -351,7 +526,7 @@ bool FileSystem::remove(const QString &fileName, QString *errorString) #ifdef Q_OS_WIN // You cannot delete a read-only file on windows, but we want to // allow that. - if (!QFileInfo(fileName).isWritable()) { + if (!isWritable(fileName)) { setFileReadOnly(fileName, false); } #endif diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h index 6a3baa7afb6e3..8f5554000e079 100644 --- a/src/common/filesystembase.h +++ b/src/common/filesystembase.h @@ -28,6 +28,10 @@ #include +#if !defined(Q_OS_MACOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 +#include +#endif + class QFile; namespace OCC { @@ -53,6 +57,8 @@ namespace FileSystem { */ void OCSYNC_EXPORT setFileHidden(const QString &filename, bool hidden); + bool OCSYNC_EXPORT isFileHidden(const QString &filename); + /** * @brief Marks the file as read-only. * @@ -89,6 +95,34 @@ namespace FileSystem { */ bool OCSYNC_EXPORT fileExists(const QString &filename, const QFileInfo & = QFileInfo()); + /** + * @brief Checks whether it is a dir. + * + * Use this over QFileInfo::isDir() and QFile::isDir() to avoid bugs with lnk + * files, see above. + */ + bool OCSYNC_EXPORT isDir(const QString &filename, const QFileInfo& = QFileInfo()); + + /** + * @brief Checks whether it is a file. + * + * Use this over QFileInfo::isDir() and QFile::isDir() to avoid bugs with lnk + * files, see above. + */ + bool OCSYNC_EXPORT isFile(const QString &filename, const QFileInfo& fileInfo = QFileInfo()); + + /** + * @brief Checks whether the file is writable. + * + * Use this over QFileInfo::isDir() and QFile::isDir() to avoid bugs with lnk + * files, see above. + */ + bool OCSYNC_EXPORT isWritable(const QString &filename, const QFileInfo &fileInfo = QFileInfo()); + + bool OCSYNC_EXPORT isReadable(const QString &filename, const QFileInfo &fileInfo = QFileInfo()); + + bool OCSYNC_EXPORT isSymLink(const QString &filename, const QFileInfo &fileInfo = QFileInfo()); + /** * @brief Rename the file \a originFileName to \a destinationFileName. * @@ -146,6 +180,9 @@ namespace FileSystem { * the windows API functions work with the normal "unixoid" representation too. */ QString OCSYNC_EXPORT pathtoUNC(const QString &str); + + std::filesystem::perms OCSYNC_EXPORT filePermissionsWin(const QString &filename); + void OCSYNC_EXPORT setFilePermissionsWin(const QString &filename, const std::filesystem::perms &perms); #endif /** diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index a0411e7f5c3d4..5d0b462bd4697 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -31,6 +31,7 @@ #include "csync_exclude.h" #include "common/utility.h" +#include "common/filesystembase.h" #include "../version.h" #include @@ -402,7 +403,7 @@ bool ExcludedFiles::isExcluded( while (path.size() > basePath.size()) { QFileInfo fi(path); if (fi.fileName() != QStringLiteral(".sync-exclude.lst") - && (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')))) { + && (FileSystem::isFileHidden(path) || fi.fileName().startsWith(QLatin1Char('.')))) { return true; } @@ -411,9 +412,8 @@ bool ExcludedFiles::isExcluded( } } - QFileInfo fi(filePath); ItemType type = ItemTypeFile; - if (fi.isDir()) { + if (OCC::FileSystem::isDir(filePath)) { type = ItemTypeDirectory; } @@ -437,9 +437,7 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const QString &path, Ite if (filetype == ItemTypeDirectory) { const auto basePath = QString(_localPath + path + QLatin1Char('/')); const QString absolutePath = basePath + QStringLiteral(".sync-exclude.lst"); - QFileInfo excludeFileInfo(absolutePath); - - if (excludeFileInfo.isReadable()) { + if (FileSystem::isReadable(absolutePath)) { addExcludeFilePath(absolutePath); reloadExcludeFiles(); } else { diff --git a/src/gui/caseclashfilenamedialog.cpp b/src/gui/caseclashfilenamedialog.cpp index 27ca251a69911..88a152b98e42d 100644 --- a/src/gui/caseclashfilenamedialog.cpp +++ b/src/gui/caseclashfilenamedialog.cpp @@ -19,6 +19,7 @@ #include "account.h" #include "folder.h" +#include "common/filesystembase.h" #include #include @@ -180,12 +181,12 @@ QString CaseClashFilenameDialog::caseClashConflictFile(const QString &conflictFi while(it.hasNext()) { const auto filePath = it.next(); qCDebug(lcCaseClashConflictFialog) << filePath; - QFileInfo fileInfo(filePath); - if(fileInfo.isDir()) { + if (FileSystem::isDir(filePath)) { continue; } + QFileInfo fileInfo(filePath); const auto currentFileName = fileInfo.fileName(); if (currentFileName.compare(conflictFileName, Qt::CaseInsensitive) == 0 && currentFileName != conflictFileName) { diff --git a/src/gui/conflictsolver.cpp b/src/gui/conflictsolver.cpp index 6a1de573c1e73..780e4087b1dd4 100644 --- a/src/gui/conflictsolver.cpp +++ b/src/gui/conflictsolver.cpp @@ -80,18 +80,19 @@ bool ConflictSolver::deleteLocalVersion() return false; } - QFileInfo info(_localVersionFilename); - if (!info.exists()) { + if (!FileSystem::fileExists(_localVersionFilename)) { return false; } - const auto message = info.isDir() ? tr("Do you want to delete the directory %1 and all its contents permanently?").arg(info.dir().dirName()) + QFileInfo info(_localVersionFilename); + const auto message = FileSystem::isDir(_localVersionFilename) + ? tr("Do you want to delete the directory %1 and all its contents permanently?").arg(info.dir().dirName()) : tr("Do you want to delete the file %1 permanently?").arg(info.fileName()); const auto result = QMessageBox::question(_parentWidget, tr("Confirm deletion"), message, QMessageBox::Yes, QMessageBox::No); if (result != QMessageBox::Yes) return false; - if (info.isDir()) { + if (FileSystem::isDir(_localVersionFilename)) { return FileSystem::removeRecursively(_localVersionFilename); } else { return QFile(_localVersionFilename).remove(); @@ -118,7 +119,7 @@ bool ConflictSolver::renameLocalVersion() const auto targetFilename = [=] { uint i = 1; auto result = renamePattern.arg(i); - while (QFileInfo::exists(result)) { + while (FileSystem::fileExists(result)) { Q_ASSERT(i > 0); i++; result = renamePattern.arg(i); @@ -146,8 +147,7 @@ bool ConflictSolver::overwriteRemoteVersion() return false; } - QFileInfo info(_localVersionFilename); - if (!info.exists()) { + if (!FileSystem::fileExists(_localVersionFilename)) { return false; } diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 5ac1e63e5d009..37d83430ae117 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -175,17 +175,17 @@ void Folder::checkLocalPath() _canonicalLocalPath = Utility::trailingSlashPath(_canonicalLocalPath); - if (fi.isDir() && fi.isReadable()) { + if (FileSystem::isDir(_definition.localPath) && FileSystem::isReadable(_definition.localPath)) { qCDebug(lcFolder) << "Checked local path ok"; } else { // Check directory again if (!FileSystem::fileExists(_definition.localPath, fi)) { _syncResult.appendErrorString(tr("Local folder %1 does not exist.").arg(_definition.localPath)); _syncResult.setStatus(SyncResult::SetupError); - } else if (!fi.isDir()) { + } else if (!FileSystem::isDir(_definition.localPath)) { _syncResult.appendErrorString(tr("%1 should be a folder but is not.").arg(_definition.localPath)); _syncResult.setStatus(SyncResult::SetupError); - } else if (!fi.isReadable()) { + } else if (!FileSystem::isReadable(_definition.localPath)) { _syncResult.appendErrorString(tr("%1 is not readable.").arg(_definition.localPath)); _syncResult.setStatus(SyncResult::SetupError); } @@ -1471,8 +1471,9 @@ void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QS // Note: This assumes we're getting file watcher notifications // for folders only on creation and deletion - if we got a notification // on content change that would create spurious warnings. - QFileInfo fi(_canonicalLocalPath + path); - if (!fi.exists()) + const auto fullPath = _canonicalLocalPath + path; + QFileInfo fi(fullPath); + if (!FileSystem::fileExists(fullPath)) return; bool ok = false; @@ -1482,7 +1483,7 @@ void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QS if (!blacklist.contains(path + "/")) return; - const auto message = fi.isDir() + const auto message = FileSystem::isDir(fullPath) ? tr("The folder %1 was created but was excluded from synchronization previously. " "Data inside it will not be synchronized.") .arg(fi.filePath()) diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index a5d68f1b70a3d..46073c24874a0 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1320,7 +1320,7 @@ QStringList FolderMan::findFileInLocalFolders(const QString &relPath, const Acco QString path = folder->cleanPath() + '/'; path += serverPath.mid(folder->remotePathTrailingSlash().length()); - if (QFile::exists(path)) { + if (FileSystem::fileExists(path)) { re.append(path); } } @@ -1754,19 +1754,19 @@ static QString checkPathValidityRecursive(const QString &path) #endif const QFileInfo selFile(path); - if (!selFile.exists()) { + if (!FileSystem::fileExists(path)) { QString parentPath = selFile.dir().path(); if (parentPath != path) return checkPathValidityRecursive(parentPath); return FolderMan::tr("The selected path does not exist!"); } - if (!selFile.isDir()) { + if (!FileSystem::isDir(path)) { return FolderMan::tr("The selected path is not a folder!"); } #ifdef Q_OS_WIN - if (!selFile.isWritable()) { + if (!FileSystem::isWritable(path)) { // isWritable() doesn't cover all NTFS permissions // try creating and removing a test file, and make sure it is excluded from sync if (!Utility::canCreateFileInPath(path)) { @@ -1774,7 +1774,7 @@ static QString checkPathValidityRecursive(const QString &path) } } #else - if (!selFile.isWritable()) { + if (!FileSystem::isWritable(path)) { return FolderMan::tr("You have no permission to write to the selected folder!"); } #endif @@ -1787,7 +1787,7 @@ static QString checkPathValidityRecursive(const QString &path) static QString canonicalPath(const QString &path) { QFileInfo selFile(path); - if (!selFile.exists()) { + if (!FileSystem::fileExists(path)) { const auto parentPath = selFile.dir().path(); // It's possible for the parentPath to match the path @@ -1880,7 +1880,7 @@ QString FolderMan::findGoodPathForNewSyncFolder(const QString &basePath, const Q int attempt = 1; forever { const auto isGood = FolderMan::instance()->checkPathValidityForNewFolder(folder, serverUrl).second.isEmpty() && - (allowExisting == GoodPathStrategy::AllowOverrideExistingPath || !QFileInfo::exists(folder)); + (allowExisting == GoodPathStrategy::AllowOverrideExistingPath || !FileSystem::fileExists(folder)); if (isGood) { break; } diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 237d6454f8539..fc7d05d685a45 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -84,7 +84,7 @@ void FolderWatcher::appendSubPaths(QDir dir, QStringList& subPaths) { QString path = dir.path() + "/" + newSubPaths[i]; QFileInfo fileInfo(path); subPaths.append(path); - if (fileInfo.isDir()) { + if (FileSystem::isDir(path)) { QDir dir(path); appendSubPaths(dir, subPaths); } @@ -176,9 +176,8 @@ int FolderWatcher::lockChangeDebouncingTimout() const void FolderWatcher::changeDetected(const QString &path) { - QFileInfo fileInfo(path); QStringList paths(path); - if (fileInfo.isDir()) { + if (FileSystem::isDir(path)) { QDir dir(path); appendSubPaths(dir, paths); } diff --git a/src/gui/folderwatcher_win.cpp b/src/gui/folderwatcher_win.cpp index bb3ea49fec5eb..b89d23808c5db 100644 --- a/src/gui/folderwatcher_win.cpp +++ b/src/gui/folderwatcher_win.cpp @@ -152,7 +152,7 @@ void WatcherThread::watchChanges(size_t fileNotifyBufferSize, // and new files in a folder, probably because of the folder's mtime // changing. We don't need them. const bool skip = curEntry->Action == FILE_ACTION_MODIFIED - && QFileInfo(longfile).isDir(); + && FileSystem::isDir(longfile); if (!skip) { emit changed(longfile); diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 321e43b600d84..f55b60f14fc64 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -40,6 +40,7 @@ #include "tray/sortedactivitylistmodel.h" #include "tray/syncstatussummary.h" #include "tray/unifiedsearchresultslistmodel.h" +#include "filesystem.h" #ifdef WITH_LIBCLOUDPROVIDERS #include "cloudproviders/cloudprovidermanager.h" @@ -513,7 +514,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & Folder *f = FolderMan::instance()->folder(folder); if (f) { QString fullPath = f->path() + '/' + progress._lastCompletedItem._file; - if (QFile(fullPath).exists()) { + if (FileSystem::fileExists(fullPath)) { connect(action, &QAction::triggered, this, [this, fullPath] { this->slotOpenPath(fullPath); }); } else { action->setEnabled(false); diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index e3914c1276199..b4b5a913a28ce 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -1058,8 +1058,8 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *) // If the parent doesn't accept new files, go to the root of the sync folder QFileInfo fileInfo(localFile); const auto parentRecord = parentDir.journalRecord(); - if ((fileInfo.isFile() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile)) - || (fileInfo.isDir() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) { + if ((FileSystem::isFile(localFile) && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile)) + || (FileSystem::isDir(localFile) && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) { defaultDirAndName = QFileInfo(defaultDirAndName).fileName(); } @@ -1234,7 +1234,7 @@ void SocketApi::sendEncryptFolderCommandMenuEntries(const QFileInfo &fileInfo, !fileData.folder->accountState() || !fileData.folder->accountState()->account() || !fileData.folder->accountState()->account()->capabilities().clientSideEncryptionAvailable() || - !fileInfo.isDir() || + !FileSystem::isDir(fileInfo.absoluteFilePath()) || isE2eEncryptedPath) { return; } @@ -1262,7 +1262,7 @@ void SocketApi::sendLockFileCommandMenuEntries(const QFileInfo &fileInfo, const FileData &fileData, const OCC::SocketListener* const listener) const { - if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable()) { + if (!FileSystem::isDir(fileInfo.absoluteFilePath()) && syncFolder->accountState()->account()->capabilities().filesLockAvailable()) { if (syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::UnlockedItem) { listener->sendMessage(QLatin1String("MENU_ITEM:LOCK_FILE::") + tr("Lock file")); } else { @@ -1280,7 +1280,7 @@ void SocketApi::sendLockFileInfoMenuEntries(const QFileInfo &fileInfo, const SyncJournalFileRecord &record) const { static constexpr auto SECONDS_PER_MINUTE = 60; - if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable() && + if (!FileSystem::isDir(fileInfo.absoluteFilePath()) && syncFolder->accountState()->account()->capabilities().filesLockAvailable() && syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::LockedItem) { listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_OWNER:d:") + tr("Locked by %1").arg(record._lockstate._lockOwnerDisplayName)); const auto lockExpirationTime = record._lockstate._lockTime + record._lockstate._lockTimeout; @@ -1381,7 +1381,7 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe const QFileInfo fileInfo(fileData.localPath); sendLockFileInfoMenuEntries(fileInfo, syncFolder, fileData, listener, record); - if (!fileInfo.isDir()) { + if (!FileSystem::isDir(fileData.localPath)) { listener->sendMessage(QLatin1String("MENU_ITEM:ACTIVITY") + flagString + tr("Activity")); } diff --git a/src/gui/syncrunfilelog.cpp b/src/gui/syncrunfilelog.cpp index 5246b0f11360e..5610fb6a02ff7 100644 --- a/src/gui/syncrunfilelog.cpp +++ b/src/gui/syncrunfilelog.cpp @@ -42,7 +42,7 @@ void SyncRunFileLog::start(const QString &folderPath) QString filename = logpath + QLatin1String("/") + filenameSingle + QLatin1String("_sync.log"); int depthIndex = 2; - while(QFile::exists(filename)) { + while (FileSystem::fileExists(filename)) { QFile file(filename); file.open(QIODevice::ReadOnly| QIODevice::Text); @@ -65,9 +65,8 @@ void SyncRunFileLog::start(const QString &folderPath) } // When the file is too big, just rename it to an old name. - QFileInfo info(filename); - bool exists = info.exists(); - if (exists && info.size() > logfileMaxSize) { + bool exists = FileSystem::fileExists(filename); + if (exists && FileSystem::getSize(filename) > logfileMaxSize) { exists = false; QString newFilename = filename + QLatin1String(".1"); QFile::remove(newFilename); diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index 908f760bdd896..a817d2a7ce62e 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -1,5 +1,6 @@ #include "notificationhandler.h" #include "usermodel.h" +#include "common/filesystembase.h" #include "accountmanager.h" #include "owncloudgui.h" @@ -576,24 +577,24 @@ void User::slotProgressInfo(const QString &folder, const ProgressInfo &progress) continue; } - if (activity._syncFileItemStatus == SyncFileItem::Conflict && !QFileInfo::exists(f->path() + activity._file)) { + if (activity._syncFileItemStatus == SyncFileItem::Conflict && !FileSystem::fileExists(f->path() + activity._file)) { _activityModel->removeActivityFromActivityList(activity); continue; } - if (activity._syncFileItemStatus == SyncFileItem::FileLocked && !QFileInfo::exists(f->path() + activity._file)) { + if (activity._syncFileItemStatus == SyncFileItem::FileLocked && !FileSystem::fileExists(f->path() + activity._file)) { _activityModel->removeActivityFromActivityList(activity); continue; } - if (activity._syncFileItemStatus == SyncFileItem::FileIgnored && !QFileInfo::exists(f->path() + activity._file)) { + if (activity._syncFileItemStatus == SyncFileItem::FileIgnored && !FileSystem::fileExists(f->path() + activity._file)) { _activityModel->removeActivityFromActivityList(activity); continue; } - if (!QFileInfo::exists(f->path() + activity._file)) { + if (!FileSystem::fileExists(f->path() + activity._file)) { _activityModel->removeActivityFromActivityList(activity); continue; } diff --git a/src/libsync/caseclashconflictsolver.cpp b/src/libsync/caseclashconflictsolver.cpp index 753c2a7c0fc68..dc758ba4ed4cf 100644 --- a/src/libsync/caseclashconflictsolver.cpp +++ b/src/libsync/caseclashconflictsolver.cpp @@ -47,7 +47,7 @@ CaseClashConflictSolver::CaseClashConflictSolver(const QString &targetFilePath, #if !defined(QT_NO_DEBUG) QFileInfo targetFileInfo(_targetFilePath); Q_ASSERT(targetFileInfo.isAbsolute()); - Q_ASSERT(QFileInfo::exists(_conflictFilePath)); + Q_ASSERT(FileSystem::fileExists(_conflictFilePath)); #endif } diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 23b743a46c6c2..22a49f278f376 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -606,7 +606,6 @@ void ProcessDirectoryJob::postProcessServerNew(const SyncFileItemPtr &item, if (!localEntry.isValid() && item->_type == ItemTypeFile && opts._vfs->mode() != Vfs::Off && - !FileSystem::isLnkFile(item->_file) && _pinState != PinState::AlwaysLocal && !FileSystem::isExcludeFile(item->_file)) { @@ -2165,7 +2164,7 @@ bool ProcessDirectoryJob::isVfsWithSuffix() const void ProcessDirectoryJob::computePinState(PinState parentState) { _pinState = parentState; - if (_queryLocal != ParentDontExist && QFileInfo::exists(_discoveryData->_localDir + _currentFolder._local)) { + if (_queryLocal != ParentDontExist && FileSystem::fileExists(_discoveryData->_localDir + _currentFolder._local)) { if (auto state = _discoveryData->_syncOptions._vfs->pinState(_currentFolder._local)) // ouch! pin local or original? _pinState = *state; } diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 866eccdef12ef..cc65bf1363e00 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -268,7 +268,7 @@ bool FileSystem::removeRecursively(const QString &path, const std::function #include -#if !defined(Q_OS_MACOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 -#include -#endif +#include class QFile; @@ -96,7 +94,6 @@ namespace FileSystem { bool OWNCLOUDSYNC_EXPORT fileChanged(const QString &fileName, qint64 previousSize, time_t previousMtime); - /** * @brief Like !fileChanged() but with verbose logging if the file *did* change. */ diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 4f41e124ed202..d83aedf242128 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -1070,6 +1070,7 @@ Result OwncloudPropagator::staticUpdat if (!dBresult) { return dBresult.error(); } + const auto result = vfs->convertToPlaceholder(fsPath, item, {}, updateType); if (!result) { return result.error(); @@ -1460,13 +1461,13 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status) !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) && !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) { try { - if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) { + if (FileSystem::fileExists(propagator()->fullLocalPath(_item->_file))) { FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadOnly); qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); } - if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) { + if (!_item->_renameTarget.isEmpty() && FileSystem::fileExists(propagator()->fullLocalPath(_item->_renameTarget))) { FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadOnly); qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); @@ -1485,13 +1486,13 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status) !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) || !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) { try { - if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) { + if (FileSystem::fileExists(propagator()->fullLocalPath(_item->_file))) { FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadWrite); qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); } - if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) { + if (!_item->_renameTarget.isEmpty() && FileSystem::fileExists(propagator()->fullLocalPath(_item->_renameTarget))) { FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadWrite); qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 84240ba6418dd..a36a11ccd4992 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -1007,7 +1007,7 @@ void PropagateDownloadFile::checksumValidateFailedAbortDownload(const QString &e void PropagateDownloadFile::deleteExistingFolder() { QString existingDir = propagator()->fullLocalPath(_item->_file); - if (!QFileInfo(existingDir).isDir()) { + if (!FileSystem::isDir(existingDir)) { return; } @@ -1204,9 +1204,17 @@ void PropagateDownloadFile::downloadFinished() if (previousFileExists) { // Preserve the existing file permissions. const auto existingFile = QFileInfo{filename}; +#ifdef Q_OS_WIN + const auto existingPermissions = FileSystem::filePermissionsWin(filename); + const auto tmpFilePermissions = FileSystem::filePermissionsWin(_tmpFile.fileName()); + if (existingPermissions != tmpFilePermissions) { + FileSystem::setFilePermissionsWin(_tmpFile.fileName(), existingPermissions); + } +#else if (existingFile.permissions() != _tmpFile.permissions()) { _tmpFile.setPermissions(existingFile.permissions()); } +#endif preserveGroupOwnership(_tmpFile.fileName(), existingFile); // Make the file a hydrated placeholder if possible @@ -1228,7 +1236,7 @@ void PropagateDownloadFile::downloadFinished() } const auto isConflict = (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT - && (QFileInfo(filename).isDir() || !FileSystem::fileEquals(filename, _tmpFile.fileName()))) || + && (FileSystem::isDir(filename) || !FileSystem::fileEquals(filename, _tmpFile.fileName()))) || _item->_instruction == CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT; if (isConflict) { diff --git a/src/libsync/propagateremotemove.cpp b/src/libsync/propagateremotemove.cpp index 09cb179d385a9..401a8f27bf124 100644 --- a/src/libsync/propagateremotemove.cpp +++ b/src/libsync/propagateremotemove.cpp @@ -18,6 +18,7 @@ #include "account.h" #include "common/syncjournalfilerecord.h" #include "filesystem.h" +#include "common/filesystembase.h" #include "common/asserts.h" #include #include @@ -253,7 +254,7 @@ void PropagateRemoteMove::finalize() const auto targetFile = propagator()->fullLocalPath(_item->_renameTarget); - if (QFileInfo::exists(targetFile)) { + if (FileSystem::fileExists(targetFile)) { // Delete old db data. if (!propagator()->_journal->deleteFileRecord(_item->_originalFile)) { qCWarning(lcPropagateRemoteMove) << "could not delete file from local DB" << _item->_originalFile; @@ -277,7 +278,7 @@ void PropagateRemoteMove::finalize() } } - if (!QFileInfo::exists(targetFile)) { + if (!FileSystem::fileExists(targetFile)) { propagator()->_journal->commit("Remote Rename"); done(SyncFileItem::Success, {}, ErrorCategory::NoError); return; diff --git a/src/libsync/propagateuploadencrypted.cpp b/src/libsync/propagateuploadencrypted.cpp index c64483ed6f1ed..6999459fbc686 100644 --- a/src/libsync/propagateuploadencrypted.cpp +++ b/src/libsync/propagateuploadencrypted.cpp @@ -4,6 +4,7 @@ #include "clientsideencryption.h" #include "foldermetadata.h" #include "encryptedfoldermetadatahandler.h" +#include "filesystem.h" #include "account.h" #include #include @@ -182,7 +183,7 @@ void PropagateUploadEncrypted::slotUploadMetadataFinished(int statusCode, const qCDebug(lcPropagateUploadEncrypted) << "Finalizing the upload part, now the actuall uploader will take over"; emit finalized(Utility::trailingSlashPath(outputInfo.path()) + outputInfo.fileName(), Utility::trailingSlashPath(_remoteParentPath) + outputInfo.fileName(), - outputInfo.size()); + FileSystem::getSize(_completeFileName)); } } // namespace OCC \ No newline at end of file diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 4b18a944c7f61..313d4b334080f 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -163,8 +163,7 @@ void PropagateLocalMkdir::startLocalMkdir() // When turning something that used to be a file into a directory // we need to delete the file first. - QFileInfo fi(newDirStr); - if (fi.exists() && fi.isFile()) { + if (FileSystem::fileExists(newDirStr) && FileSystem::isFile(newDirStr)) { if (_deleteExistingFile) { QString removeError; if (!FileSystem::remove(newDirStr, &removeError)) { @@ -282,7 +281,7 @@ void PropagateLocalRename::start() const auto existingFile = propagator()->fullLocalPath(previousNameInDb); const auto targetFile = propagator()->fullLocalPath(_item->_renameTarget); - const auto fileAlreadyMoved = !QFileInfo::exists(propagator()->fullLocalPath(_item->_originalFile)) && QFileInfo::exists(existingFile); + const auto fileAlreadyMoved = !FileSystem::fileExists(propagator()->fullLocalPath(_item->_originalFile)) && FileSystem::fileExists(existingFile); auto pinState = OCC::PinState::Unspecified; if (!fileAlreadyMoved) { auto pinStateResult = vfs->pinState(propagator()->adjustRenamedPath(_item->_file)); @@ -294,7 +293,7 @@ void PropagateLocalRename::start() // if the file is a file underneath a moved dir, the _item->file is equal // to _item->renameTarget and the file is not moved as a result. qCDebug(lcPropagateLocalRename) << _item->_file << _item->_renameTarget << _item->_originalFile << previousNameInDb << (fileAlreadyMoved ? "original file has already moved" : "original file is still there"); - Q_ASSERT(QFileInfo::exists(propagator()->fullLocalPath(_item->_originalFile)) || QFileInfo::exists(existingFile)); + Q_ASSERT(FileSystem::fileExists(propagator()->fullLocalPath(_item->_originalFile)) || FileSystem::fileExists(existingFile)); if (_item->_file != _item->_renameTarget) { propagator()->reportProgress(*_item, 0); qCDebug(lcPropagateLocalRename) << "MOVE " << existingFile << " => " << targetFile; diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 3470504e2709f..3b7b01f86de87 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -287,7 +287,7 @@ void SyncEngine::conflictRecordMaintenance() const auto conflictRecordPaths = _journal->conflictRecordPaths(); for (const auto &path : conflictRecordPaths) { auto fsPath = _propagator->fullLocalPath(QString::fromUtf8(path)); - if (!QFileInfo::exists(fsPath)) { + if (!FileSystem::fileExists(fsPath)) { _journal->deleteConflictRecord(path); } } @@ -326,7 +326,7 @@ void SyncEngine::caseClashConflictRecordMaintenance() const auto conflictRecordPaths = _journal->caseClashConflictRecordPaths(); for (const auto &path : conflictRecordPaths) { const auto fsPath = _propagator->fullLocalPath(QString::fromUtf8(path)); - if (!QFileInfo::exists(fsPath)) { + if (!FileSystem::fileExists(fsPath)) { _journal->deleteCaseClashConflictByPathRecord(path); } } @@ -640,7 +640,7 @@ void SyncEngine::startSync() _discoveryPhase->_account = _account; _discoveryPhase->_excludes = _excludedFiles.data(); const QString excludeFilePath = _localPath + QStringLiteral(".sync-exclude.lst"); - if (QFile::exists(excludeFilePath)) { + if (FileSystem::fileExists(excludeFilePath)) { _discoveryPhase->_excludes->addExcludeFilePath(excludeFilePath); _discoveryPhase->_excludes->reloadExcludeFiles(); } @@ -1237,7 +1237,7 @@ void SyncEngine::wipeVirtualFiles(const QString &localPath, SyncJournalDb &journ // If the local file is a dehydrated placeholder, wipe it too. // Otherwise leave it to allow the next sync to have a new-new conflict. QString localFile = localPath + rec._path; - if (QFile::exists(localFile) && vfs.isDehydratedPlaceholder(localFile)) { + if (FileSystem::fileExists(localFile) && vfs.isDehydratedPlaceholder(localFile)) { qCDebug(lcEngine) << "Removing local dehydrated placeholder" << rec.path(); QFile::remove(localFile); } diff --git a/src/libsync/vfs/cfapi/cfapiwrapper.cpp b/src/libsync/vfs/cfapi/cfapiwrapper.cpp index 00aee1190829f..a483d2c066383 100644 --- a/src/libsync/vfs/cfapi/cfapiwrapper.cpp +++ b/src/libsync/vfs/cfapi/cfapiwrapper.cpp @@ -784,12 +784,11 @@ OCC::CfApiWrapper::FileHandle OCC::CfApiWrapper::handleForPath(const QString &pa return {}; } - QFileInfo pathFileInfo(path); - if (!pathFileInfo.exists()) { + if (!FileSystem::fileExists(path)) { return {}; } - if (pathFileInfo.isDir()) { + if (FileSystem::isDir(path)) { HANDLE handle = nullptr; const qint64 openResult = CfOpenFileWithOplock(path.toStdWString().data(), CF_OPEN_FILE_FLAG_NONE, &handle); if (openResult == S_OK) { @@ -797,7 +796,7 @@ OCC::CfApiWrapper::FileHandle OCC::CfApiWrapper::handleForPath(const QString &pa } else { qCWarning(lcCfApiWrapper) << "Could not open handle for " << path << " result: " << QString::fromWCharArray(_com_error(openResult).ErrorMessage()); } - } else if (pathFileInfo.isFile()) { + } else if (FileSystem::isFile(path)) { const auto longpath = OCC::FileSystem::longWinPath(path); const auto handle = CreateFile(longpath.toStdWString().data(), 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); diff --git a/src/libsync/vfs/cfapi/vfs_cfapi.cpp b/src/libsync/vfs/cfapi/vfs_cfapi.cpp index 1b91250222575..414caed1bf08c 100644 --- a/src/libsync/vfs/cfapi/vfs_cfapi.cpp +++ b/src/libsync/vfs/cfapi/vfs_cfapi.cpp @@ -21,6 +21,7 @@ #include "hydrationjob.h" #include "syncfileitem.h" #include "filesystem.h" +#include "common/filesystembase.h" #include "common/syncjournaldb.h" #include "config.h" @@ -48,7 +49,7 @@ bool registerShellExtension() // assume CFAPI_SHELL_EXTENSIONS_LIB_NAME is always in the same folder as the main executable // assume CFAPI_SHELL_EXTENSIONS_LIB_NAME is always in the same folder as the main executable const auto shellExtensionDllPath = QDir::toNativeSeparators(QString(QCoreApplication::applicationDirPath() + QStringLiteral("/") + CFAPI_SHELL_EXTENSIONS_LIB_NAME + QStringLiteral(".dll"))); - if (!QFileInfo::exists(shellExtensionDllPath)) { + if (!OCC::FileSystem::fileExists(shellExtensionDllPath)) { Q_ASSERT(false); qCWarning(lcCfApi) << "Register CfAPI shell extensions failed. Dll does not exist in " << QCoreApplication::applicationDirPath(); return false; @@ -236,16 +237,6 @@ Result VfsCfApi::dehydratePlaceholder(const SyncFileItem &item) Result VfsCfApi::convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType) { const auto localPath = QDir::toNativeSeparators(filename); - - if (item._type != ItemTypeDirectory && OCC::FileSystem::isLnkFile(filename)) { - qCInfo(lcCfApi) << "File \"" << filename << "\" is a Windows shortcut. Not converting it to a placeholder."; - const auto pinState = pinStateLocal(localPath); - if (!pinState || *pinState != PinState::Excluded) { - setPinStateLocal(localPath, PinState::Excluded); - } - return Vfs::ConvertToPlaceholderResult::Ok; - } - const auto replacesPath = QDir::toNativeSeparators(replacesFile); if (cfapi::findPlaceholderInfo(localPath)) { @@ -281,8 +272,6 @@ bool VfsCfApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData) const auto hasReparsePoint = (ffd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; const auto hasCloudTag = hasReparsePoint && (ffd->dwReserved0 & ~IO_REPARSE_TAG_CLOUD_MASK) == (IO_REPARSE_TAG_CLOUD & ~IO_REPARSE_TAG_CLOUD_MASK); - const auto isWindowsShortcut = !isDirectory && FileSystem::isLnkFile(stat->path); - const auto isExcludeFile = !isDirectory && FileSystem::isExcludeFile(stat->path); stat->is_metadata_missing = !hasCloudTag; @@ -298,7 +287,7 @@ bool VfsCfApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData) } else if (isSparseFile && isPinned) { stat->type = ItemTypeVirtualFileDownload; return true; - } else if (!isSparseFile && isUnpinned && !isWindowsShortcut && !isExcludeFile) { + } else if (!isSparseFile && isUnpinned && !isExcludeFile) { stat->type = ItemTypeVirtualFileDehydration; return true; } else if (isSparseFile) { @@ -522,9 +511,10 @@ int VfsCfApi::finalizeHydrationJob(const QString &requestId) VfsCfApi::HydratationAndPinStates VfsCfApi::computeRecursiveHydrationAndPinStates(const QString &folderPath, const Optional &basePinState) { Q_ASSERT(!folderPath.endsWith('/')); + const auto fullPath = params().filesystemPath + folderPath; QFileInfo info(params().filesystemPath + folderPath); - if (!info.exists()) { + if (!FileSystem::fileExists(fullPath)) { return {}; } const auto effectivePin = pinState(folderPath); @@ -533,7 +523,7 @@ VfsCfApi::HydratationAndPinStates VfsCfApi::computeRecursiveHydrationAndPinState : (*effectivePin == *basePinState) ? *effectivePin : PinState::Inherited; - if (info.isDir()) { + if (FileSystem::isDir(fullPath)) { const auto dirState = HydratationAndPinStates { pinResult, {} diff --git a/src/libsync/vfs/suffix/vfs_suffix.cpp b/src/libsync/vfs/suffix/vfs_suffix.cpp index 71dd2b9774b37..59c868b31fb69 100644 --- a/src/libsync/vfs/suffix/vfs_suffix.cpp +++ b/src/libsync/vfs/suffix/vfs_suffix.cpp @@ -150,8 +150,7 @@ bool VfsSuffix::isDehydratedPlaceholder(const QString &filePath) { if (!filePath.endsWith(fileSuffix())) return false; - QFileInfo fi(filePath); - return fi.exists() && fi.size() == 1; + return FileSystem::fileExists(filePath) && FileSystem::getSize(filePath) == 1; } bool VfsSuffix::statTypeVirtualFile(csync_file_stat_t *stat, void *) diff --git a/test/testsynccfapi.cpp b/test/testsynccfapi.cpp index 25163554601fa..a3d84cbca8765 100644 --- a/test/testsynccfapi.cpp +++ b/test/testsynccfapi.cpp @@ -1363,9 +1363,8 @@ private slots: QVERIFY(!localFileLocked.isWritable()); } - void testLinkFileDoesNotConvertToPlaceholder() + void testLinkFileDownload() { - // inspired by GH issue #6041 FakeFolder fakeFolder{FileInfo{}}; auto vfs = setupVfs(fakeFolder); @@ -1375,8 +1374,11 @@ private slots: QVERIFY(fakeFolder.syncOnce()); ItemCompletedSpy completeSpy(fakeFolder); QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!vfs->pinState("linkfile.lnk").isValid() || vfs->pinState("linkfile.lnk").get() == PinState::Excluded); + QVERIFY(vfs->pinState("linkfile.lnk").isValid()); QVERIFY(itemInstruction(completeSpy, "linkfile.lnk", CSYNC_INSTRUCTION_NONE)); + triggerDownload(fakeFolder, "linkfile.lnk"); + QVERIFY(fakeFolder.syncOnce()); + QVERIFY(itemInstruction(completeSpy, "linkfile.lnk", CSYNC_INSTRUCTION_SYNC)); } void testFolderDoesNotUpdatePlaceholderMetadata() From 7501a03db6f061d5044ff667debdb97ad8e43fc6 Mon Sep 17 00:00:00 2001 From: alex-z Date: Mon, 15 Apr 2024 06:44:06 +0200 Subject: [PATCH 2/3] Unit tests. Test .lnk file with a real shortcut file. Test will hang if QFileInfo is used on .lnk files. Signed-off-by: alex-z --- test/CMakeLists.txt | 1 + test/fakeshortcut.base64 | 1 + test/testsynccfapi.cpp | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 test/fakeshortcut.base64 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ca00593ac161b..75eaaf7049e26 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -101,6 +101,7 @@ configure_file(fakefiledrope2eefoldermetadata.json "${PROJECT_BINARY_DIR}/bin/fa configure_file(e2etestsfakecert.pem "${PROJECT_BINARY_DIR}/bin/e2etestsfakecert.pem" COPYONLY) configure_file(e2etestsfakecertprivatekey.pem "${PROJECT_BINARY_DIR}/bin/e2etestsfakecertprivatekey.pem" COPYONLY) configure_file(e2etestsfakecertpublickey.pem "${PROJECT_BINARY_DIR}/bin/e2etestsfakecertpublickey.pem" COPYONLY) +configure_file(fake2eelocksucceeded.json "${PROJECT_BINARY_DIR}/bin/fakeshortcut.base64" COPYONLY) if(ADD_E2E_TESTS) diff --git a/test/fakeshortcut.base64 b/test/fakeshortcut.base64 new file mode 100644 index 0000000000000..d8238b664e110 --- /dev/null +++ b/test/fakeshortcut.base64 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/test/testsynccfapi.cpp b/test/testsynccfapi.cpp index a3d84cbca8765..a306bc44e2b25 100644 --- a/test/testsynccfapi.cpp +++ b/test/testsynccfapi.cpp @@ -1368,6 +1368,8 @@ private slots: FakeFolder fakeFolder{FileInfo{}}; auto vfs = setupVfs(fakeFolder); + qInfo("Starting .lnk test. It might hand and will get killed after timeout..."); + // Create a Windows shortcut (.lnk) file fakeFolder.remoteModifier().insert("linkfile.lnk"); @@ -1379,6 +1381,33 @@ private slots: triggerDownload(fakeFolder, "linkfile.lnk"); QVERIFY(fakeFolder.syncOnce()); QVERIFY(itemInstruction(completeSpy, "linkfile.lnk", CSYNC_INSTRUCTION_SYNC)); + + // a real .lnk file contents stored as base64 for tests + QFile fakeShortcutBase64(QStringLiteral("fakeshortcut.base64")); + QVERIFY(fakeShortcutBase64.open(QFile::ReadOnly)); + const auto fakeShortcutBase64Binary = QByteArray::fromBase64(fakeShortcutBase64.readAll()); + fakeShortcutBase64.close(); + + // fill the .lnk file with binary data from real shortcut and turn it into OnlineOnly file + const QString shortcutFilePathOnDisk = fakeFolder.localPath() + "linkfile.lnk"; + QFile shorcutFileOnDisk(shortcutFilePathOnDisk); + QVERIFY(shorcutFileOnDisk.open(QFile::WriteOnly)); + QVERIFY(shorcutFileOnDisk.write(fakeShortcutBase64Binary)); + shorcutFileOnDisk.close(); + + // run tests on it + ::setPinState(shortcutFilePathOnDisk, PinState::OnlineOnly, cfapi::NoRecurse); + QVERIFY(fakeFolder.syncOnce()); + QVERIFY(vfs->pinState("linkfile.lnk").isValid()); + QVERIFY(itemInstruction(completeSpy, "linkfile.lnk", CSYNC_INSTRUCTION_SYNC)); + + // trigget download of online only .lnk file + triggerDownload(fakeFolder, "linkfile.lnk"); + QVERIFY(fakeFolder.syncOnce()); + QVERIFY(vfs->pinState("linkfile.lnk").isValid()); + QVERIFY(itemInstruction(completeSpy, "linkfile.lnk", CSYNC_INSTRUCTION_SYNC)); + + qInfo("Finishing .lnk test"); } void testFolderDoesNotUpdatePlaceholderMetadata() From 28d69e9e2c7157f1b18884b0de819c0061ec6178 Mon Sep 17 00:00:00 2001 From: alex-z Date: Fri, 19 Apr 2024 00:20:37 +0200 Subject: [PATCH 3/3] Bugfix. Migrate non-placeholder shortcuts to placeholder version. Signed-off-by: alex-z --- src/libsync/discovery.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 22a49f278f376..2b55eaf1c1d2f 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -1136,7 +1136,12 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( } else if (dbEntry._type == ItemTypeVirtualFileDehydration || localEntry.type == ItemTypeVirtualFileDehydration) { item->_direction = SyncFileItem::Down; item->_instruction = CSYNC_INSTRUCTION_SYNC; - item->_type = ItemTypeVirtualFileDehydration; + const auto pinState = _discoveryData->_syncOptions._vfs->pinState(path._local); + if (FileSystem::isLnkFile(path._local) && !_discoveryData->_syncOptions._vfs->pinState(path._local).isValid()) { + item->_type = ItemTypeVirtualFileDownload; + } else { + item->_type = ItemTypeVirtualFileDehydration; + } } else if (!serverModified && (dbEntry._inode != localEntry.inode || (localEntry.isMetadataMissing && item->_type == ItemTypeFile && !FileSystem::isLnkFile(item->_file))