mirror of https://github.com/nextcloud/desktop
291 lines
12 KiB
C++
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);
|
|
}
|
|
}
|