if a virtual file change but bothing changed: set it as in sync
authorMatthieu Gallien <matthieu.gallien@nextcloud.com>
Thu, 11 Apr 2024 16:39:20 +0000 (18:39 +0200)
committerMatthieu Gallien <matthieu_gallien@yahoo.fr>
Tue, 23 Apr 2024 16:22:09 +0000 (18:22 +0200)
some software (at least outlook native software) may fiddle with CfApi
placeholder metadta and set a .msg file to be out of sync state when
opening it

in that case, we will let the sync engine go over it and decide what to
do

it is then possible in that case that we would just put it back in "in
sync" state

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
13 files changed:
src/common/vfs.h
src/csync/csync.h
src/gui/folder.cpp
src/libsync/discovery.cpp
src/libsync/owncloudpropagator.cpp
src/libsync/owncloudpropagator.h
src/libsync/progressdispatcher.cpp
src/libsync/vfs/cfapi/cfapiwrapper.cpp
src/libsync/vfs/cfapi/cfapiwrapper.h
src/libsync/vfs/cfapi/vfs_cfapi.cpp
src/libsync/vfs/cfapi/vfs_cfapi.h
src/libsync/vfs/suffix/vfs_suffix.h
src/libsync/vfs/xattr/vfs_xattr.h

index 060b5ec585fb29f434e63abdeedb7004a61caba8..b3c5df9d08262d7380b20120ff29a13d3f797bd7 100644 (file)
@@ -187,7 +187,11 @@ public:
      * If the remote metadata changes, the local placeholder's metadata should possibly
      * change as well.
      */
-    Q_REQUIRED_RESULT virtual Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) = 0;
+    [[nodiscard]] virtual Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) = 0;
+
+    [[nodiscard]] virtual Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) = 0;
+
+    [[nodiscard]] virtual bool isPlaceHolderInSync(const QString &filePath) const = 0;
 
     /// Create a new dehydrated placeholder. Called from PropagateDownload.
     Q_REQUIRED_RESULT virtual Result<void, QString> createPlaceholder(const SyncFileItem &item) = 0;
@@ -325,6 +329,8 @@ public:
     [[nodiscard]] bool isHydrating() const override { return false; }
 
     Result<void, QString> updateMetadata(const QString &, time_t, qint64, const QByteArray &) override { return {}; }
+    Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) override {Q_UNUSED(filePath) Q_UNUSED(fileId) return {QString{}};}
+    [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; }
     Result<void, QString> createPlaceholder(const SyncFileItem &) override { return {}; }
     Result<void, QString> dehydratePlaceholder(const SyncFileItem &) override { return {}; }
     Result<ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &, const SyncFileItem &, const QString &, const UpdateMetadataTypes) override { return ConvertToPlaceholderResult::Ok; }
index 235f0cd729afb6c0ef3bad0f46b4630f9afca176..3baf87587f46cd0796956b3d662344a7ecd54ef3 100644 (file)
@@ -153,6 +153,7 @@ enum SyncInstructions {
     CSYNC_INSTRUCTION_UPDATE_METADATA     = 1 << 10, /* If the etag has been updated and need to be writen to the db,
                                                         but without any propagation (UPDATE|RECONCILE) */
     CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
+    CSYNC_INSTRUCTION_UPDATE_VFS_METADATA = 1 << 13, /* vfs item metadata are out of sync and we need to tell operating system about it */
 };
 
 Q_ENUM_NS(SyncInstructions)
index c26eb4a90b9f4f6f6b1fa786b81e2f7c7e14622c..d7c5fbac54bc957611f10e36be075b1fd364b007 100644 (file)
@@ -621,13 +621,18 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason)
             spurious = true;
 
             if (auto pinState = _vfs->pinState(relativePath.toString())) {
-                if (*pinState == PinState::AlwaysLocal && record.isVirtualFile())
+                if (*pinState == PinState::AlwaysLocal && record.isVirtualFile()) {
                     spurious = false;
-                if (*pinState == PinState::OnlineOnly && record.isFile())
+                }
+                if (*pinState == PinState::OnlineOnly && record.isFile()) {
                     spurious = false;
+                }
             } else {
                 spurious = false;
             }
