Rename Placeholders to Virtual Files in code #6531
authorChristian Kamm <mail@ckamm.de>
Fri, 18 May 2018 06:29:40 +0000 (08:29 +0200)
committerKevin Ottens <kevin.ottens@nextcloud.com>
Tue, 15 Dec 2020 09:57:53 +0000 (10:57 +0100)
33 files changed:
CMakeLists.txt
NEXTCLOUD.cmake
cmake/modules/MacOSXBundleInfo.plist.in
cmake/modules/NSIS.template.in
config.h.in
src/csync/csync.h
src/csync/csync_private.h
src/csync/csync_reconcile.cpp
src/csync/csync_update.cpp
src/gui/accountsettings.cpp
src/gui/application.cpp
src/gui/application.h
src/gui/folder.cpp
src/gui/folder.h
src/gui/folderwizard.cpp
src/gui/folderwizard.h
src/gui/owncloud.xml.in
src/gui/owncloudsetupwizard.cpp
src/gui/socketapi.cpp
src/gui/socketapi.h
src/gui/wizard/owncloudadvancedsetuppage.cpp
src/gui/wizard/owncloudadvancedsetuppage.h
src/gui/wizard/owncloudadvancedsetuppage.ui
src/gui/wizard/owncloudwizard.cpp
src/gui/wizard/owncloudwizard.h
src/libsync/owncloudpropagator.cpp
src/libsync/owncloudpropagator.h
src/libsync/propagatedownload.cpp
src/libsync/syncengine.cpp
src/libsync/syncoptions.h
test/CMakeLists.txt
test/testsyncplaceholders.cpp [deleted file]
test/testsyncvirtualfiles.cpp [new file with mode: 0644]

index f541e4143781a5320211e53f27a30ef50de9cea5..e7ac73a3566d0e376a7fa66b8afcca3dcae00372 100644 (file)
@@ -14,8 +14,8 @@ else ()
 endif()
 
 # Default suffix if the theme doesn't define one
-if(NOT DEFINED APPLICATION_PLACEHOLDER_SUFFIX)
-    set(APPLICATION_PLACEHOLDER_SUFFIX "${APPLICATION_SHORTNAME}_placeholder" CACHE STRING "Placeholder suffix (not including the .)")
+if(NOT DEFINED APPLICATION_VIRTUALFILE_SUFFIX)
+    set(APPLICATION_VIRTUALFILE_SUFFIX "${APPLICATION_SHORTNAME}_virtual" CACHE STRING "Virtual file suffix (not including the .)")
 endif()
 
 # need this logic to not mess with re/uninstallations via macosx.pkgproj
index 870cad83ed83611d5a36e488d7fac40867bbf41d..6f034094cd6768c6d9c87683ed6b61388701ce07 100644 (file)
@@ -9,7 +9,7 @@ set( APPLICATION_ICON_NAME  "Nextcloud" )
 set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered, the UI field will be pre-filled with it" )
 set( APPLICATION_SERVER_URL_ENFORCE ON ) # If set and APPLICATION_SERVER_URL is defined, the server can only connect to the pre-defined URL
 set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
-set( APPLICATION_PLACEHOLDER_SUFFIX "nextcloud" CACHE STRING "Placeholder suffix (not including the .)")
+set( APPLICATION_VIRTUALFILE_SUFFIX "nextcloud" CACHE STRING "Virtual file suffix (not including the .)")
 
 set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
 set( LINUX_APPLICATION_ID "${APPLICATION_REV_DOMAIN}.${LINUX_PACKAGE_SHORTNAME}")
index 55a9df55223256d1b5842081eaf5800dcf4071c4..04855b81398db864b25c9171de24f2a50f466f68 100644 (file)
 <array>
     <dict>
         <key>UTTypeIdentifier</key>
-        <string>@APPLICATION_REV_DOMAIN@.placeholder</string>
+        <string>@APPLICATION_REV_DOMAIN@.VIRTUALFILE</string>
         <key>UTTypeTagSpecification</key>
         <dict>
             <key>public.filename-extension</key>
-            <string>@APPLICATION_PLACEHOLDER_SUFFIX@</string>
+            <string>@APPLICATION_VIRTUALFILE_SUFFIX@</string>
             <key>public.mime-type</key>
             <string>application/octet-stream</string>
         </dict>
 <array>
     <dict>
         <key>CFBundleTypeName</key>
-        <string>@APPLICATION_EXECUTABLE@ Download Placeholder</string>
+        <string>@APPLICATION_EXECUTABLE@ Download Virtual File</string>
         <key>CFBundleTypeRole</key>
         <string>Editor</string>
         <key>LSHandlerRank</key>
         <string>Owner</string>
         <key>LSItemContentTypes</key>
         <array>
-            <string>@APPLICATION_REV_DOMAIN@.placeholder</string>
+            <string>@APPLICATION_REV_DOMAIN@.VIRTUALFILE</string>
         </array>
     </dict>
 </array>
index 982faf3f14ecedf09b10984103e2adea4984ed13..8fff6e1961cacd6de2765cb59574c10181d11b48 100644 (file)
@@ -7,8 +7,8 @@
 !define APPLICATION_CMD_EXECUTABLE "@APPLICATION_EXECUTABLE@cmd.exe"
 !define APPLICATION_DOMAIN "@APPLICATION_DOMAIN@"
 !define APPLICATION_LICENSE "@APPLICATION_LICENSE@"
-!define APPLICATION_PLACEHOLDER_SUFFIX "@APPLICATION_PLACEHOLDER_SUFFIX@"
-!define APPLICATION_PLACEHOLDER_FILECLASS "@APPLICATION_EXECUTABLE@.@APPLICATION_PLACEHOLDER_SUFFIX@"
+!define APPLICATION_VIRTUALFILE_SUFFIX "@APPLICATION_VIRTUALFILE_SUFFIX@"
+!define APPLICATION_VIRTUALFILE_FILECLASS "@APPLICATION_EXECUTABLE@.@APPLICATION_VIRTUALFILE_SUFFIX@"
 !define WIN_SETUP_BITMAP_PATH "@WIN_SETUP_BITMAP_PATH@"
 
 !define CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@"
@@ -474,7 +474,7 @@ Section "${APPLICATION_NAME}" SEC_APPLICATION
    File "${SOURCE_PATH}/sync-exclude.lst"
 
    ;Add file association
-   !insertmacro APP_ASSOCIATE "${APPLICATION_PLACEHOLDER_SUFFIX}" "${APPLICATION_PLACEHOLDER_FILECLASS}" "Placeholder for Remote File" "$INSTDIR\${APPLICATION_EXECUTABLE},0" "Download" "$INSTDIR\${APPLICATION_EXECUTABLE} $\"%1$\""
+   !insertmacro APP_ASSOCIATE "${APPLICATION_VIRTUALFILE_SUFFIX}" "${APPLICATION_VIRTUALFILE_FILECLASS}" "Virtual File for Remote File" "$INSTDIR\${APPLICATION_EXECUTABLE},0" "Download" "$INSTDIR\${APPLICATION_EXECUTABLE} $\"%1$\""
 
 SectionEnd
 
@@ -654,7 +654,7 @@ Section Uninstall
    DeleteRegKey HKCR "${APPLICATION_NAME}"
 
    ;Remove file association
-   !insertmacro APP_UNASSOCIATE "${APPLICATION_PLACEHOLDER_SUFFIX}" "${APPLICATION_PLACEHOLDER_FILECLASS}"
+   !insertmacro APP_UNASSOCIATE "${APPLICATION_VIRTUALFILE_SUFFIX}" "${APPLICATION_VIRTUALFILE_FILECLASS}"
 
    ;Shell extension
    !ifdef OPTION_SECTION_SC_SHELL_EXT
index f6074b166a9b02b8dfcddbd57953ccf67f6fd3e1..c645c0be6f3926b4cede34a677806f5b6cf9ca99 100644 (file)
@@ -26,8 +26,8 @@
 #cmakedefine APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "@APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR@"
 #cmakedefine APPLICATION_WIZARD_HEADER_TITLE_COLOR "@APPLICATION_WIZARD_HEADER_TITLE_COLOR@"
 #cmakedefine APPLICATION_WIZARD_USE_CUSTOM_LOGO "@APPLICATION_WIZARD_USE_CUSTOM_LOGO@"
-#cmakedefine APPLICATION_PLACEHOLDER_SUFFIX "@APPLICATION_PLACEHOLDER_SUFFIX@"
-#define APPLICATION_DOTPLACEHOLDER_SUFFIX "." APPLICATION_PLACEHOLDER_SUFFIX
+#cmakedefine APPLICATION_VIRTUALFILE_SUFFIX "@APPLICATION_VIRTUALFILE_SUFFIX@"
+#define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX
 
 #cmakedefine ZLIB_FOUND @ZLIB_FOUND@
 
index db21803ee64f017b908a9775bcb83411b80c869b..50ebfe9799a6a1d0a621f2da57387b5fccf5b3ca 100644 (file)
@@ -136,8 +136,8 @@ enum ItemType {
     ItemTypeSoftLink = 1,
     ItemTypeDirectory = 2,
     ItemTypeSkip = 3,
-    ItemTypePlaceholder = 4,
-    ItemTypePlaceholderDownload = 5
+    ItemTypeVirtualFile = 4,
+    ItemTypeVirtualFileDownload = 5
 };
 
 
