diff --git a/src/libsync/bulkpropagatordownloadjob.cpp b/src/libsync/bulkpropagatordownloadjob.cpp index 4a2e28640ddc1..e2e93da98b815 100644 --- a/src/libsync/bulkpropagatordownloadjob.cpp +++ b/src/libsync/bulkpropagatordownloadjob.cpp @@ -40,6 +40,69 @@ BulkPropagatorDownloadJob::BulkPropagatorDownloadJob(OwncloudPropagator *propaga } } +namespace +{ +static QString makeRecallFileName(const QString &fn) +{ + QString recallFileName(fn); + // Add _recall-XXXX before the extension. + int dotLocation = recallFileName.lastIndexOf('.'); + // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file) + if (dotLocation <= recallFileName.lastIndexOf('/') + 1) { + dotLocation = recallFileName.size(); + } + + QString timeString = QDateTime::currentDateTimeUtc().toString("yyyyMMdd-hhmmss"); + recallFileName.insert(dotLocation, "_.sys.admin#recall#-" + timeString); + + return recallFileName; +} + +void handleRecallFile(const QString &filePath, const QString &folderPath, SyncJournalDb &journal) +{ + qCDebug(lcBulkPropagatorDownloadJob) << "handleRecallFile: " << filePath; + + FileSystem::setFileHidden(filePath, true); + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(lcBulkPropagatorDownloadJob) << "Could not open recall file" << file.errorString(); + return; + } + QFileInfo existingFile(filePath); + QDir baseDir = existingFile.dir(); + + while (!file.atEnd()) { + QByteArray line = file.readLine(); + line.chop(1); // remove trailing \n + + QString recalledFile = QDir::cleanPath(baseDir.filePath(line)); + if (!recalledFile.startsWith(folderPath) || !recalledFile.startsWith(baseDir.path())) { + qCWarning(lcBulkPropagatorDownloadJob) << "Ignoring recall of " << recalledFile; + continue; + } + + // Path of the recalled file in the local folder + QString localRecalledFile = recalledFile.mid(folderPath.size()); + + SyncJournalFileRecord record; + if (!journal.getFileRecord(localRecalledFile, &record) || !record.isValid()) { + qCWarning(lcBulkPropagatorDownloadJob) << "No db entry for recall of" << localRecalledFile; + continue; + } + + qCInfo(lcBulkPropagatorDownloadJob) << "Recalling" << localRecalledFile << "Checksum:" << record._checksumHeader; + + QString targetPath = makeRecallFileName(recalledFile); + + qCDebug(lcBulkPropagatorDownloadJob) << "Copy recall file: " << recalledFile << " -> " << targetPath; + // Remove the target first, QFile::copy will not overwrite it. + FileSystem::remove(targetPath); + QFile::copy(recalledFile, targetPath); + } +} +} + void BulkPropagatorDownloadJob::addDownloadItem(const SyncFileItemPtr &item) { Q_ASSERT(item->isDirectory() || item->_type == ItemTypeVirtualFileDehydration || item->_type == ItemTypeVirtualFile); @@ -83,6 +146,13 @@ void BulkPropagatorDownloadJob::startAfterIsEncryptedIsChecked(const SyncFileIte Q_ASSERT(vfs && vfs->mode() == Vfs::WindowsCfApi); Q_ASSERT(item->_type == ItemTypeVirtualFileDehydration || item->_type == ItemTypeVirtualFile); + if (propagator()->localFileNameClash(item->_file)) { + if (!updateMetadata(item, true)) { + return; + } + return; + } + // For virtual files just dehydrate or create the placeholder and be done if (item->_type == ItemTypeVirtualFileDehydration) { const auto fsPath = propagator()->fullLocalPath(item->_file); @@ -106,10 +176,6 @@ void BulkPropagatorDownloadJob::startAfterIsEncryptedIsChecked(const SyncFileIte } } else if (item->_type == ItemTypeVirtualFile) { qCDebug(lcBulkPropagatorDownloadJob) << "creating virtual file" << item->_file; - if (propagator()->localFileNameClash(item->_file)) { - abortWithError(item, SyncFileItem::FileNameClash, tr("File %1 can not be downloaded because of a local file name clash!").arg(QDir::toNativeSeparators(item->_file))); - return; - } const auto r = vfs->createPlaceholder(*item); if (!r) { qCCritical(lcBulkPropagatorDownloadJob) << "Could not create a placholder for a file" << QDir::toNativeSeparators(item->_file) << ":" << r.error(); @@ -123,7 +189,7 @@ void BulkPropagatorDownloadJob::startAfterIsEncryptedIsChecked(const SyncFileIte return; } - if (!updateMetadata(item)) { + if (!updateMetadata(item, false)) { return; } @@ -191,7 +257,7 @@ void BulkPropagatorDownloadJob::start(const SyncFileItemPtr &item) } } -bool BulkPropagatorDownloadJob::updateMetadata(const SyncFileItemPtr &item) +bool BulkPropagatorDownloadJob::updateMetadata(const SyncFileItemPtr &item, bool isConflict) { const auto fn = propagator()->fullLocalPath(item->_file); const auto result = propagator()->updateMetadata(*item); @@ -203,6 +269,18 @@ bool BulkPropagatorDownloadJob::updateMetadata(const SyncFileItemPtr &item) return false; } + propagator()->_journal->commit("download file start2"); + + if (isConflict) { + continueWithError(item, SyncFileItem::Conflict, {}); + } + + // handle the special recall file + if (!item->_remotePerm.hasPermission(RemotePermissions::IsShared) + && (item->_file == QLatin1String(".sys.admin#recall#") || item->_file.endsWith(QLatin1String("/.sys.admin#recall#")))) { + handleRecallFile(fn, propagator()->localPath(), *propagator()->_journal); + } + const auto isLockOwnedByCurrentUser = item->_lockOwnerId == propagator()->account()->davUser(); const auto isUserLockOwnedByCurrentUser = (item->_lockOwnerType == SyncFileItem::LockOwnerType::UserLock && isLockOwnedByCurrentUser); @@ -231,9 +309,18 @@ void BulkPropagatorDownloadJob::abortWithError(SyncFileItemPtr item, SyncFileIte abort(AbortType::Synchronous); if (item) { item->_errorString = error; + item->_status = status; emit propagator()->itemCompleted(item, ErrorCategory::GenericError); } done(status); } +void BulkPropagatorDownloadJob::continueWithError(SyncFileItemPtr item, SyncFileItem::Status status, const QString &error) +{ + qCInfo(lcBulkPropagatorDownloadJob) << "item finished with status" << status << error; + item->_errorString = error; + item->_status = status; + emit propagator()->itemCompleted(item, ErrorCategory::NoError); +} + } diff --git a/src/libsync/bulkpropagatordownloadjob.h b/src/libsync/bulkpropagatordownloadjob.h index e0796cb5bd866..b14837cdaf344 100644 --- a/src/libsync/bulkpropagatordownloadjob.h +++ b/src/libsync/bulkpropagatordownloadjob.h @@ -48,12 +48,11 @@ private slots: void done( const SyncFileItem::Status status); - void abortWithError(SyncFileItemPtr item, - SyncFileItem::Status status, - const QString &error); + void abortWithError(SyncFileItemPtr item, SyncFileItem::Status status, const QString &error); + void continueWithError(SyncFileItemPtr item, SyncFileItem::Status status, const QString &error); private: - bool updateMetadata(const SyncFileItemPtr &item); + bool updateMetadata(const SyncFileItemPtr &item, bool isConflict); void checkPropagationIsDone(); std::vector _filesToDownload; diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 8031c663029ef..a9f0ba8ef385b 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -689,17 +689,7 @@ void OwncloudPropagator::startFilePropagation(const SyncFileItemPtr &item, } removedDirectory = item->_file + "/"; } else { - const auto isVfsCfApi = syncOptions()._vfs && syncOptions()._vfs->mode() == Vfs::WindowsCfApi; - const auto isDownload = item->_direction == SyncFileItem::Down && - (item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC); - const auto isVirtualFile = item->_type == ItemTypeVirtualFile || item->_type == ItemTypeVirtualFileDehydration; - const auto shouldAddBulkPropagateDownloadItem = isDownload && isVirtualFile && isVfsCfApi && !directories.isEmpty(); - - if (shouldAddBulkPropagateDownloadItem) { - addBulkPropagateDownloadItem(item, directories); - } else { - directories.top().second->appendTask(item); - } + directories.top().second->appendTask(item); } if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) {