desktop/src/libsync/propagateremotemkdir.cpp

291 lines
12 KiB
C++

/*
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "propagateremotemkdir.h"
#include "owncloudpropagator_p.h"
#include "account.h"
#include "common/syncjournalfilerecord.h"
#include "propagateuploadencrypted.h"
#include "deletejob.h"
#include "common/asserts.h"
#include "encryptfolderjob.h"
#include "filesystem.h"
#include "csync/csync.h"
#include <QFile>
#include <QLoggingCategory>
namespace OCC {
Q_LOGGING_CATEGORY(lcPropagateRemoteMkdir, "nextcloud.sync.propagator.remotemkdir", QtInfoMsg)
PropagateRemoteMkdir::PropagateRemoteMkdir(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
: PropagateItemJob(propagator, item)
{
const auto path = _item->_file;
const auto slashPosition = path.lastIndexOf('/');
const auto parentPath = slashPosition >= 0 ? path.left(slashPosition) : QString();
SyncJournalFileRecord parentRec;
bool ok = propagator->_journal->getFileRecord(parentPath, &parentRec);
if (!ok) {
return;
}
}
void PropagateRemoteMkdir::start()
{
if (propagator()->_abortRequested)
return;
qCDebug(lcPropagateRemoteMkdir) << _item->_file;
propagator()->_activeJobList.append(this);
if (!_deleteExisting) {
slotMkdir();
return;
}
_job = new DeleteJob(propagator()->account(),
propagator()->fullRemotePath(_item->_file),
this);
connect(qobject_cast<DeleteJob *>(_job), &DeleteJob::finishedSignal, this, &PropagateRemoteMkdir::slotMkdir);
_job->start();
}
void PropagateRemoteMkdir::slotStartMkcolJob()
{
if (propagator()->_abortRequested)
return;
qCDebug(lcPropagateRemoteMkdir) << _item->_file;
_job = new MkColJob(propagator()->account(),
propagator()->fullRemotePath(_item->_file),
this);
connect(qobject_cast<MkColJob *>(_job), &MkColJob::finishedWithError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
connect(qobject_cast<MkColJob *>(_job), &MkColJob::finishedWithoutError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
_job->start();
}
void PropagateRemoteMkdir::slotStartEncryptedMkcolJob(const QString &path, const QString &filename, quint64 size)
{
Q_UNUSED(path)
Q_UNUSED(size)
if (propagator()->_abortRequested)
return;
qDebug() << filename;
qCDebug(lcPropagateRemoteMkdir) << filename;
auto job = new MkColJob(propagator()->account(),
propagator()->fullRemotePath(filename),
{{"e2e-token", _uploadEncryptedHelper->folderToken() }},
this);
connect(job, &MkColJob::finishedWithError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
connect(job, &MkColJob::finishedWithoutError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
_job = job;
_job->start();
}
void PropagateRemoteMkdir::abort(PropagatorJob::AbortType abortType)
{
if (_job && _job->reply())
_job->reply()->abort();
if (abortType == AbortType::Asynchronous) {
emit abortFinished();
}
}
void PropagateRemoteMkdir::setDeleteExisting(bool enabled)
{
_deleteExisting = enabled;
}
void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, const QString &jobHttpReasonPhraseString, const QString &jobPath)
{
if (_item->_httpErrorCode == 405) {
// This happens when the directory already exists. Nothing to do.
qDebug(lcPropagateRemoteMkdir) << "Folder" << jobPath << "already exists.";
} else if (err != QNetworkReply::NoError) {
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&propagator()->_anotherSyncNeeded);
done(status, _item->_errorString, errorCategoryFromNetworkError(err));
return;
} else if (_item->_httpErrorCode != 201) {
// Normally we expect "201 Created"
// If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must
// throw an error.
done(SyncFileItem::NormalError,
tr("Wrong HTTP code returned by server. Expected 201, but received \"%1 %2\".")
.arg(_item->_httpErrorCode)
.arg(jobHttpReasonPhraseString), ErrorCategory::GenericError);
return;
}
propagator()->_activeJobList.append(this);
auto propfindJob = new PropfindJob(propagator()->account(), jobPath, this);
propfindJob->setProperties({QByteArrayLiteral("http://owncloud.org/ns:share-types"), QByteArrayLiteral("http://owncloud.org/ns:permissions"), QByteArrayLiteral("http://nextcloud.org/ns:is-mount-root")});
connect(propfindJob, &PropfindJob::result, this, [this, jobPath](const QVariantMap &result){
propagator()->_activeJobList.removeOne(this);
_item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions")).toString(),
propagator()->account()->serverHasMountRootProperty() ? RemotePermissions::MountedPermissionAlgorithm::UseMountRootProperty : RemotePermissions::MountedPermissionAlgorithm::WildGuessMountedSubProperty,
result);
_item->_sharedByMe = !result.value(QStringLiteral("share-types")).toString().isEmpty();
_item->_isShared = _item->_remotePerm.hasPermission(RemotePermissions::IsShared) || _item->_sharedByMe;
_item->_lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
if (!_uploadEncryptedHelper && !_item->isEncrypted()) {
success();
} else {
// We still need to mark that folder encrypted in case we were uploading it as encrypted one
// Another scenario, is we are creating a new folder because of move operation on an encrypted folder that works via remove + re-upload
propagator()->_activeJobList.append(this);
// We're expecting directory path in /Foo/Bar convention...
Q_ASSERT(jobPath.startsWith('/') && !jobPath.endsWith('/'));
// But encryption job expect it in Foo/Bar/ convention
auto job = new OCC::EncryptFolderJob(propagator()->account(),
propagator()->_journal,
jobPath.mid(1),
_item->_file,
propagator()->remotePath(),
_item->_fileId,
propagator(),
_item);
job->setParent(this);
connect(job, &OCC::EncryptFolderJob::finished, this, &PropagateRemoteMkdir::slotEncryptFolderFinished);
job->start();
}
});
connect(propfindJob, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) {
const auto err = reply ? reply->error() : QNetworkReply::NetworkError::UnknownNetworkError;
propagator()->_activeJobList.removeOne(this);
done(SyncFileItem::NormalError, {}, errorCategoryFromNetworkError(err));
});
propfindJob->start();
}
void PropagateRemoteMkdir::slotMkdir()
{
if (!_item->_originalFile.isEmpty() && !_item->_renameTarget.isEmpty() && _item->_renameTarget != _item->_originalFile) {
const auto existingFile = propagator()->fullLocalPath(propagator()->adjustRenamedPath(_item->_originalFile));
const auto targetFile = propagator()->fullLocalPath(_item->_renameTarget);
QString renameError;
if (!FileSystem::rename(existingFile, targetFile, &renameError)) {
done(SyncFileItem::NormalError, renameError, ErrorCategory::GenericError);
return;
}
emit propagator()->touchedFile(existingFile);
emit propagator()->touchedFile(targetFile);
}
const auto path = _item->_file;
const auto slashPosition = path.lastIndexOf('/');
const auto parentPath = slashPosition >= 0 ? path.left(slashPosition) : QString();
SyncJournalFileRecord parentRec;
bool ok = propagator()->_journal->getFileRecord(parentPath, &parentRec);
if (!ok) {
done(SyncFileItem::NormalError, {}, ErrorCategory::GenericError);
return;
}
if (!hasEncryptedAncestor()) {
slotStartMkcolJob();
return;
}
// We should be encrypted as well since our parent is
const auto remoteParentPath = parentRec._e2eMangledName.isEmpty() ? parentPath : parentRec._e2eMangledName;
_uploadEncryptedHelper = new PropagateUploadEncrypted(propagator(), remoteParentPath, _item, this);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::finalized,
this, &PropagateRemoteMkdir::slotStartEncryptedMkcolJob);
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::error,
[]{ qCDebug(lcPropagateRemoteMkdir) << "Error setting up encryption."; });
_uploadEncryptedHelper->start();
}
void PropagateRemoteMkdir::slotMkcolJobFinished()
{
propagator()->_activeJobList.removeOne(this);
ASSERT(_job);
QNetworkReply::NetworkError err = _job->reply()->error();
_item->_httpErrorCode = _job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
_item->_responseTimeStamp = _job->responseTimestamp();
_item->_requestId = _job->requestId();
_item->_fileId = _job->reply()->rawHeader("OC-FileId");
qCInfo(lcPropagateRemoteMkdir()) << "mkcol job error string:" << _item->_errorString << _job->errorString();
_item->_errorString = _job->errorString();
const auto jobHttpReasonPhraseString = _job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
const auto jobPath = _job->path();
if (_uploadEncryptedHelper && _uploadEncryptedHelper->isFolderLocked() && !_uploadEncryptedHelper->isUnlockRunning()) {
// since we are done, we need to unlock a folder in case it was locked
connect(_uploadEncryptedHelper, &PropagateUploadEncrypted::folderUnlocked, this, [this, err, jobHttpReasonPhraseString, jobPath]() {
finalizeMkColJob(err, jobHttpReasonPhraseString, jobPath);
});
_uploadEncryptedHelper->unlockFolder();
} else {
finalizeMkColJob(err, jobHttpReasonPhraseString, jobPath);
}
}
void PropagateRemoteMkdir::slotEncryptFolderFinished(int status, EncryptionStatusEnums::ItemEncryptionStatus encryptionStatus)
{
if (status != EncryptFolderJob::Success) {
done(SyncFileItem::FatalError, tr("Failed to encrypt a folder %1").arg(_item->_file), ErrorCategory::GenericError);
return;
}
qCDebug(lcPropagateRemoteMkdir) << "Success making the new folder encrypted";
propagator()->_activeJobList.removeOne(this);
_item->_e2eEncryptionStatus = encryptionStatus;
_item->_e2eEncryptionStatusRemote = encryptionStatus;
if (_item->isEncrypted()) {
_item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(propagator()->account()->capabilities().clientSideEncryptionVersion());
}
success();
}
void PropagateRemoteMkdir::success()
{
// Never save the etag on first mkdir.
// Only fully propagated directories should have the etag set.
auto itemCopy = *_item;
itemCopy._etag.clear();
// save the file id already so we can detect rename or remove
const auto result = propagator()->updateMetadata(itemCopy);
if (!result) {
done(SyncFileItem::FatalError, tr("Error writing metadata to the database: %1").arg(result.error()), ErrorCategory::GenericError);
return;
} else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
done(SyncFileItem::FatalError, tr("The file %1 is currently in use").arg(_item->_file), ErrorCategory::GenericError);
return;
}
done(SyncFileItem::Success, {}, ErrorCategory::NoError);
}
}