diff options
-rw-r--r-- | src/common/syncjournaldb.cpp | 16 | ||||
-rw-r--r-- | src/common/syncjournaldb.h | 7 | ||||
-rw-r--r-- | src/common/utility.cpp | 2 | ||||
-rw-r--r-- | src/common/utility.h | 8 | ||||
-rw-r--r-- | src/gui/socketapi.cpp | 116 | ||||
-rw-r--r-- | src/gui/socketapi.h | 3 | ||||
-rw-r--r-- | src/libsync/filesystem.h | 1 | ||||
-rw-r--r-- | src/libsync/syncengine.cpp | 2 | ||||
-rw-r--r-- | test/testsyncconflict.cpp | 4 |
9 files changed, 150 insertions, 9 deletions
diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index e052b8502..a8578effa 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -1954,6 +1954,22 @@ QByteArrayList SyncJournalDb::conflictRecordPaths() return paths; } +QByteArray SyncJournalDb::conflictFileBaseName(const QByteArray &conflictName) +{ + auto conflict = conflictRecord(conflictName); + QByteArray result; + if (conflict.isValid()) { + getFileRecordsByFileId(conflict.baseFileId, [&result](const SyncJournalFileRecord &record) { + if (!record._path.isEmpty()) + result = record._path; + }); + } + + if (result.isEmpty()) + result = Utility::conflictFileBaseNameFromPattern(conflictName); + return result; +} + void SyncJournalDb::clearFileTable() { QMutexLocker lock(&_mutex); diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index 4db859bbe..6f174b46d 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -237,6 +237,13 @@ public: /// Return all paths of files with a conflict tag in the name and records in the db QByteArrayList conflictRecordPaths(); + /** Find the base name for a conflict file name, using journal or name pattern + * + * The path must by sync-folder relative. + * + * Will return an empty string if it's not even a conflict file by pattern. + */ + QByteArray conflictFileBaseName(const QByteArray &conflictName); /** * Delete any file entry. This will force the next sync to re-sync everything as if it was new, diff --git a/src/common/utility.cpp b/src/common/utility.cpp index d2a46bb7c..28d2ea3e5 100644 --- a/src/common/utility.cpp +++ b/src/common/utility.cpp @@ -603,7 +603,7 @@ bool Utility::isConflictFile(const QString &name) return false; } -QByteArray Utility::conflictFileBaseName(const QByteArray &conflictName) +QByteArray Utility::conflictFileBaseNameFromPattern(const QByteArray &conflictName) { // This function must be able to deal with conflict files for conflict files. // To do this, we scan backwards, for the outermost conflict marker and diff --git a/src/common/utility.h b/src/common/utility.h index 5e1ff930e..f6628c3e4 100644 --- a/src/common/utility.h +++ b/src/common/utility.h @@ -40,6 +40,8 @@ class QSettings; namespace OCC { +class SyncJournal; + Q_DECLARE_LOGGING_CATEGORY(lcUtility) /** \addtogroup libsync @@ -206,14 +208,14 @@ namespace Utility { OCSYNC_EXPORT bool isConflictFile(const char *name); OCSYNC_EXPORT bool isConflictFile(const QString &name); - /** Find the base name for a conflict file name + /** Find the base name for a conflict file name, using name pattern only * * Will return an empty string if it's not a conflict file. * * Prefer to use the data from the conflicts table in the journal to determine - * a conflict's base file. + * a conflict's base file, see SyncJournal::conflictFileBaseName() */ - OCSYNC_EXPORT QByteArray conflictFileBaseName(const QByteArray &conflictName); + OCSYNC_EXPORT QByteArray conflictFileBaseNameFromPattern(const QByteArray &conflictName); #ifdef Q_OS_WIN OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName); diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index e4ebd4ffc..c1c77f354 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -49,7 +49,7 @@ #include <QLocalSocket> #include <QStringBuilder> #include <QMessageBox> - +#include <QFileDialog> #include <QClipboard> #include <QStandardPaths> @@ -621,6 +621,68 @@ void SocketApi::command_DOWNLOAD_VIRTUAL_FILE(const QString &filesArg, SocketLis } } +void SocketApi::command_DELETE_ITEM(const QString &localFile, SocketListener *) +{ + QFileInfo info(localFile); + + auto result = QMessageBox::question( + nullptr, tr("Confirm deletion"), + info.isDir() + ? tr("Do you want to delete the directory <i>%1</i> and all its contents permanently?").arg(info.dir().dirName()) + : tr("Do you want to delete the file <i>%1</i> permanently?").arg(info.fileName()), + QMessageBox::Yes, QMessageBox::No); + if (result != QMessageBox::Yes) + return; + + if (info.isDir()) { + FileSystem::removeRecursively(localFile); + } else { + QFile(localFile).remove(); + } +} + +void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *) +{ + auto fileData = FileData::get(localFile); + auto parentDir = fileData.parentFolder(); + if (!fileData.folder) + return; // should not have shown menu item + + QString defaultDirAndName = fileData.folderRelativePath; + + // If it's a conflict, we want to save it under the base name by default + if (Utility::isConflictFile(defaultDirAndName)) { + defaultDirAndName = fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8()); + } + + // If the parent doesn't accept new files, go to the root of the sync folder + QFileInfo fileInfo(localFile); + auto parentRecord = parentDir.journalRecord(); + if ((fileInfo.isFile() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile)) + || (fileInfo.isDir() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) { + defaultDirAndName = QFileInfo(defaultDirAndName).fileName(); + } + + // Add back the folder path + defaultDirAndName = QDir(fileData.folder->path()).filePath(defaultDirAndName); + + auto target = QFileDialog::getSaveFileName( + nullptr, + tr("Select new location..."), + defaultDirAndName, + QString(), nullptr, QFileDialog::HideNameFilterDetails); + if (target.isEmpty()) + return; + + QString error; + if (!FileSystem::uncheckedRenameReplace(localFile, target, &error)) { + qCWarning(lcSocketApi) << "Rename error:" << error; + QMessageBox::warning( + nullptr, tr("Error"), + tr("Moving file failed:\n\n%1").arg(error)); + } +} + void SocketApi::emailPrivateLink(const QString &link) { Utility::openEmailComposer( @@ -724,6 +786,11 @@ SyncJournalFileRecord SocketApi::FileData::journalRecord() const return record; } +SocketApi::FileData SocketApi::FileData::parentFolder() const +{ + return FileData::get(QFileInfo(localPath).dir().path().toUtf8()); +} + void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener) { listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN")); @@ -747,12 +814,57 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe // Some options only show for single files if (files.size() == 1) { FileData fileData = FileData::get(files.first()); - bool isOnTheServer = fileData.journalRecord().isValid(); + auto record = fileData.journalRecord(); + bool isOnTheServer = record.isValid(); auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); if (fileData.folder && fileData.folder->accountState()->isConnected()) { sendSharingContextMenuOptions(fileData, listener); listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser")); + + // Conflict files get conflict resolution actions + bool isConflict = Utility::isConflictFile(fileData.folderRelativePath); + if (isConflict || !isOnTheServer) { + // Check whether this new file is in a read-only directory + QFileInfo fileInfo(fileData.localPath); + auto parentDir = fileData.parentFolder(); + auto parentRecord = parentDir.journalRecord(); + bool canAddToDir = + (fileInfo.isFile() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile)) + || (fileInfo.isDir() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)); + bool canChangeFile = + !isOnTheServer + || (record._remotePerm.hasPermission(RemotePermissions::CanDelete) + && record._remotePerm.hasPermission(RemotePermissions::CanMove) + && record._remotePerm.hasPermission(RemotePermissions::CanRename)); + + if (isConflict && canChangeFile) { + if (canAddToDir) { + if (isOnTheServer) { + // Conflict file that is already uploaded + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Rename...")); + } else { + // Local-only conflict file + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Rename and upload...")); + } + } else { + if (isOnTheServer) { + // Uploaded conflict file in read-only directory + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and rename...")); + } else { + // Local-only conflict file in a read-only dir + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move, rename and upload...")); + } + } + listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete local changes")); + } + + // File in a read-only directory? + if (!isConflict && !isOnTheServer && !canAddToDir) { + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and upload...")); + listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete")); + } + } } } diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index 5666c48b9..4ab576d4e 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -78,6 +78,7 @@ private: static FileData get(const QString &localFile); SyncFileStatus syncFileStatus() const; SyncJournalFileRecord journalRecord() const; + FileData parentFolder() const; Folder *folder; QString localPath; @@ -105,6 +106,8 @@ private: Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_DOWNLOAD_VIRTUAL_FILE(const QString &filesArg, SocketListener *listener); + Q_INVOKABLE void command_DELETE_ITEM(const QString &localFile, SocketListener *listener); + Q_INVOKABLE void command_MOVE_ITEM(const QString &localFile, SocketListener *listener); // Fetch the private link and call targetFun void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun); diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index 2dbbdb5c8..70dffef42 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -18,6 +18,7 @@ #include <QString> #include <ctime> +#include <functional> #include <owncloudlib.h> // Chain in the base include and extend the namespace diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index e3cb90e66..5227a8873 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -352,7 +352,7 @@ void SyncEngine::conflictRecordMaintenance() record.path = bapath; // Determine fileid of target file - auto basePath = Utility::conflictFileBaseName(bapath); + auto basePath = Utility::conflictFileBaseNameFromPattern(bapath); SyncJournalFileRecord baseRecord; if (_journal->getFileRecord(basePath, &baseRecord) && baseRecord.isValid()) { record.baseFileId = baseRecord._fileId; diff --git a/test/testsyncconflict.cpp b/test/testsyncconflict.cpp index 8703ccc41..92d330ac1 100644 --- a/test/testsyncconflict.cpp +++ b/test/testsyncconflict.cpp @@ -125,7 +125,7 @@ private slots: QVERIFY(conflictMap.contains(a1FileId)); QVERIFY(conflictMap.contains(a2FileId)); QCOMPARE(conflictMap.size(), 2); - QCOMPARE(Utility::conflictFileBaseName(conflictMap[a1FileId].toUtf8()), QByteArray("A/a1")); + QCOMPARE(Utility::conflictFileBaseNameFromPattern(conflictMap[a1FileId].toUtf8()), QByteArray("A/a1")); // Check that the conflict file contains the username QVERIFY(conflictMap[a1FileId].contains(QString("(conflicted copy %1 ").arg(fakeFolder.syncEngine().account()->davDisplayName()))); @@ -384,7 +384,7 @@ private slots: { QFETCH(QString, input); QFETCH(QString, output); - QCOMPARE(Utility::conflictFileBaseName(input.toUtf8()), output.toUtf8()); + QCOMPARE(Utility::conflictFileBaseNameFromPattern(input.toUtf8()), output.toUtf8()); } void testLocalDirRemoteFileConflict() |