+            if (spurious && !_vfs->isPlaceHolderInSync(path)) {
+                spurious = false;
+            }
         }
         if (spurious) {
             qCInfo(lcFolder) << "Ignoring spurious notification for file" << relativePath;
index 387633c73f54ce8f1fc73b6e72f316bb2f3dc169..86450a2a53e41fe5f02b27acac84dead5f1be5d7 100644 (file)
@@ -1680,6 +1680,12 @@ void ProcessDirectoryJob::processFileFinalize(
         }
     }
 
+    if (_discoveryData->_syncOptions._vfs &&
+        item->_type == CSyncEnums::ItemTypeFile &&
+        !_discoveryData->_syncOptions._vfs->isPlaceHolderInSync(_discoveryData->_localDir + path._local)) {
+        item->_instruction = CSyncEnums::CSYNC_INSTRUCTION_UPDATE_VFS_METADATA;
+    }
+
     if (path._original != path._target && (item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA || item->_instruction == CSYNC_INSTRUCTION_NONE)) {
         ASSERT(_dirItem && _dirItem->_instruction == CSYNC_INSTRUCTION_RENAME);
         // This is because otherwise subitems are not updated!  (ideally renaming a directory could
index ac159c8d02c983db36cd29ae671b99759763df63..2148c6613675584cbe0e1ea5724554717a2e93e6 100644 (file)
@@ -396,6 +396,8 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item)
         } else {
             return new PropagateLocalRename(this, item);
         }
+    case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA:
+        return new PropagateVfsUpdateMetadataJob(this, item);
     case CSYNC_INSTRUCTION_IGNORE:
     case CSYNC_INSTRUCTION_ERROR:
         return new PropagateIgnoreJob(this, item);
@@ -1764,4 +1766,15 @@ void PropagateIgnoreJob::start()
     done(status, _item->_errorString, ErrorCategory::NoError);
 }
 
+void PropagateVfsUpdateMetadataJob::start()
+{
+    const auto fullFileName = propagator()->fullLocalPath(_item->_file);
+    const auto result = propagator()->syncOptions()._vfs->updatePlaceholderMarkInSync(fullFileName, _item->_fileId);
+    emit propagator()->touchedFile(fullFileName);
+    if (!result) {
+        qCWarning(lcPropagator()) << "error when updating VFS metadata" << result.error();
+    }
+    done(SyncFileItem::Success, {}, {});
+}
+
 }
index d8f5f8eec022f9e600dff3644359a782931752ee..1a96ece644c5d0e1efc16ee0aa13e62310805eec 100644 (file)
@@ -414,6 +414,17 @@ public:
     void start() override;
 };
 