index bba72237f345a3ec06c49cf85ea17bac8d1c0e33..009c8741b64a5e233fc78f7cdcb48b91de08e30d 100644 (file)
@@ -208,14 +208,14 @@ struct OCSYNC_EXPORT csync_s {
   bool upload_conflict_files = false;
 
   /**
-   * Whether new remote files should start out as placeholders.
+   * Whether new remote files should start out as virtual.
    */
-  bool new_files_are_placeholders = false;
+  bool new_files_are_virtual = false;
 
   /**
-   * The suffix to use for placeholder files.
+   * The suffix to use for virtual files.
    */
-  QByteArray placeholder_suffix;
+  QByteArray virtual_file_suffix;
 
   csync_s(const char *localUri, OCC::SyncJournalDb *statedb);
   ~csync_s();
index 9cd00b39325b027d1b20c14fc4ac837c5a022e96..4c884083aa48542459e7306fd6be8ff31c6bdcea 100644 (file)
@@ -132,25 +132,25 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
         /* If it is ignored, other->instruction will be  IGNORE so this one will also be ignored */
     }
 
-    // If the user adds a file locally check whether a placeholder for that name exists.
+    // If the user adds a file locally check whether a virtual file for that name exists.
     // If so, go to "potential conflict" mode by switching the remote entry to be a
     // real file.
     if (!other
         && ctx->current == LOCAL_REPLICA
         && cur->instruction == CSYNC_INSTRUCTION_NEW
-        && cur->type != ItemTypePlaceholder) {
-        // Check if we have a placeholder entry in the remote tree
-        auto placeholderPath = cur->path;
-        placeholderPath.append(ctx->placeholder_suffix);
-        other = other_tree->findFile(placeholderPath);
+        && cur->type != ItemTypeVirtualFile) {
+        // Check if we have a virtual file  entry in the remote tree
+        auto virtualFilePath = cur->path;
+        virtualFilePath.append(ctx->virtual_file_suffix);
+        other = other_tree->findFile(virtualFilePath);
         if (!other) {
             /* Check the renamed path as well. */
-            other = other_tree->findFile(csync_rename_adjust_parent_path(ctx, placeholderPath));
+            other = other_tree->findFile(csync_rename_adjust_parent_path(ctx, virtualFilePath));
         }
-        if (other && other->type == ItemTypePlaceholder) {
-            qCInfo(lcReconcile) << "Found placeholder for local" << cur->path << "in remote tree";
+        if (other && other->type == ItemTypeVirtualFile) {
+            qCInfo(lcReconcile) << "Found virtual file for local" << cur->path << "in remote tree";
             other->path = cur->path;
-            other->type = ItemTypePlaceholderDownload;
+            other->type = ItemTypeVirtualFileDownload;
             other->instruction = CSYNC_INSTRUCTION_EVAL;
         } else {
             other = nullptr;
@@ -176,12 +176,12 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
                 cur->instruction = CSYNC_INSTRUCTION_NEW;
                 break;
             }
-            /* If the local placeholder is gone it should be reestablished.
+            /* If the local virtual file is gone, it should be reestablished.
              * Unless the base file is seen in the local tree now. */
-            if (cur->type == ItemTypePlaceholder
+            if (cur->type == ItemTypeVirtualFile
                 && ctx->current == REMOTE_REPLICA
-                && cur->path.endsWith(ctx->placeholder_suffix)
-                && !other_tree->findFile(cur->path.left(cur->path.size() - ctx->placeholder_suffix.size()))) {
+                && cur->path.endsWith(ctx->virtual_file_suffix)
+                && !other_tree->findFile(cur->path.left(cur->path.size() - ctx->virtual_file_suffix.size()))) {
                 cur->instruction = CSYNC_INSTRUCTION_NEW;
                 break;
             }
@@ -455,13 +455,13 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
                 cur->instruction = CSYNC_INSTRUCTION_NEW;
             break;
         case CSYNC_INSTRUCTION_NONE:
-            // NONE/NONE on placeholders might become a REMOVE if the base file
+            // NONE/NONE on virtual files might become a REMOVE if the base file
             // is found in the local tree.
-            if (cur->type == ItemTypePlaceholder
+            if (cur->type == ItemTypeVirtualFile
                 && other->instruction == CSYNC_INSTRUCTION_NONE
                 && ctx->current == LOCAL_REPLICA
-                && cur->path.endsWith(ctx->placeholder_suffix)
-                && ctx->local.files.findFile(cur->path.left(cur->path.size() - ctx->placeholder_suffix.size()))) {
+                && cur->path.endsWith(ctx->virtual_file_suffix)
+                && ctx->local.files.findFile(cur->path.left(cur->path.size() - ctx->virtual_file_suffix.size()))) {
                 cur->instruction = CSYNC_INSTRUCTION_REMOVE;
             }
             break;
index cf05290c1d407a94ab7668a89aabcc3ae0b82b4a..cc8f891c769dca9bfe9876263bcc1badd0658839 100644 (file)
@@ -201,17 +201,17 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
       }
   }
 
-  // The db entry might be for a placeholder, so look for that on the
+  // The db entry might be for a virtual file, so look for that on the
   // remote side. If we find one, change the current fs to look like a
-  // placeholder too, because that's what one would see if the remote
+  // virtual file too, because that's what one would see if the remote
   // db was filled from the database.
   if (ctx->current == REMOTE_REPLICA && !base.isValid() && fs->type == ItemTypeFile) {
-      auto placeholderPath = fs->path;
-      placeholderPath.append(ctx->placeholder_suffix);
-      ctx->statedb->getFileRecord(placeholderPath, &base);
-      if (base.isValid() && base._type == ItemTypePlaceholder) {
-          fs->type = ItemTypePlaceholder;
-          fs->path = placeholderPath;
+      auto virtualFilePath = fs->path;
+      virtualFilePath.append(ctx->virtual_file_suffix);
+      ctx->statedb->getFileRecord(virtualFilePath, &base);
+      if (base.isValid() && base._type == ItemTypeVirtualFile) {
+          fs->type = ItemTypeVirtualFile;
+          fs->path = virtualFilePath;
       } else {
           base = OCC::SyncJournalFileRecord();
       }
@@ -238,19 +238,19 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
                 fs->type, base._type,
                 base._serverHasIgnoredFiles, base._e2eMangledName.constData());
 
-      // If the db suggests a placeholder should be downloaded,
+      // If the db suggests a virtual file should be downloaded,
       // treat the file as new on the remote.
-      if (ctx->current == REMOTE_REPLICA && base._type == ItemTypePlaceholderDownload) {
+      if (ctx->current == REMOTE_REPLICA && base._type == ItemTypeVirtualFileDownload) {
           fs->instruction = CSYNC_INSTRUCTION_NEW;
-          fs->type = ItemTypePlaceholderDownload;
+          fs->type = ItemTypeVirtualFileDownload;
           goto out;
       }
 
-      // If what the db thinks is a placeholder is actually a file/dir,
+      // If what the db thinks is a virtual file is actually a file/dir,
       // treat it as new locally.
       if (ctx->current == LOCAL_REPLICA
-          && (base._type == ItemTypePlaceholder || base._type == ItemTypePlaceholderDownload)
-          && fs->type != ItemTypePlaceholder) {
+          && (base._type == ItemTypeVirtualFile || base._type == ItemTypeVirtualFileDownload)
+          && fs->type != ItemTypeVirtualFile) {
           fs->instruction = CSYNC_INSTRUCTION_EVAL;
           goto out;
       }
@@ -258,8 +258,8 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
       if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
           fs->instruction = CSYNC_INSTRUCTION_EVAL;
 
-          if (fs->type == ItemTypePlaceholder) {
-              // If the local thing is a placeholder, we just update the metadata
+          if (fs->type == ItemTypeVirtualFile) {
+              // If the local thing is a virtual file, we just update the metadata
               fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
           } else if (base._type != fs->type) {
               // Preserve the EVAL flag later on if the type has changed.
@@ -385,10 +385,10 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
               if (!base.isValid())
                   return;
 
-              if (base._type == ItemTypePlaceholderDownload) {
-                  // Remote rename of a placeholder file we have locally scheduled
+              if (base._type == ItemTypeVirtualFileDownload) {
+                  // Remote rename of a virtual file we have locally scheduled
                   // for download. We just consider this NEW but mark it for download.
-                  fs->type = ItemTypePlaceholderDownload;
+                  fs->type = ItemTypeVirtualFileDownload;
                   done = true;
                   return;
               }
@@ -397,7 +397,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
               // Since we don't do the same checks again in reconcile, we can't
               // just skip the candidate, but have to give up completely.
               if (base._type != fs->type
-                  && base._type != ItemTypePlaceholder) {
+                  && base._type != ItemTypeVirtualFile) {
                   qCWarning(lcUpdate, "file types different, not a rename");
                   done = true;
                   return;
@@ -411,10 +411,10 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
 
               // Now we know there is a sane rename candidate.
 
-              // Rename of a placeholder
-              if (base._type == ItemTypePlaceholder && fs->type == ItemTypeFile) {
-                  fs->type = ItemTypePlaceholder;
-                  fs->path.append(ctx->placeholder_suffix);
+              // Rename of a virtual file
+              if (base._type == ItemTypeVirtualFile && fs->type == ItemTypeFile) {
+                  fs->type = ItemTypeVirtualFile;
+                  fs->path.append(ctx->virtual_file_suffix);
               }
 
               // Record directory renames
@@ -457,12 +457,12 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
               }
           }
 
-          // Turn new remote files into placeholders if the option is enabled.
-          if (ctx->new_files_are_placeholders
+          // Turn new remote files into virtual files if the option is enabled.
+          if (ctx->new_files_are_virtual
               && fs->instruction == CSYNC_INSTRUCTION_NEW
               && fs->type == ItemTypeFile) {
-              fs->type = ItemTypePlaceholder;
-              fs->path.append(ctx->placeholder_suffix);
+              fs->type = ItemTypeVirtualFile;
+              fs->path.append(ctx->virtual_file_suffix);
           }
 
           goto out;
@@ -785,15 +785,15 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
         fullpath = QByteArray() % uri % '/' % filename;
     }
 
-    // When encountering placeholder files, read the relevant
+    // When encountering virtual files, read the relevant
     // entry from the db instead.
     if (ctx->current == LOCAL_REPLICA
         && dirent->type == ItemTypeFile
-        && filename.endsWith(ctx->placeholder_suffix)) {
+        && filename.endsWith(ctx->virtual_file_suffix)) {
         QByteArray db_uri = fullpath.mid(strlen(ctx->local.uri) + 1);
 
         if( ! fill_tree_from_db(ctx, db_uri.constData(), true) ) {
-            qCWarning(lcUpdate) << "Placeholder without db entry for" << filename;
+            qCWarning(lcUpdate) << "Virtual file without db entry for" << filename;
             QFile::remove(fullpath);
         }
 
index 5304d3547e8a13699a7c9fa0fc6a927f80f1379a..4bf282675382733b729825b0b1ca02733f365bcc 100644 (file)
@@ -497,7 +497,7 @@ void AccountSettings::slotFolderWizardAccepted()
         folderWizard->field(QLatin1String("sourceFolder")).toString());
     definition.targetPath = FolderDefinition::prepareTargetPath(
         folderWizard->property("targetPath").toString());
-    definition.usePlaceholders = folderWizard->property("usePlaceholders").toBool();
+    definition.useVirtualFiles = folderWizard->property("useVirtualFiles").toBool();
 
     {
         QDir dir(definition.localPath);
index ee4d84abbcb4fb2741e55f0f349ea346a213f0ee..2a97ecd024dfc0e80d0ec3e7dc363413e8ea5a91 100644 (file)
@@ -500,9 +500,9 @@ void Application::parseOptions(const QStringList &options)
             _backgroundMode = true;
         } else if (option == QLatin1String("--version") || option == QLatin1String("-v")) {
             _versionOnly = true;
-        } else if (option.endsWith(QStringLiteral(APPLICATION_DOTPLACEHOLDER_SUFFIX))) {
-            // placeholder file, open it after the Folder were created (if the app is not terminated)
-            QTimer::singleShot(0, this, [this, option] { openPlaceholder(option); });
+        } else if (option.endsWith(QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX))) {
+            // virtual file, open it after the Folder were created (if the app is not terminated)
+            QTimer::singleShot(0, this, [this, option] { openVirtualFile(option); });
         } else {
             showHint("Unrecognized option '" + option.toStdString() + "'");
         }
@@ -678,10 +678,10 @@ void Application::slotGuiIsShowingSettings()
     emit isShowingSettingsDialog();
 }
 
-void Application::openPlaceholder(const QString &filename)
+void Application::openVirtualFile(const QString &filename)
 {
-    QString placeholderExt = QStringLiteral(APPLICATION_DOTPLACEHOLDER_SUFFIX);
-    if (!filename.endsWith(placeholderExt)) {
+    QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
+    if (!filename.endsWith(virtualFileExt)) {
         qWarning(lcApplication) << "Can only handle file ending in .owncloud. Unable to open" << filename;
         return;
     }
@@ -692,8 +692,8 @@ void Application::openPlaceholder(const QString &filename)
         return;
     }
     QString relativePath = QDir::cleanPath(filename).mid(folder->cleanPath().length() + 1);
-    folder->downloadPlaceholder(relativePath);
-    QString normalName = filename.left(filename.size() - placeholderExt.size());
+    folder->downloadVirtualFile(relativePath);
+    QString normalName = filename.left(filename.size() - virtualFileExt.size());
     auto con = QSharedPointer<QMetaObject::Connection>::create();
     *con = QObject::connect(folder, &Folder::syncFinished, [con, normalName] {
         QObject::disconnect(*con);
@@ -709,9 +709,9 @@ bool Application::event(QEvent *event)
     if (event->type() == QEvent::FileOpen) {
         QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
         qCDebug(lcApplication) << "QFileOpenEvent" << openEvent->file();
-        // placeholder file, open it after the Folder were created (if the app is not terminated)
+        // virtual file, open it after the Folder were created (if the app is not terminated)
         QString fn = openEvent->file();
-        QTimer::singleShot(0, this, [this, fn] { openPlaceholder(fn); });
+        QTimer::singleShot(0, this, [this, fn] { openVirtualFile(fn); });
     }
 #endif
     return SharedTools::QtSingleApplication::event(event);
index 546dc809b9fb93b26a186f35d2910180bea69000..d2b4e4963fe60d37d72c348f32d65148f57c4216 100644 (file)
@@ -74,10 +74,10 @@ public slots:
     void slotownCloudWizardDone(int);
     void slotCrash();
     /**
-     * Will download a placeholder file, and open the result.
-     * The argument is the filename of the placeholder file (including the extension)
+     * Will download a virtual file, and open the result.
+     * The argument is the filename of the virtual file (including the extension)
      */
-    void openPlaceholder(const QString &filename);
+    void openVirtualFile(const QString &filename);
 
 protected:
     void parseOptions(const QStringList &);
index 3b87871a4a0e5f9b4d3a40bca9cf76959950d243..8d4ba16ffbb0371d9038bf91d19acc8c9d78b44e 100644 (file)
@@ -520,9 +520,9 @@ void Folder::slotWatchedPathChanged(const QString &path)
     scheduleThisFolderSoon();
 }
 
-void Folder::downloadPlaceholder(const QString &_relativepath)
+void Folder::downloadVirtualFile(const QString &_relativepath)
 {
-    qCInfo(lcFolder) << "Download placeholder: " << _relativepath;
+    qCInfo(lcFolder) << "Download virtual file: " << _relativepath;
     auto relativepath = _relativepath.toUtf8();
 
     // Set in the database that we should download the file
@@ -530,7 +530,7 @@ void Folder::downloadPlaceholder(const QString &_relativepath)
     _journal.getFileRecord(relativepath, &record);
     if (!record.isValid())
         return;
-    record._type = ItemTypePlaceholderDownload;
+    record._type = ItemTypeVirtualFileDownload;
     _journal.setFileRecord(record);
 
     // Make sure we go over that file during the discovery
@@ -554,9 +554,10 @@ void Folder::saveToSettings() const
         return other != this && other->cleanPath() == this->cleanPath();
     });
 
-    if (_definition.usePlaceholders) {
-        // If placeholders are enabled, save the folder to a group
+    if (_definition.useVirtualFiles) {
+        // If virtual files are enabled, save the folder to a group
         // that will not be read by older (<2.5.0) clients.
+        // The name is from when virtual files were called placeholders.
         settingsGroup = QStringLiteral("FoldersWithPlaceholders");
     } else if (_saveBackwardsCompatible || oneAccountOnly) {
         // The folder is saved to backwards-compatible "Folders"
@@ -720,8 +721,8 @@ void Folder::setSyncOptions()
     opt._newBigFolderSizeLimit = newFolderLimit.first ? newFolderLimit.second * 1000LL * 1000LL : -1; // convert from MB to B
     opt._confirmExternalStorage = cfgFile.confirmExternalStorage();
     opt._moveFilesToTrash = cfgFile.moveToTrash();
-    opt._newFilesArePlaceholders = _definition.usePlaceholders;
-    opt._placeholderSuffix = QStringLiteral(APPLICATION_DOTPLACEHOLDER_SUFFIX);
+    opt._newFilesAreVirtual = _definition.useVirtualFiles;
+    opt._virtualFileSuffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
 
     QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE");
     if (!chunkSizeEnv.isEmpty()) {
@@ -1125,7 +1126,7 @@ void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder)
     settings.setValue(QLatin1String("targetPath"), folder.targetPath);
     settings.setValue(QLatin1String("paused"), folder.paused);
     settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles);
-    settings.setValue(QLatin1String("usePlaceholders"), folder.usePlaceholders);
+    settings.setValue(QLatin1String("usePlaceholders"), folder.useVirtualFiles);
 
     // Happens only on Windows when the explorer integration is enabled.
     if (!folder.navigationPaneClsid.isNull())
@@ -1146,7 +1147,7 @@ bool FolderDefinition::load(QSettings &settings, const QString &alias,
     folder->paused = settings.value(QLatin1String("paused")).toBool();
     folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool();
     folder->navigationPaneClsid = settings.value(QLatin1String("navigationPaneClsid")).toUuid();
-    folder->usePlaceholders = settings.value(QLatin1String("usePlaceholders")).toBool();
+    folder->useVirtualFiles = settings.value(QLatin1String("usePlaceholders")).toBool();
     settings.endGroup();
 
     // Old settings can contain paths with native separators. In the rest of the
index c52f666af125642d987b429ef2fb4ef41b1c603d..c155ee1953c3049930f3e1dd614bd711d752fa1e 100644 (file)
@@ -60,8 +60,8 @@ public:
     bool paused = false;
     /// whether the folder syncs hidden files
     bool ignoreHiddenFiles = false;
-    /// New files are downloaded as placeholders
-    bool usePlaceholders = false;
+    /// New files are downloaded as virtual files
+    bool useVirtualFiles = false;
     /// The CLSID where this folder appears in registry for the Explorer navigation pane entry.
     QUuid navigationPaneClsid;
 
@@ -280,10 +280,10 @@ public slots:
     void slotWatchedPathChanged(const QString &path);
 
     /**
-     * Mark a placeholder as being ready for download, and start a sync.
-     * relativePath is the patch to the placeholder file (includeing the extension)
+     * Mark a virtual file as being ready for download, and start a sync.
+     * relativePath is the patch to the file (including the extension)
      */
-    void downloadPlaceholder(const QString &relativepath);
+    void downloadVirtualFile(const QString &relativepath);
 
 private slots:
     void slotSyncStarted();
index dbbdbd06f6ab9fd2dac16527a9c8307f5efa9472..f1ea6696e9a2a1a13e7a9d054d3d57735996e9b5 100644 (file)
@@ -495,9 +495,9 @@ FolderWizardSelectiveSync::FolderWizardSelectiveSync(const AccountPtr &account)
     layout->addWidget(_selectiveSync);
 
     if (ConfigFile().showExperimentalOptions()) {
-        _placeholderCheckBox = new QCheckBox(tr("Use virtual files instead of downloading content immediately (experimental)"));
-        connect(_placeholderCheckBox, &QCheckBox::clicked, this, &FolderWizardSelectiveSync::placeholderCheckboxClicked);
-        layout->addWidget(_placeholderCheckBox);
+        _virtualFilesCheckBox = new QCheckBox(tr("Use virtual files instead of downloading content immediately (experimental)"));
+        connect(_virtualFilesCheckBox, &QCheckBox::clicked, this, &FolderWizardSelectiveSync::virtualFilesCheckboxClicked);
+        layout->addWidget(_virtualFilesCheckBox);
     }
 }
 
@@ -524,7 +524,7 @@ void FolderWizardSelectiveSync::initializePage()
 bool FolderWizardSelectiveSync::validatePage()
 {
     wizard()->setProperty("selectiveSyncBlackList", QVariant(_selectiveSync->createBlackList()));
-    wizard()->setProperty("usePlaceholders", QVariant(_placeholderCheckBox && _placeholderCheckBox->isChecked()));
+    wizard()->setProperty("useVirtualFiles", QVariant(_virtualFilesCheckBox && _virtualFilesCheckBox->isChecked()));
     return true;
 }
 
@@ -538,14 +538,14 @@ void FolderWizardSelectiveSync::cleanupPage()
     QWizardPage::cleanupPage();
 }
 
-void FolderWizardSelectiveSync::placeholderCheckboxClicked()
+void FolderWizardSelectiveSync::virtualFilesCheckboxClicked()
 {
     // The click has already had an effect on the box, so if it's
     // checked it was newly activated.
-    if (_placeholderCheckBox->isChecked()) {
-        OwncloudWizard::askExperimentalPlaceholderFeature([this](bool enable) {
+    if (_virtualFilesCheckBox->isChecked()) {
+        OwncloudWizard::askExperimentalVirtualFilesFeature([this](bool enable) {
             if (!enable)
-                _placeholderCheckBox->setChecked(false);
+                _virtualFilesCheckBox->setChecked(false);
         });
     }
 }
index 32e548f011312fa25eda85c0fc34d2782edf1d0e..0a9328b519d97cd5173cb8aea74513ec503ff01a 100644 (file)
@@ -131,11 +131,11 @@ public:
     void cleanupPage() override;
 
 private slots:
-    void placeholderCheckboxClicked();
+    void virtualFilesCheckboxClicked();
 
 private:
     SelectiveSyncWidget *_selectiveSync;
-    QCheckBox *_placeholderCheckBox = nullptr;
+    QCheckBox *_virtualFilesCheckBox = nullptr;
 };
 
 /**
index 8bc9b48fe0863311e2a0ae6ed8906a62afdce6cd..78c8b4b1b9bfc7b45e2d5b58fdb7be7487f6cf0c 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
   <mime-type type="application/x-@APPLICATION_EXECUTABLE@">
-    <comment>@APPLICATION_NAME@ placeholders</comment>
-    <glob pattern="*.@APPLICATION_PLACEHOLDER_SUFFIX@"/>
+    <comment>@APPLICATION_NAME@ virtual files</comment>
+    <glob pattern="*.@APPLICATION_VIRTUALFILE_SUFFIX@"/>
   </mime-type>
 </mime-info>
index 1d8ff440df6855e4e212de016c2a637fb0fb8ca8..efffcb6a73f559919cc6ed70dec476179be7c9bc 100644 (file)
@@ -633,7 +633,7 @@ void OwncloudSetupWizard::slotAssistantFinished(int result)
             folderDefinition.localPath = localFolder;
             folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_remoteFolder);
             folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
-            folderDefinition.usePlaceholders = _ocWizard->usePlaceholderSync();
+            folderDefinition.useVirtualFiles = _ocWizard->useVirtualFileSync();
             if (folderMan->navigationPaneHelper().showInExplorerNavigationPane())
                 folderDefinition.navigationPaneClsid = QUuid::createUuid();
 
index 50ac868b09189ad1d143320476c9d10e8ec4d3d3..04acddae88a97cd110447ec07c3f8b12f35ddb9f 100644 (file)
@@ -686,18 +686,18 @@ void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListen
     fetchPrivateLinkUrlHelper(localFile, &SocketApi::openPrivateLink);
 }
 
-void SocketApi::command_DOWNLOAD_PLACEHOLDER(const QString &filesArg, SocketListener *)
+void SocketApi::command_DOWNLOAD_VIRTUAL_FILE(const QString &filesArg, SocketListener *)
 {
     QStringList files = filesArg.split(QLatin1Char('\x1e')); // Record Separator
-    auto placeholderSuffix = QStringLiteral(APPLICATION_DOTPLACEHOLDER_SUFFIX);
+    auto suffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
 
     for (const auto &file : files) {
-        if (!file.endsWith(placeholderSuffix))
+        if (!file.endsWith(suffix))
             continue;
         auto folder = FolderMan::instance()->folderForPath(file);
         if (folder) {
             QString relativePath = QDir::cleanPath(file).mid(folder->cleanPath().length() + 1);
-            folder->downloadPlaceholder(relativePath);
+            folder->downloadVirtualFile(relativePath);
         }
     }
 }
@@ -972,16 +972,16 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
         }
     }
 
-    // Placeholder download action
+    // Virtual file download action
     if (syncFolder) {
-        auto placeholderSuffix = QStringLiteral(APPLICATION_DOTPLACEHOLDER_SUFFIX);
-        bool hasPlaceholderFile = false;
+        auto virtualFileSuffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
+        bool hasVirtualFile = false;
         for (const auto &file : files) {
-            if (file.endsWith(placeholderSuffix))
-                hasPlaceholderFile = true;
+            if (file.endsWith(virtualFileSuffix))
+                hasVirtualFile = true;
         }
-        if (hasPlaceholderFile)
-            listener->sendMessage(QLatin1String("MENU_ITEM:DOWNLOAD_PLACEHOLDER::") + tr("Download file(s)", "", files.size()));
+        if (hasVirtualFile)
+            listener->sendMessage(QLatin1String("MENU_ITEM:DOWNLOAD_VIRTUAL_FILE::") + tr("Download file(s)", "", files.size()));
     }
 
     listener->sendMessage(QString("GET_MENU_ITEMS:END"));
index e6cb5ecbaa7697c10d123fd0ffa0079fbfbf3005..cb5c357fd750d50406940081fb0785799d09f660 100644 (file)
@@ -106,7 +106,7 @@ private:
     Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
     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_PLACEHOLDER(const QString &filesArg, SocketListener *listener);
+    Q_INVOKABLE void command_DOWNLOAD_VIRTUAL_FILE(const QString &filesArg, SocketListener *listener);
     Q_INVOKABLE void command_RESOLVE_CONFLICT(const QString &localFile, SocketListener *listener);
     Q_INVOKABLE void command_DELETE_ITEM(const QString &localFile, SocketListener *listener);
     Q_INVOKABLE void command_MOVE_ITEM(const QString &localFile, SocketListener *listener);
index 97d0ed2e96164ca0e62d5ab63367ca8ad63f6dfb..245b871460f70a4f982c197701ca0ca9208ab1a3 100644 (file)
@@ -56,7 +56,7 @@ OwncloudAdvancedSetupPage::OwncloudAdvancedSetupPage()
 
     connect(_ui.rSyncEverything, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSyncEverythingClicked);
     connect(_ui.rSelectiveSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSelectiveSyncClicked);
-    connect(_ui.rPlaceholderSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotPlaceholderSyncClicked);
+    connect(_ui.rVirtualFileSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotVirtualFileSyncClicked);
     connect(_ui.bSelectiveSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSelectiveSyncClicked);
 
     QIcon appIcon = theme->applicationIcon();
@@ -106,8 +106,8 @@ void OwncloudAdvancedSetupPage::initializePage()
         // If the layout were wrapped in a widget, the auto-grouping of the
         // radio buttons no longer works and there are surprising margins.
         // Just manually hide the button and remove the layout.
-        _ui.rPlaceholderSync->hide();
-        _ui.wSyncStrategy->layout()->removeItem(_ui.lPlaceholderSync);
+        _ui.rVirtualFileSync->hide();
+        _ui.wSyncStrategy->layout()->removeItem(_ui.lVirtualFileSync);
     }
 
     _checking = false;
@@ -241,9 +241,9 @@ QStringList OwncloudAdvancedSetupPage::selectiveSyncBlacklist() const
     return _selectiveSyncBlacklist;
 }
 
-bool OwncloudAdvancedSetupPage::usePlaceholderSync() const
+bool OwncloudAdvancedSetupPage::useVirtualFileSync() const
 {
-    return _ui.rPlaceholderSync->isChecked();
+    return _ui.rVirtualFileSync->isChecked();
 }
 
 bool OwncloudAdvancedSetupPage::isConfirmBigFolderChecked() const
@@ -363,15 +363,15 @@ void OwncloudAdvancedSetupPage::slotSelectiveSyncClicked()
     }
 }
 
-void OwncloudAdvancedSetupPage::slotPlaceholderSyncClicked()
+void OwncloudAdvancedSetupPage::slotVirtualFileSyncClicked()
 {
-    OwncloudWizard::askExperimentalPlaceholderFeature([this](bool enable) {
+    OwncloudWizard::askExperimentalVirtualFilesFeature([this](bool enable) {
         if (!enable)
             return;
 
         _ui.lSelectiveSyncSizeLabel->setText(QString());
         _selectiveSyncBlacklist.clear();
-        setRadioChecked(_ui.rPlaceholderSync);
+        setRadioChecked(_ui.rVirtualFileSync);
     });
 }
 
@@ -422,15 +422,15 @@ void OwncloudAdvancedSetupPage::customizeStyle()
 void OwncloudAdvancedSetupPage::setRadioChecked(QRadioButton *radio)
 {
     // We don't want clicking the radio buttons to immediately adjust the checked state
-    // for selective sync and placeholder sync, so we keep them uncheckable until
+    // for selective sync and virtual file sync, so we keep them uncheckable until
     // they should be checked.
     radio->setCheckable(true);
     radio->setChecked(true);
 
     if (radio != _ui.rSelectiveSync)
         _ui.rSelectiveSync->setCheckable(false);
-    if (radio != _ui.rPlaceholderSync)
-        _ui.rPlaceholderSync->setCheckable(false);
+    if (radio != _ui.rVirtualFileSync)
+        _ui.rVirtualFileSync->setCheckable(false);
 }
 
 } // namespace OCC
index b4a24681c0639bdb4930026ac6f76ea43edd2e8e..7fcde225a716abdd89b0fbf1f125c65091fa77c4 100644 (file)
@@ -41,7 +41,7 @@ public:
     bool validatePage() override;
     QString localFolder() const;
     QStringList selectiveSyncBlacklist() const;
-    bool usePlaceholderSync() const;
+    bool useVirtualFileSync() const;
     bool isConfirmBigFolderChecked() const;
     void setRemoteFolder(const QString &remoteFolder);
     void setMultipleFoldersExist(bool exist);
@@ -58,7 +58,7 @@ private slots:
     void slotSelectFolder();
     void slotSyncEverythingClicked();
     void slotSelectiveSyncClicked();
-    void slotPlaceholderSyncClicked();
+    void slotVirtualFileSyncClicked();
     void slotQuotaRetrieved(const QVariantMap &result);
 
 private:
index 8964e34270a44b884ee3187368acdf8258c1483b..ff34d57fac9ad048997af5ac21a305f9b9075385 100644 (file)
          </layout>
         </item>
         <item>
-         <layout class="QHBoxLayout" name="lPlaceholderSync">
+         <layout class="QHBoxLayout" name="lVirtualFileSync">
           <item>
-           <widget class="QRadioButton" name="rPlaceholderSync">
+           <widget class="QRadioButton" name="rVirtualFileSync">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
               <horstretch>0</horstretch>
index effc6955fd40016b9d77b4b65f110bb0c38d441b..4c9f4e6c1f6035011418f647b64e9017a5eaeb16 100644 (file)
@@ -133,9 +133,9 @@ QStringList OwncloudWizard::selectiveSyncBlacklist() const
     return _advancedSetupPage->selectiveSyncBlacklist();
 }
 
-bool OwncloudWizard::usePlaceholderSync() const
+bool OwncloudWizard::useVirtualFileSync() const
 {
-    return _advancedSetupPage->usePlaceholderSync();
+    return _advancedSetupPage->useVirtualFileSync();
 }
 
 bool OwncloudWizard::isConfirmBigFolderChecked() const
@@ -326,7 +326,7 @@ void OwncloudWizard::bringToTop()
     ownCloudGui::raiseDialog(this);
 }
 
-void OwncloudWizard::askExperimentalPlaceholderFeature(const std::function<void(bool enable)> &callback)
+void OwncloudWizard::askExperimentalVirtualFilesFeature(const std::function<void(bool enable)> &callback)
 {
     auto msgBox = new QMessageBox(
         QMessageBox::Warning,
@@ -337,7 +337,7 @@ void OwncloudWizard::askExperimentalPlaceholderFeature(const std::function<void(
            "\n\n"
            "This is a new, experimental mode. If you decide to use it, please report any "
            "issues that come up.")
-            .arg(APPLICATION_DOTPLACEHOLDER_SUFFIX));
+            .arg(APPLICATION_DOTVIRTUALFILE_SUFFIX));
     msgBox->addButton(tr("Enable experimental mode"), QMessageBox::AcceptRole);
     msgBox->addButton(tr("Stay safe"), QMessageBox::RejectRole);
     connect(msgBox, &QMessageBox::finished, msgBox, [callback, msgBox](int result) {
index b81c2cd9f6ac0d9fcc0b44485e7a92c1f398800a..5a61a741fb42fe545c8520ec58c3fd6f5b570405 100644 (file)
@@ -67,7 +67,7 @@ public:
     QString ocUrl() const;
     QString localFolder() const;
     QStringList selectiveSyncBlacklist() const;
-    bool usePlaceholderSync() const;
+    bool useVirtualFileSync() const;
     bool isConfirmBigFolderChecked() const;
 
     void enableFinishOnResultWidget(bool enable);
@@ -78,11 +78,11 @@ public:
     void bringToTop();
 
     /**
-     * Shows a dialog explaining the placeholder mode and warning about it
+     * Shows a dialog explaining the virtual files mode and warning about it
      * being experimental. Calles the callback with true if enabling was
      * chosen.
      */
-    static void askExperimentalPlaceholderFeature(const std::function<void(bool enable)> &callback);
+    static void askExperimentalVirtualFilesFeature(const std::function<void(bool enable)> &callback);
 
     // FIXME: Can those be local variables?
     // Set from the OwncloudSetupPage, later used from OwncloudHttpCredsPage
index 14f3c03f432df21a365afecc8398f23360c91ad1..b55f594412786b5cf0590510a9972129cf0a0e1d 100644 (file)
@@ -606,9 +606,9 @@ QString OwncloudPropagator::getFilePath(const QString &tmp_file_name) const
     return _localDir + tmp_file_name;
 }
 
-QString OwncloudPropagator::addPlaceholderSuffix(const QString &fileName) const
+QString OwncloudPropagator::addVirtualFileSuffix(const QString &fileName) const
 {
-    return fileName + _syncOptions._placeholderSuffix;
+    return fileName + _syncOptions._virtualFileSuffix;
 }
 
 void OwncloudPropagator::scheduleNextJob()
index 39419cd5b2da5b93a7007b2411e08148e1d32a64..245f03f3ce9455f43f6f63a1fdde54f2ac961426 100644 (file)
@@ -453,7 +453,7 @@ public:
 
     /* returns the local file path for the given tmp_file_name */
     QString getFilePath(const QString &tmp_file_name) const;
-    QString addPlaceholderSuffix(const QString &fileName) const;
+    QString addVirtualFileSuffix(const QString &fileName) const;
 
     /** Creates the job for an item.
      */
index 69ba22bd74864da3a8b068243a1c1e1f803a963c..d12bccc621a97a9476ea8567f8356d7f270778a3 100644 (file)
@@ -387,10 +387,10 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
 {
     _stopwatch.start();
 
-    // For placeholder files just create the file and be done
-    if (_item->_type == ItemTypePlaceholder) {
+    // For virtual files just create the file and be done
+    if (_item->_type == ItemTypeVirtualFile) {
         auto fn = propagator()->getFilePath(_item->_file);
-        qCDebug(lcPropagateDownload) << "creating placeholder file" << fn;
+        qCDebug(lcPropagateDownload) << "creating virtual file" << fn;
         QFile file(fn);
         file.open(QFile::ReadWrite | QFile::Truncate);
         file.write(" ");
@@ -399,14 +399,14 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
         return;
     }
 
-    // If we want to download something that used to be a placeholder,
-    // wipe the placeholder and proceed with a normal download
-    if (_item->_type == ItemTypePlaceholderDownload) {
-        auto placeholder = propagator()->addPlaceholderSuffix(_item->_file);
-        auto fn = propagator()->getFilePath(placeholder);
-        qCDebug(lcPropagateDownload) << "Downloading file that used to be a placeholder" << fn;
+    // If we want to download something that used to be a virtual file,
+    // wipe the virtual file and proceed with a normal download
+    if (_item->_type == ItemTypeVirtualFileDownload) {
+        auto virtualFile = propagator()->addVirtualFileSuffix(_item->_file);
+        auto fn = propagator()->getFilePath(virtualFile);
+        qCDebug(lcPropagateDownload) << "Downloading file that used to be a virtual file" << fn;
         QFile::remove(fn);
-        propagator()->_journal->deleteFileRecord(placeholder);
+        propagator()->_journal->deleteFileRecord(virtualFile);
         _item->_type = ItemTypeFile;
     }
 
index a6f3e9c28998cbdebd167dc5519abe8f169593fe..1ca1d47a748fdc4dd7ae12b544dbac6a70cd1198 100644 (file)
@@ -621,7 +621,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
             if (remote) {
                 QString filePath = _localPath + item->_file;
 
-                if (other && other->type != ItemTypePlaceholder && other->type != ItemTypePlaceholderDownload) {
+                if (other && other->type != ItemTypeVirtualFile && other->type != ItemTypeVirtualFileDownload) {
                     // Even if the mtime is different on the server, we always want to keep the mtime from
                     // the file system in the DB, this is to avoid spurious upload on the next sync
                     item->_modtime = other->modtime;
@@ -858,11 +858,11 @@ void SyncEngine::startSync()
         return shouldDiscoverLocally(path);
     };
 
-    _csync_ctx->new_files_are_placeholders = _syncOptions._newFilesArePlaceholders;
-    _csync_ctx->placeholder_suffix = _syncOptions._placeholderSuffix.toUtf8();
+    _csync_ctx->new_files_are_virtual = _syncOptions._newFilesAreVirtual;
+    _csync_ctx->virtual_file_suffix = _syncOptions._virtualFileSuffix.toUtf8();
 
-    if (_csync_ctx->new_files_are_placeholders && _csync_ctx->placeholder_suffix.isEmpty()) {
-        csyncError(tr("Using virtual files but placeholder suffix is not set"));
+    if (_csync_ctx->new_files_are_virtual && _csync_ctx->virtual_file_suffix.isEmpty()) {
+        csyncError(tr("Using virtual files but suffix is not set"));
         finalize(false);
         return;
     }
index ea48cd83e1124bcd70cb341e51cb7057df2c125d..43ef3bf189614be56f86bf91d6ea02bcf0d5eac8 100644 (file)
@@ -36,9 +36,9 @@ struct SyncOptions
     /** If remotely deleted files are needed to move to trash */
     bool _moveFilesToTrash = false;
 
-    /** Create a placeholder for new files instead of downloading */
-    bool _newFilesArePlaceholders = false;
-    QString _placeholderSuffix = ".owncloud";
+    /** Create a virtual file for new files instead of downloading */
+    bool _newFilesAreVirtual = false;
+    QString _virtualFileSuffix = ".owncloud";
 
     /** The initial un-adjusted chunk size in bytes for chunked uploads, both
      * for old and new chunking algorithm, which classifies the item to be chunked
index a3ff8e21453c12130489803073a1abffe0124c4d..3d9034cbaa986af296bde9b8b4ded7e8524bc175 100644 (file)
@@ -46,7 +46,7 @@ nextcloud_add_test(ExcludedFiles "")
 nextcloud_add_test(FileSystem "")
 nextcloud_add_test(Utility "")
 nextcloud_add_test(SyncEngine "syncenginetestutils.h")
-nextcloud_add_test(SyncPlaceholders "syncenginetestutils.h")
+nextcloud_add_test(SyncVirtualFiles "syncenginetestutils.h")
 nextcloud_add_test(SyncMove "syncenginetestutils.h")
 nextcloud_add_test(SyncConflict "syncenginetestutils.h")
 nextcloud_add_test(SyncFileStatusTracker "syncenginetestutils.h")
diff --git a/test/testsyncplaceholders.cpp b/test/testsyncplaceholders.cpp
deleted file mode 100644 (file)
index 454ef45..0000000
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- *    This software is in the public domain, furnished "as is", without technical
- *    support, and with no warranty, express or implied, as to its usefulness for
- *    any purpose.
- *
- */
-
-#include <QtTest>
-#include "syncenginetestutils.h"
-#include <syncengine.h>
-
-using namespace OCC;
-
-SyncFileItemPtr findItem(const QSignalSpy &spy, const QString &path)
-{
-    for (const QList<QVariant> &args : spy) {
-        auto item = args[0].value<SyncFileItemPtr>();
-        if (item->destination() == path)
-            return item;
-    }
-    return SyncFileItemPtr(new SyncFileItem);
-}
-
-bool itemInstruction(const QSignalSpy &spy, const QString &path, const csync_instructions_e instr)
-{
-    auto item = findItem(spy, path);
-    return item->_instruction == instr;
-}
-
-SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path)
-{
-    SyncJournalFileRecord record;
-    folder.syncJournal().getFileRecord(path, &record);
-    return record;
-}
-
-class TestSyncPlaceholders : public QObject
-{
-    Q_OBJECT
-
-private slots:
-    void testPlaceholderLifecycle_data()
-    {
-        QTest::addColumn<bool>("doLocalDiscovery");
-
-        QTest::newRow("full local discovery") << true;
-        QTest::newRow("skip local discovery") << false;
-    }
-
-    void testPlaceholderLifecycle()
-    {
-        QFETCH(bool, doLocalDiscovery);
-
-        FakeFolder fakeFolder{FileInfo()};
-        SyncOptions syncOptions;
-        syncOptions._newFilesArePlaceholders = true;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
-
-        auto cleanup = [&]() {
-            completeSpy.clear();
-            if (!doLocalDiscovery)
-                fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem);
-        };
-        cleanup();
-
-        // Create a placeholder for a new remote file
-        fakeFolder.remoteModifier().mkdir("A");
-        fakeFolder.remoteModifier().insert("A/a1", 64);
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
-        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
-        cleanup();
-
-        // Another sync doesn't actually lead to changes
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
-        QVERIFY(completeSpy.isEmpty());
-        cleanup();
-
-        // Not even when the remote is rediscovered
-        fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
-        QVERIFY(completeSpy.isEmpty());
-        cleanup();
-
-        // Neither does a remote change
-        fakeFolder.remoteModifier().appendByte("A/a1");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
-        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
-        cleanup();
-
-        // If the local placeholder file is removed, it'll just be recreated
-        if (!doLocalDiscovery)
-            fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, { "A" });
-        fakeFolder.localModifier().remove("A/a1.owncloud");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
-        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
-        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
-        cleanup();
-
-        // Remote rename is propagated
-        fakeFolder.remoteModifier().rename("A/a1", "A/a1m");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1m"));
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1m.owncloud"));
-        QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1m"));
-        QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_RENAME));
-        QCOMPARE(dbRecord(fakeFolder, "A/a1m.owncloud")._type, ItemTypePlaceholder);
-        cleanup();
-
-        // Remote remove is propagated
-        fakeFolder.remoteModifier().remove("A/a1m");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1m.owncloud"));
-        QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m"));
-        QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_REMOVE));
-        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a1m.owncloud").isValid());
-        cleanup();
-
-        // Edge case: Local placeholder but no db entry for some reason
-        fakeFolder.remoteModifier().insert("A/a2", 64);
-        fakeFolder.remoteModifier().insert("A/a3", 64);
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
-        cleanup();
-
-        fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2.owncloud");
-        fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3.owncloud");
-        fakeFolder.remoteModifier().remove("A/a3");
-        fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly);
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
-        QVERIFY(itemInstruction(completeSpy, "A/a2.owncloud", CSYNC_INSTRUCTION_NEW));
-        QVERIFY(dbRecord(fakeFolder, "A/a2.owncloud").isValid());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a3.owncloud"));
-        QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
-        cleanup();
-    }
-
-    void testPlaceholderConflict()
-    {
-        FakeFolder fakeFolder{ FileInfo() };
-        SyncOptions syncOptions;
-        syncOptions._newFilesArePlaceholders = true;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
-
-        auto cleanup = [&]() {
-            completeSpy.clear();
-        };
-        cleanup();
-
-        // Create a placeholder for a new remote file
-        fakeFolder.remoteModifier().mkdir("A");
-        fakeFolder.remoteModifier().insert("A/a1", 64);
-        fakeFolder.remoteModifier().insert("A/a2", 64);
-        fakeFolder.remoteModifier().mkdir("B");
-        fakeFolder.remoteModifier().insert("B/b1", 64);
-        fakeFolder.remoteModifier().insert("B/b2", 64);
-        fakeFolder.remoteModifier().mkdir("C");
-        fakeFolder.remoteModifier().insert("C/c1", 64);
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("B/b2.owncloud"));
-        cleanup();
-
-        // A: the correct file and a conflicting file are added, placeholders stay
-        // B: same setup, but the placeholders are deleted by the user
-        // C: user adds a *directory* locally
-        fakeFolder.localModifier().insert("A/a1", 64);
-        fakeFolder.localModifier().insert("A/a2", 30);
-        fakeFolder.localModifier().insert("B/b1", 64);
-        fakeFolder.localModifier().insert("B/b2", 30);
-        fakeFolder.localModifier().remove("B/b1.owncloud");
-        fakeFolder.localModifier().remove("B/b2.owncloud");
-        fakeFolder.localModifier().mkdir("C/c1");
-        fakeFolder.localModifier().insert("C/c1/foo");
-        QVERIFY(fakeFolder.syncOnce());
-
-        // Everything is CONFLICT since mtimes are different even for a1/b1
-        QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_CONFLICT));
-        QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_CONFLICT));
-        QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_CONFLICT));
-        QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_CONFLICT));
-        QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_CONFLICT));
-
-        // no placeholder files should remain
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a2.owncloud"));
-        QVERIFY(!fakeFolder.currentLocalState().find("B/b1.owncloud"));
-        QVERIFY(!fakeFolder.currentLocalState().find("B/b2.owncloud"));
-        QVERIFY(!fakeFolder.currentLocalState().find("C/c1.owncloud"));
-
-        // conflict files should exist
-        QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 3);
-
-        // nothing should have the placeholder tag
-        QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "B/b1")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "B/b2")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "C/c1")._type, ItemTypeFile);
-        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "B/b1.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "B/b2.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "C/c1.owncloud").isValid());
-
-        cleanup();
-    }
-
-    void testWithNormalSync()
-    {
-        FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
-        SyncOptions syncOptions;
-        syncOptions._newFilesArePlaceholders = true;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
-
-        auto cleanup = [&]() {
-            completeSpy.clear();
-        };
-        cleanup();
-
-        // No effect sync
-        QVERIFY(fakeFolder.syncOnce());
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        cleanup();
-
-        // Existing files are propagated just fine in both directions
-        fakeFolder.localModifier().appendByte("A/a1");
-        fakeFolder.localModifier().insert("A/a3");
-        fakeFolder.remoteModifier().appendByte("A/a2");
-        QVERIFY(fakeFolder.syncOnce());
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        cleanup();
-
-        // New files on the remote create placeholders
-        fakeFolder.remoteModifier().insert("A/new");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/new"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/new.owncloud"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/new"));
-        QVERIFY(itemInstruction(completeSpy, "A/new.owncloud", CSYNC_INSTRUCTION_NEW));
-        QCOMPARE(dbRecord(fakeFolder, "A/new.owncloud")._type, ItemTypePlaceholder);
-        cleanup();
-    }
-
-    void testPlaceholderDownload()
-    {
-        FakeFolder fakeFolder{FileInfo()};
-        SyncOptions syncOptions;
-        syncOptions._newFilesArePlaceholders = true;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
-
-        auto cleanup = [&]() {
-            completeSpy.clear();
-        };
-        cleanup();
-
-        auto triggerDownload = [&](const QByteArray &path) {
-            auto &journal = fakeFolder.syncJournal();
-            SyncJournalFileRecord record;
-            journal.getFileRecord(path + ".owncloud", &record);
-            if (!record.isValid())
-                return;
-            record._type = ItemTypePlaceholderDownload;
-            journal.setFileRecord(record);
-        };
-
-        // Create a placeholder for remote files
-        fakeFolder.remoteModifier().mkdir("A");
-        fakeFolder.remoteModifier().insert("A/a1");
-        fakeFolder.remoteModifier().insert("A/a2");
-        fakeFolder.remoteModifier().insert("A/a3");
-        fakeFolder.remoteModifier().insert("A/a4");
-        fakeFolder.remoteModifier().insert("A/a5");
-        fakeFolder.remoteModifier().insert("A/a6");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a4.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a5.owncloud"));
-        QVERIFY(fakeFolder.currentLocalState().find("A/a6.owncloud"));
-        cleanup();
-
-        // Download by changing the db entry
-        triggerDownload("A/a1");
-        triggerDownload("A/a2");
-        triggerDownload("A/a3");
-        triggerDownload("A/a4");
-        triggerDownload("A/a5");
-        triggerDownload("A/a6");
-        fakeFolder.remoteModifier().appendByte("A/a2");
-        fakeFolder.remoteModifier().remove("A/a3");
-        fakeFolder.remoteModifier().rename("A/a4", "A/a4m");
-        fakeFolder.localModifier().insert("A/a5");
-        fakeFolder.localModifier().insert("A/a6");
-        fakeFolder.localModifier().remove("A/a6.owncloud");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
-        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_REMOVE));
-        QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_NEW));
-        QVERIFY(itemInstruction(completeSpy, "A/a2.owncloud", CSYNC_INSTRUCTION_REMOVE));
-        QVERIFY(itemInstruction(completeSpy, "A/a3.owncloud", CSYNC_INSTRUCTION_REMOVE));
-        QVERIFY(itemInstruction(completeSpy, "A/a4.owncloud", CSYNC_INSTRUCTION_REMOVE));
-        QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
-        QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
-        QVERIFY(itemInstruction(completeSpy, "A/a5.owncloud", CSYNC_INSTRUCTION_REMOVE));
-        QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
-        QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
-        QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile);
-        QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile);
-        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a4.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a5.owncloud").isValid());
-        QVERIFY(!dbRecord(fakeFolder, "A/a6.owncloud").isValid());
-    }
-
-    // Check what might happen if an older sync client encounters placeholders
-    void testOldVersion1()
-    {
-        FakeFolder fakeFolder{ FileInfo() };
-        SyncOptions syncOptions;
-        syncOptions._newFilesArePlaceholders = true;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-
-        // Create a placeholder
-        fakeFolder.remoteModifier().mkdir("A");
-        fakeFolder.remoteModifier().insert("A/a1");
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-
-        // Simulate an old client by switching the type of all ItemTypePlaceholder
-        // entries in the db to an invalid type.
-        auto &db = fakeFolder.syncJournal();
-        SyncJournalFileRecord rec;
-        db.getFileRecord(QByteArray("A/a1.owncloud"), &rec);
-        QVERIFY(rec.isValid());
-        QCOMPARE(rec._type, ItemTypePlaceholder);
-        rec._type = static_cast<ItemType>(-1);
-        db.setFileRecord(rec);
-
-        // Also switch off new files becoming placeholders
-        syncOptions._newFilesArePlaceholders = false;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-
-        // A sync that doesn't do remote discovery has no effect
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
-        QVERIFY(!fakeFolder.currentRemoteState().find("A/a1.owncloud"));
-
-        // But with a remote discovery the placeholders will be removed and
-        // the remote files will be downloaded.
-        db.forceRemoteDiscoveryNextSync();
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-    }
-
-    // Older versions may leave db entries for foo and foo.owncloud
-    void testOldVersion2()
-    {
-        FakeFolder fakeFolder{ FileInfo() };
-
-        // Sync a file
-        fakeFolder.remoteModifier().mkdir("A");
-        fakeFolder.remoteModifier().insert("A/a1");
-        QVERIFY(fakeFolder.syncOnce());
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-
-        // Create the placeholder too
-        // In the wild, the new version would create the placeholder and the db entry
-        // while the old version would download the plain file.
-        fakeFolder.localModifier().insert("A/a1.owncloud");
-        auto &db = fakeFolder.syncJournal();
-        SyncJournalFileRecord rec;
-        db.getFileRecord(QByteArray("A/a1"), &rec);
-        rec._type = ItemTypePlaceholder;
-        rec._path = "A/a1.owncloud";
-        db.setFileRecord(rec);
-
-        SyncOptions syncOptions;
-        syncOptions._newFilesArePlaceholders = true;
-        fakeFolder.syncEngine().setSyncOptions(syncOptions);
-
-        // Check that a sync removes the placeholder and its db entry
-        QVERIFY(fakeFolder.syncOnce());
-        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
-        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
-        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
-    }
-};
-
-QTEST_GUILESS_MAIN(TestSyncPlaceholders)
-#include "testsyncplaceholders.moc"
diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp
new file mode 100644 (file)
index 0000000..8ec09a6
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+ *    This software is in the public domain, furnished "as is", without technical
+ *    support, and with no warranty, express or implied, as to its usefulness for
+ *    any purpose.
+ *
+ */
+
+#include <QtTest>
+#include "syncenginetestutils.h"
+#include <syncengine.h>
+
+using namespace OCC;
+
+SyncFileItemPtr findItem(const QSignalSpy &spy, const QString &path)
+{
+    for (const QList<QVariant> &args : spy) {
+        auto item = args[0].value<SyncFileItemPtr>();
+        if (item->destination() == path)
+            return item;
+    }
+    return SyncFileItemPtr(new SyncFileItem);
+}
+
+bool itemInstruction(const QSignalSpy &spy, const QString &path, const csync_instructions_e instr)
+{
+    auto item = findItem(spy, path);
+    return item->_instruction == instr;
+}
+
+SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path)
+{
+    SyncJournalFileRecord record;
+    folder.syncJournal().getFileRecord(path, &record);
+    return record;
+}
+
+class TestSyncVirtualFiles : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void testVirtualFileLifecycle_data()
+    {
+        QTest::addColumn<bool>("doLocalDiscovery");
+
+        QTest::newRow("full local discovery") << true;
+        QTest::newRow("skip local discovery") << false;
+    }
+
+    void testVirtualFileLifecycle()
+    {
+        QFETCH(bool, doLocalDiscovery);
+
+        FakeFolder fakeFolder{ FileInfo() };
+        SyncOptions syncOptions;
+        syncOptions._newFilesAreVirtual = true;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
+
+        auto cleanup = [&]() {
+            completeSpy.clear();
+            if (!doLocalDiscovery)
+                fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem);
+        };
+        cleanup();
+
+        // Create a virtual file for a new remote file
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert("A/a1", 64);
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+        cleanup();
+
+        // Another sync doesn't actually lead to changes
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+        QVERIFY(completeSpy.isEmpty());
+        cleanup();
+
+        // Not even when the remote is rediscovered
+        fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+        QVERIFY(completeSpy.isEmpty());
+        cleanup();
+
+        // Neither does a remote change
+        fakeFolder.remoteModifier().appendByte("A/a1");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
+        cleanup();
+
+        // If the local virtual file file is removed, it'll just be recreated
+        if (!doLocalDiscovery)
+            fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, { "A" });
+        fakeFolder.localModifier().remove("A/a1.owncloud");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+        QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
+        cleanup();
+
+        // Remote rename is propagated
+        fakeFolder.remoteModifier().rename("A/a1", "A/a1m");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1m"));
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1m.owncloud"));
+        QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1m"));
+        QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_RENAME));
+        QCOMPARE(dbRecord(fakeFolder, "A/a1m.owncloud")._type, ItemTypeVirtualFile);
+        cleanup();
+
+        // Remote remove is propagated
+        fakeFolder.remoteModifier().remove("A/a1m");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1m.owncloud"));
+        QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m"));
+        QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_REMOVE));
+        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a1m.owncloud").isValid());
+        cleanup();
+
+        // Edge case: Local virtual file but no db entry for some reason
+        fakeFolder.remoteModifier().insert("A/a2", 64);
+        fakeFolder.remoteModifier().insert("A/a3", 64);
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
+        cleanup();
+
+        fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2.owncloud");
+        fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3.owncloud");
+        fakeFolder.remoteModifier().remove("A/a3");
+        fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly);
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
+        QVERIFY(itemInstruction(completeSpy, "A/a2.owncloud", CSYNC_INSTRUCTION_NEW));
+        QVERIFY(dbRecord(fakeFolder, "A/a2.owncloud").isValid());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a3.owncloud"));
+        QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
+        cleanup();
+    }
+
+    void testVirtualFileConflict()
+    {
+        FakeFolder fakeFolder{ FileInfo() };
+        SyncOptions syncOptions;
+        syncOptions._newFilesAreVirtual = true;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
+
+        auto cleanup = [&]() {
+            completeSpy.clear();
+        };
+        cleanup();
+
+        // Create a virtual file for a new remote file
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert("A/a1", 64);
+        fakeFolder.remoteModifier().insert("A/a2", 64);
+        fakeFolder.remoteModifier().mkdir("B");
+        fakeFolder.remoteModifier().insert("B/b1", 64);
+        fakeFolder.remoteModifier().insert("B/b2", 64);
+        fakeFolder.remoteModifier().mkdir("C");
+        fakeFolder.remoteModifier().insert("C/c1", 64);
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("B/b2.owncloud"));
+        cleanup();
+
+        // A: the correct file and a conflicting file are added, virtual files stay
+        // B: same setup, but the virtual files are deleted by the user
+        // C: user adds a *directory* locally
+        fakeFolder.localModifier().insert("A/a1", 64);
+        fakeFolder.localModifier().insert("A/a2", 30);
+        fakeFolder.localModifier().insert("B/b1", 64);
+        fakeFolder.localModifier().insert("B/b2", 30);
+        fakeFolder.localModifier().remove("B/b1.owncloud");
+        fakeFolder.localModifier().remove("B/b2.owncloud");
+        fakeFolder.localModifier().mkdir("C/c1");
+        fakeFolder.localModifier().insert("C/c1/foo");
+        QVERIFY(fakeFolder.syncOnce());
+
+        // Everything is CONFLICT since mtimes are different even for a1/b1
+        QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_CONFLICT));
+        QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_CONFLICT));
+        QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_CONFLICT));
+        QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_CONFLICT));
+        QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_CONFLICT));
+
+        // no virtual file files should remain
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a2.owncloud"));
+        QVERIFY(!fakeFolder.currentLocalState().find("B/b1.owncloud"));
+        QVERIFY(!fakeFolder.currentLocalState().find("B/b2.owncloud"));
+        QVERIFY(!fakeFolder.currentLocalState().find("C/c1.owncloud"));
+
+        // conflict files should exist
+        QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 3);
+
+        // nothing should have the virtual file tag
+        QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "B/b1")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "B/b2")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "C/c1")._type, ItemTypeFile);
+        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "B/b1.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "B/b2.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "C/c1.owncloud").isValid());
+
+        cleanup();
+    }
+
+    void testWithNormalSync()
+    {
+        FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
+        SyncOptions syncOptions;
+        syncOptions._newFilesAreVirtual = true;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
+
+        auto cleanup = [&]() {
+            completeSpy.clear();
+        };
+        cleanup();
+
+        // No effect sync
+        QVERIFY(fakeFolder.syncOnce());
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        cleanup();
+
+        // Existing files are propagated just fine in both directions
+        fakeFolder.localModifier().appendByte("A/a1");
+        fakeFolder.localModifier().insert("A/a3");
+        fakeFolder.remoteModifier().appendByte("A/a2");
+        QVERIFY(fakeFolder.syncOnce());
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        cleanup();
+
+        // New files on the remote create virtual files
+        fakeFolder.remoteModifier().insert("A/new");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/new"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/new.owncloud"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/new"));
+        QVERIFY(itemInstruction(completeSpy, "A/new.owncloud", CSYNC_INSTRUCTION_NEW));
+        QCOMPARE(dbRecord(fakeFolder, "A/new.owncloud")._type, ItemTypeVirtualFile);
+        cleanup();
+    }
+
+    void testVirtualFileDownload()
+    {
+        FakeFolder fakeFolder{ FileInfo() };
+        SyncOptions syncOptions;
+        syncOptions._newFilesAreVirtual = true;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
+
+        auto cleanup = [&]() {
+            completeSpy.clear();
+        };
+        cleanup();
+
+        auto triggerDownload = [&](const QByteArray &path) {
+            auto &journal = fakeFolder.syncJournal();
+            SyncJournalFileRecord record;
+            journal.getFileRecord(path + ".owncloud", &record);
+            if (!record.isValid())
+                return;
+            record._type = ItemTypeVirtualFileDownload;
+            journal.setFileRecord(record);
+        };
+
+        // Create a virtual file for remote files
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert("A/a1");
+        fakeFolder.remoteModifier().insert("A/a2");
+        fakeFolder.remoteModifier().insert("A/a3");
+        fakeFolder.remoteModifier().insert("A/a4");
+        fakeFolder.remoteModifier().insert("A/a5");
+        fakeFolder.remoteModifier().insert("A/a6");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a4.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a5.owncloud"));
+        QVERIFY(fakeFolder.currentLocalState().find("A/a6.owncloud"));
+        cleanup();
+
+        // Download by changing the db entry
+        triggerDownload("A/a1");
+        triggerDownload("A/a2");
+        triggerDownload("A/a3");
+        triggerDownload("A/a4");
+        triggerDownload("A/a5");
+        triggerDownload("A/a6");
+        fakeFolder.remoteModifier().appendByte("A/a2");
+        fakeFolder.remoteModifier().remove("A/a3");
+        fakeFolder.remoteModifier().rename("A/a4", "A/a4m");
+        fakeFolder.localModifier().insert("A/a5");
+        fakeFolder.localModifier().insert("A/a6");
+        fakeFolder.localModifier().remove("A/a6.owncloud");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
+        QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_REMOVE));
+        QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_NEW));
+        QVERIFY(itemInstruction(completeSpy, "A/a2.owncloud", CSYNC_INSTRUCTION_REMOVE));
+        QVERIFY(itemInstruction(completeSpy, "A/a3.owncloud", CSYNC_INSTRUCTION_REMOVE));
+        QVERIFY(itemInstruction(completeSpy, "A/a4.owncloud", CSYNC_INSTRUCTION_REMOVE));
+        QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
+        QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
+        QVERIFY(itemInstruction(completeSpy, "A/a5.owncloud", CSYNC_INSTRUCTION_REMOVE));
+        QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
+        QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
+        QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile);
+        QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile);
+        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a4.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a5.owncloud").isValid());
+        QVERIFY(!dbRecord(fakeFolder, "A/a6.owncloud").isValid());
+    }
+
+    // Check what might happen if an older sync client encounters virtual files
+    void testOldVersion1()
+    {
+        FakeFolder fakeFolder{ FileInfo() };
+        SyncOptions syncOptions;
+        syncOptions._newFilesAreVirtual = true;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+        // Create a virtual file
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert("A/a1");
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+
+        // Simulate an old client by switching the type of all ItemTypeVirtualFile
+        // entries in the db to an invalid type.
+        auto &db = fakeFolder.syncJournal();
+        SyncJournalFileRecord rec;
+        db.getFileRecord(QByteArray("A/a1.owncloud"), &rec);
+        QVERIFY(rec.isValid());
+        QCOMPARE(rec._type, ItemTypeVirtualFile);
+        rec._type = static_cast<ItemType>(-1);
+        db.setFileRecord(rec);
+
+        // Also switch off new files becoming virtual files
+        syncOptions._newFilesAreVirtual = false;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+
+        // A sync that doesn't do remote discovery has no effect
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+        QVERIFY(!fakeFolder.currentRemoteState().find("A/a1.owncloud"));
+
+        // But with a remote discovery the virtual files will be removed and
+        // the remote files will be downloaded.
+        db.forceRemoteDiscoveryNextSync();
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+    }
+
+    // Older versions may leave db entries for foo and foo.owncloud
+    void testOldVersion2()
+    {
+        FakeFolder fakeFolder{ FileInfo() };
+
+        // Sync a file
+        fakeFolder.remoteModifier().mkdir("A");
+        fakeFolder.remoteModifier().insert("A/a1");
+        QVERIFY(fakeFolder.syncOnce());
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+        // Create the virtual file too
+        // In the wild, the new version would create the virtual file and the db entry
+        // while the old version would download the plain file.
+        fakeFolder.localModifier().insert("A/a1.owncloud");
+        auto &db = fakeFolder.syncJournal();
+        SyncJournalFileRecord rec;
+        db.getFileRecord(QByteArray("A/a1"), &rec);
+        rec._type = ItemTypeVirtualFile;
+        rec._path = "A/a1.owncloud";
+        db.setFileRecord(rec);
+
+        SyncOptions syncOptions;
+        syncOptions._newFilesAreVirtual = true;
+        fakeFolder.syncEngine().setSyncOptions(syncOptions);
+
+        // Check that a sync removes the virtual file and its db entry
+        QVERIFY(fakeFolder.syncOnce());
+        QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+        QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+        QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+    }
+};
+
+QTEST_GUILESS_MAIN(TestSyncVirtualFiles)
+#include "testsyncvirtualfiles.moc"