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
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}")
<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>
!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@"
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
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
#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@
ItemTypeSoftLink = 1,
ItemTypeDirectory = 2,
ItemTypeSkip = 3,
- ItemTypePlaceholder = 4,
- ItemTypePlaceholderDownload = 5
+ ItemTypeVirtualFile = 4,
+ ItemTypeVirtualFileDownload = 5
};
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();
/* 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;
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;
}
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;
}
}
- // 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();
}
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;
}
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.
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;
}
// 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;
// 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
}
}
- // 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;
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);
}
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);
_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() + "'");
}
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;
}
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);
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);
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 &);
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
_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
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"
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()) {
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())
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
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;
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();
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);
}
}
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;
}
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);
});
}
}
void cleanupPage() override;
private slots:
- void placeholderCheckboxClicked();
+ void virtualFilesCheckboxClicked();
private:
SelectiveSyncWidget *_selectiveSync;
- QCheckBox *_placeholderCheckBox = nullptr;
+ QCheckBox *_virtualFilesCheckBox = nullptr;
};
/**
<?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>
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();
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);
}
}
}
}
}
- // 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"));
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);
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();
// 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;
return _selectiveSyncBlacklist;
}
-bool OwncloudAdvancedSetupPage::usePlaceholderSync() const
+bool OwncloudAdvancedSetupPage::useVirtualFileSync() const
{
- return _ui.rPlaceholderSync->isChecked();
+ return _ui.rVirtualFileSync->isChecked();
}
bool OwncloudAdvancedSetupPage::isConfirmBigFolderChecked() const
}
}
-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);
});
}
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
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);
void slotSelectFolder();
void slotSyncEverythingClicked();
void slotSelectiveSyncClicked();
- void slotPlaceholderSyncClicked();
+ void slotVirtualFileSyncClicked();
void slotQuotaRetrieved(const QVariantMap &result);
private:
</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>
return _advancedSetupPage->selectiveSyncBlacklist();
}
-bool OwncloudWizard::usePlaceholderSync() const
+bool OwncloudWizard::useVirtualFileSync() const
{
- return _advancedSetupPage->usePlaceholderSync();
+ return _advancedSetupPage->useVirtualFileSync();
}
bool OwncloudWizard::isConfirmBigFolderChecked() const
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,
"\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) {
QString ocUrl() const;
QString localFolder() const;
QStringList selectiveSyncBlacklist() const;
- bool usePlaceholderSync() const;
+ bool useVirtualFileSync() const;
bool isConfirmBigFolderChecked() const;
void enableFinishOnResultWidget(bool enable);
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
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()
/* 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.
*/
{
_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(" ");
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;
}
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;
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;
}
/** 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
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")
+++ /dev/null
-/*
- * 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"
--- /dev/null
+/*
+ * 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"