+class PropagateVfsUpdateMetadataJob : public PropagateItemJob
+{
+    Q_OBJECT
+public:
+    PropagateVfsUpdateMetadataJob(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
+        : PropagateItemJob(propagator, item)
+    {
+    }
+    void start() override;
+};
+
 class PropagateUploadFileCommon;
 
 class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject
index 49c34c53211d72181cbcd82ad8ce318c778921ac..ec189b1c970322d3a2575514de7982121fd211a1 100644 (file)
@@ -56,6 +56,8 @@ QString Progress::asResultString(const SyncFileItem &item)
         return QCoreApplication::translate("progress", "Error");
     case CSYNC_INSTRUCTION_UPDATE_METADATA:
         return QCoreApplication::translate("progress", "Updated local metadata");
+    case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA:
+        return QCoreApplication::translate("progress", "Updated local virtual files metadata");
     case CSYNC_INSTRUCTION_NONE:
     case CSYNC_INSTRUCTION_EVAL:
         return QCoreApplication::translate("progress", "Unknown");
@@ -87,6 +89,8 @@ QString Progress::asActionString(const SyncFileItem &item)
         return QCoreApplication::translate("progress", "error");
     case CSYNC_INSTRUCTION_UPDATE_METADATA:
         return QCoreApplication::translate("progress", "updating local metadata");
+    case CSYNC_INSTRUCTION_UPDATE_VFS_METADATA:
+        return QCoreApplication::translate("progress", "updating local virtual files metadata");
     case CSYNC_INSTRUCTION_NONE:
     case CSYNC_INSTRUCTION_EVAL:
         break;
index c928a3a809d2316db007598f527305ad55d0d797..8b168879569441538f46d06c82203ccddedfd174 100644 (file)
@@ -285,7 +285,12 @@ enum class CfApiUpdateMetadataType {
     AllMetadata,
 };
 
-OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderState(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath, CfApiUpdateMetadataType updateType)
+OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderState(const QString &path,
+                                                                                  time_t modtime,
+                                                                                  qint64 size,
+                                                                                  const QByteArray &fileId,
+                                                                                  const QString &replacesPath,
+                                                                                  CfApiUpdateMetadataType updateType)
 {
     if (updateType == CfApiUpdateMetadataType::AllMetadata && modtime <= 0) {
             return {QString{"Could not update metadata due to invalid modification time for %1: %2"}.arg(path).arg(modtime)};
@@ -900,3 +905,12 @@ OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::up
 {
     return updatePlaceholderState(path, {}, {}, fileId, replacesPath, CfApiUpdateMetadataType::OnlyBasicMetadata);
 }
+
+bool OCC::CfApiWrapper::isPlaceHolderInSync(const QString &filePath)
+{
+    if (const auto originalInfo = findPlaceholderInfo(filePath)) {
+        return originalInfo->InSyncState == CF_IN_SYNC_STATE_IN_SYNC;
+    }
+
+    return true;
+}
index 94ebff914cbe64fd008196ea4d64a411a3d561a3..8d9b2228ce93579f6ca33aa13290b804e6b8945f 100644 (file)
@@ -98,6 +98,7 @@ NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> upd
 NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath);
 NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> dehydratePlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId);
 NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &path, const QByteArray &fileId, const QString &replacesPath = QString());
+NEXTCLOUD_CFAPI_EXPORT bool isPlaceHolderInSync(const QString &filePath);
 
 }
 
index 165e558cf0fe7aa2ad57902c7521693b539f95e6..a81b6606f69a3d9a67cfbe62be5cabe5faf09251 100644 (file)
@@ -198,6 +198,16 @@ Result<void, QString> VfsCfApi::updateMetadata(const QString &filePath, time_t m
     }
 }
 
+Result<Vfs::ConvertToPlaceholderResult, QString> VfsCfApi::updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId)
+{
+    return cfapi::updatePlaceholderMarkInSync(filePath, fileId, {});
+}
+
+bool VfsCfApi::isPlaceHolderInSync(const QString &filePath) const
+{
+    return cfapi::isPlaceHolderInSync(filePath);
+}
+
 Result<void, QString> VfsCfApi::createPlaceholder(const SyncFileItem &item)
 {
     Q_ASSERT(params().filesystemPath.endsWith('/'));
index 38e04e00ef4468468b387f1b6b9e4e75a7927ec8..7e8937fc2e4c500990394e86b0e1cdcf01b27ef7 100644 (file)
@@ -43,6 +43,10 @@ public:
 
     Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) override;
 
+    Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) override;
+
+    [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override;
+
     Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
     Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) override;
     Result<Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType) override;
index 85e39b1b00d9259437a41dde665934d70bb95aa7..3d73d5f365468f3d470781d1d46e7dc42aed288e 100644 (file)
@@ -39,6 +39,8 @@ public:
     [[nodiscard]] bool isHydrating() const override;
 
     Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) override;
+    Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) override {Q_UNUSED(filePath) Q_UNUSED(fileId) return {QString{}};}
+    [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; }
 
     Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
     Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) override;
index de331f4a7a87709745c2bb09da3d1b851a904960..ea7039d4f448526b9676f99c0bab34bdaf8cd1d4 100644 (file)
@@ -39,6 +39,8 @@ public:
     [[nodiscard]] bool isHydrating() const override;
 
     Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) override;
+    Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) override {Q_UNUSED(filePath) Q_UNUSED(fileId) return {QString{}};}
+    [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; }
 
     Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
     Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) override;