2014-11-11 18:09:01 +03:00
/*
* 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 "propagateremotemove.h"
2016-10-10 17:55:31 +03:00
# include "propagatorjobs.h"
2014-11-11 18:09:01 +03:00
# include "owncloudpropagator_p.h"
# include "account.h"
2017-09-01 19:11:43 +03:00
# include "common/syncjournalfilerecord.h"
2015-03-11 12:51:36 +03:00
# include "filesystem.h"
2017-09-01 19:11:43 +03:00
# include "common/asserts.h"
2014-11-11 18:09:01 +03:00
# include <QFile>
2015-02-12 22:10:31 +03:00
# include <QStringList>
2016-11-15 20:47:04 +03:00
# include <QDir>
2014-11-11 18:09:01 +03:00
2014-12-02 16:20:13 +03:00
namespace OCC {
2014-11-11 18:09:01 +03:00
2017-12-28 22:33:10 +03:00
Q_LOGGING_CATEGORY ( lcMoveJob , " nextcloud.sync.networkjob.move " , QtInfoMsg )
Q_LOGGING_CATEGORY ( lcPropagateRemoteMove , " nextcloud.sync.propagator.remotemove " , QtInfoMsg )
2017-05-09 15:24:11 +03:00
2014-12-18 14:09:48 +03:00
MoveJob : : MoveJob ( AccountPtr account , const QString & path ,
2014-11-11 18:09:01 +03:00
const QString & destination , QObject * parent )
: AbstractNetworkJob ( account , path , parent )
, _destination ( destination )
{
}
2017-05-17 11:55:42 +03:00
2016-08-02 14:48:56 +03:00
MoveJob : : MoveJob ( AccountPtr account , const QUrl & url , const QString & destination ,
QMap < QByteArray , QByteArray > extraHeaders , QObject * parent )
: AbstractNetworkJob ( account , QString ( ) , parent )
, _destination ( destination )
, _url ( url )
, _extraHeaders ( extraHeaders )
{
}
2014-11-11 18:09:01 +03:00
void MoveJob : : start ( )
{
QNetworkRequest req ;
2014-11-11 18:27:06 +03:00
req . setRawHeader ( " Destination " , QUrl : : toPercentEncoding ( _destination , " / " ) ) ;
2016-08-02 14:48:56 +03:00
for ( auto it = _extraHeaders . constBegin ( ) ; it ! = _extraHeaders . constEnd ( ) ; + + it ) {
req . setRawHeader ( it . key ( ) , it . value ( ) ) ;
}
2017-03-03 13:20:53 +03:00
if ( _url . isValid ( ) ) {
sendRequest ( " MOVE " , _url , req ) ;
} else {
sendRequest ( " MOVE " , makeDavUrl ( path ( ) ) , req ) ;
}
2014-11-11 18:09:01 +03:00
if ( reply ( ) - > error ( ) ! = QNetworkReply : : NoError ) {
2017-05-09 15:24:11 +03:00
qCWarning ( lcPropagateRemoteMove ) < < " Network error: " < < reply ( ) - > errorString ( ) ;
2014-11-11 18:09:01 +03:00
}
AbstractNetworkJob : : start ( ) ;
}
bool MoveJob : : finished ( )
{
2017-03-30 14:46:20 +03:00
qCInfo ( lcMoveJob ) < < " MOVE of " < < reply ( ) - > request ( ) . url ( ) < < " FINISHED WITH STATUS "
2018-04-17 21:21:49 +03:00
< < replyStatusString ( ) ;
2017-03-30 14:46:20 +03:00
2014-11-11 18:09:01 +03:00
emit finishedSignal ( ) ;
return true ;
}
void PropagateRemoteMove : : start ( )
{
2020-02-05 14:57:09 +03:00
if ( propagator ( ) - > _abortRequested )
2014-11-11 18:09:01 +03:00
return ;
2018-12-23 12:19:20 +03:00
QString origin = propagator ( ) - > adjustRenamedPath ( _item - > _file ) ;
qCDebug ( lcPropagateRemoteMove ) < < origin < < _item - > _renameTarget ;
2014-11-11 18:09:01 +03:00
2020-09-22 12:47:40 +03:00
QString targetFile ( propagator ( ) - > fullLocalPath ( _item - > _renameTarget ) ) ;
2014-11-07 13:41:21 +03:00
2018-12-23 12:19:20 +03:00
if ( origin = = _item - > _renameTarget ) {
2015-10-05 06:20:09 +03:00
// The parent has been renamed already so there is nothing more to do.
2021-01-21 18:07:30 +03:00
if ( ! _item - > _encryptedFileName . isEmpty ( ) ) {
// when renaming non-encrypted folder that contains encrypted folder, nested files of its encrypted folder are incorrectly displayed in the Settings dialog
// encrypted name is displayed instead of a local folder name, unless the sync folder is removed, then added again and re-synced
// we are fixing it by modifying the "_encryptedFileName" in such a way so it will have a renamed root path at the beginning of it as expected
// corrected "_encryptedFileName" is later used in propagator()->updateMetadata() call that will update the record in the Sync journal DB
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 ) ;
return ;
}
const auto remoteParentPath = parentRec . _e2eMangledName . isEmpty ( ) ? parentPath : parentRec . _e2eMangledName ;
const auto lastSlashPosition = _item - > _encryptedFileName . lastIndexOf ( ' / ' ) ;
const auto encryptedName = lastSlashPosition > = 0 ? _item - > _encryptedFileName . mid ( lastSlashPosition + 1 ) : QString ( ) ;
if ( ! encryptedName . isEmpty ( ) ) {
_item - > _encryptedFileName = remoteParentPath + " / " + encryptedName ;
}
}
2014-11-11 18:09:01 +03:00
finalize ( ) ;
return ;
2015-02-12 14:59:00 +03:00
}
2020-09-22 12:47:40 +03:00
QString remoteSource = propagator ( ) - > fullRemotePath ( origin ) ;
QString remoteDestination = QDir : : cleanPath ( propagator ( ) - > account ( ) - > davUrl ( ) . path ( ) + propagator ( ) - > fullRemotePath ( _item - > _renameTarget ) ) ;
2019-03-07 16:35:39 +03:00
2018-11-21 14:23:08 +03:00
auto & vfs = propagator ( ) - > syncOptions ( ) . _vfs ;
2019-03-07 16:35:39 +03:00
auto itype = _item - > _type ;
ASSERT ( itype ! = ItemTypeVirtualFileDownload & & itype ! = ItemTypeVirtualFileDehydration ) ;
if ( vfs - > mode ( ) = = Vfs : : WithSuffix & & itype ! = ItemTypeDirectory ) {
2018-08-15 11:46:16 +03:00
const auto suffix = vfs - > fileSuffix ( ) ;
2019-03-07 16:35:39 +03:00
bool sourceHadSuffix = remoteSource . endsWith ( suffix ) ;
bool destinationHadSuffix = remoteDestination . endsWith ( suffix ) ;
// Remote source and destination definitely shouldn't have the suffix
if ( sourceHadSuffix )
remoteSource . chop ( suffix . size ( ) ) ;
if ( destinationHadSuffix )
remoteDestination . chop ( suffix . size ( ) ) ;
QString folderTarget = _item - > _renameTarget ;
// Users can rename the file *and at the same time* add or remove the vfs
// suffix. That's a complicated case where a remote rename plus a local hydration
// change is requested. We don't currently deal with that. Instead, the rename
// is propagated and the local vfs suffix change is reverted.
// The discovery would still set up _renameTarget without the changed
// suffix, since that's what must be propagated to the remote but the local
// file may have a different name. folderTargetAlt will contain this potential
// name.
QString folderTargetAlt = folderTarget ;
if ( itype = = ItemTypeFile ) {
ASSERT ( ! sourceHadSuffix & & ! destinationHadSuffix ) ;
// If foo -> bar.owncloud, the rename target will be "bar"
folderTargetAlt = folderTarget + suffix ;
} else if ( itype = = ItemTypeVirtualFile ) {
ASSERT ( sourceHadSuffix & & destinationHadSuffix ) ;
// If foo.owncloud -> bar, the rename target will be "bar.owncloud"
folderTargetAlt . chop ( suffix . size ( ) ) ;
}
2020-09-22 12:47:40 +03:00
QString localTarget = propagator ( ) - > fullLocalPath ( folderTarget ) ;
QString localTargetAlt = propagator ( ) - > fullLocalPath ( folderTargetAlt ) ;
2019-03-07 16:35:39 +03:00
// If the expected target doesn't exist but a file with different hydration
// state does, rename the local file to bring it in line with what the discovery
// has set up.
if ( ! FileSystem : : fileExists ( localTarget ) & & FileSystem : : fileExists ( localTargetAlt ) ) {
QString error ;
if ( ! FileSystem : : uncheckedRenameReplace ( localTargetAlt , localTarget , & error ) ) {
done ( SyncFileItem : : NormalError , tr ( " Could not rename %1 to %2, error: %3 " )
. arg ( folderTargetAlt , folderTarget , error ) ) ;
return ;
}
qCInfo ( lcPropagateRemoteMove ) < < " Suffix vfs required local rename of "
< < folderTargetAlt < < " to " < < folderTarget ;
2018-09-26 14:41:02 +03:00
}
}
2019-03-07 16:35:39 +03:00
qCDebug ( lcPropagateRemoteMove ) < < remoteSource < < remoteDestination ;
2018-09-26 14:41:02 +03:00
2019-03-07 16:35:39 +03:00
_job = new MoveJob ( propagator ( ) - > account ( ) , remoteSource , remoteDestination , this ) ;
2017-09-20 11:14:48 +03:00
connect ( _job . data ( ) , & MoveJob : : finishedSignal , this , & PropagateRemoteMove : : slotMoveJobFinished ) ;
2017-01-17 16:29:12 +03:00
propagator ( ) - > _activeJobList . append ( this ) ;
2015-02-12 14:59:00 +03:00
_job - > start ( ) ;
2014-11-11 18:09:01 +03:00
}
2017-08-11 01:03:03 +03:00
void PropagateRemoteMove : : abort ( PropagatorJob : : AbortType abortType )
2014-11-11 18:09:01 +03:00
{
if ( _job & & _job - > reply ( ) )
_job - > reply ( ) - > abort ( ) ;
2017-08-11 01:03:03 +03:00
if ( abortType = = AbortType : : Asynchronous ) {
emit abortFinished ( ) ;
}
2014-11-11 18:09:01 +03:00
}
void PropagateRemoteMove : : slotMoveJobFinished ( )
{
2017-01-17 16:29:12 +03:00
propagator ( ) - > _activeJobList . removeOne ( this ) ;
2014-11-11 18:09:01 +03:00
2017-02-07 15:52:15 +03:00
ASSERT ( _job ) ;
2014-11-11 18:09:01 +03:00
QNetworkReply : : NetworkError err = _job - > reply ( ) - > error ( ) ;
2015-04-15 16:19:11 +03:00
_item - > _httpErrorCode = _job - > reply ( ) - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ;
2018-04-04 17:27:08 +03:00
_item - > _responseTimeStamp = _job - > responseTimestamp ( ) ;
_item - > _requestId = _job - > requestId ( ) ;
2014-11-11 18:09:01 +03:00
if ( err ! = QNetworkReply : : NoError ) {
2015-09-30 16:34:50 +03:00
SyncFileItem : : Status status = classifyError ( err , _item - > _httpErrorCode ,
2017-01-17 16:29:12 +03:00
& propagator ( ) - > _anotherSyncNeeded ) ;
2014-11-11 18:09:01 +03:00
done ( status , _job - > errorString ( ) ) ;
return ;
}
2015-04-15 16:19:11 +03:00
if ( _item - > _httpErrorCode ! = 201 ) {
2015-10-05 06:20:09 +03:00
// Normally we expect "201 Created"
2014-11-11 18:09:01 +03:00
// If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must
// throw an error.
2017-05-17 11:54:57 +03:00
done ( SyncFileItem : : NormalError ,
tr ( " Wrong HTTP code returned by server. Expected 201, but received \" %1 %2 \" . " )
. arg ( _item - > _httpErrorCode )
. arg ( _job - > reply ( ) - > attribute ( QNetworkRequest : : HttpReasonPhraseAttribute ) . toString ( ) ) ) ;
2014-11-11 18:09:01 +03:00
return ;
}
finalize ( ) ;
}
void PropagateRemoteMove : : finalize ( )
{
2019-06-21 10:39:33 +03:00
// Retrieve old db data.
2016-04-11 13:41:26 +03:00
// if reading from db failed still continue hoping that deleteFileRecord
// reopens the db successfully.
// The db is only queried to transfer the content checksum from the old
// to the new record. It is not a problem to skip it here.
2019-06-21 10:39:33 +03:00
SyncJournalFileRecord oldRecord ;
propagator ( ) - > _journal - > getFileRecord ( _item - > _originalFile , & oldRecord ) ;
auto & vfs = propagator ( ) - > syncOptions ( ) . _vfs ;
auto pinState = vfs - > pinState ( _item - > _originalFile ) ;
// Delete old db data.
2017-01-17 16:29:12 +03:00
propagator ( ) - > _journal - > deleteFileRecord ( _item - > _originalFile ) ;
2019-06-21 10:39:33 +03:00
vfs - > setPinState ( _item - > _originalFile , PinState : : Inherited ) ;
2015-11-10 17:05:00 +03:00
2019-01-21 13:19:45 +03:00
SyncFileItem newItem ( * _item ) ;
2019-03-07 16:35:39 +03:00
newItem . _type = _item - > _type ;
2016-04-11 13:41:26 +03:00
if ( oldRecord . isValid ( ) ) {
2019-01-21 13:19:45 +03:00
newItem . _checksumHeader = oldRecord . _checksumHeader ;
if ( newItem . _size ! = oldRecord . _fileSize ) {
qCWarning ( lcPropagateRemoteMove ) < < " File sizes differ on server vs sync journal: " < < newItem . _size < < oldRecord . _fileSize ;
2017-04-28 11:03:49 +03:00
// the server might have claimed a different size, we take the old one from the DB
2019-01-21 13:19:45 +03:00
newItem . _size = oldRecord . _fileSize ;
2016-04-11 13:41:26 +03:00
}
2016-03-15 12:28:47 +03:00
}
2021-06-28 13:32:57 +03:00
const auto result = propagator ( ) - > updateMetadata ( newItem ) ;
if ( ! result ) {
done ( SyncFileItem : : FatalError , tr ( " Error updating metadata: %1 " ) . arg ( result . error ( ) ) ) ;
return ;
} else if ( * result = = Vfs : : ConvertToPlaceholderResult : : Locked ) {
done ( SyncFileItem : : SoftError , tr ( " The file %1 is currently in use " ) . arg ( newItem . _file ) ) ;
2016-04-07 12:47:04 +03:00
return ;
}
2019-06-21 10:39:33 +03:00
if ( pinState & & * pinState ! = PinState : : Inherited
& & ! vfs - > setPinState ( newItem . _renameTarget , * pinState ) ) {
done ( SyncFileItem : : NormalError , tr ( " Error setting pin state " ) ) ;
return ;
}
2016-10-10 17:55:31 +03:00
2017-08-24 18:31:46 +03:00
if ( _item - > isDirectory ( ) ) {
2018-12-23 12:19:20 +03:00
propagator ( ) - > _renamedDirectories . insert ( _item - > _file , _item - > _renameTarget ) ;
2017-01-17 16:29:12 +03:00
if ( ! adjustSelectiveSync ( propagator ( ) - > _journal , _item - > _file , _item - > _renameTarget ) ) {
2016-10-10 17:55:31 +03:00
done ( SyncFileItem : : FatalError , tr ( " Error writing metadata to the database " ) ) ;
return ;
}
}
2017-01-17 16:29:12 +03:00
propagator ( ) - > _journal - > commit ( " Remote Rename " ) ;
2014-11-11 18:09:01 +03:00
done ( SyncFileItem : : Success ) ;
}
2016-10-10 17:55:31 +03:00
bool PropagateRemoteMove : : adjustSelectiveSync ( SyncJournalDb * journal , const QString & from_ , const QString & to_ )
{
2020-05-29 16:07:05 +03:00
bool ok = false ;
2016-10-10 17:55:31 +03:00
// We only care about preserving the blacklist. The white list should anyway be empty.
// And the undecided list will be repopulated on the next sync, if there is anything too big.
QStringList list = journal - > getSelectiveSyncList ( SyncJournalDb : : SelectiveSyncBlackList , & ok ) ;
if ( ! ok )
return false ;
bool changed = false ;
2017-02-07 15:52:15 +03:00
ASSERT ( ! from_ . endsWith ( QLatin1String ( " / " ) ) ) ;
ASSERT ( ! to_ . endsWith ( QLatin1String ( " / " ) ) ) ;
2016-10-10 17:55:31 +03:00
QString from = from_ + QLatin1String ( " / " ) ;
QString to = to_ + QLatin1String ( " / " ) ;
2020-08-13 13:23:02 +03:00
for ( auto & s : list ) {
if ( s . startsWith ( from ) ) {
s = s . replace ( 0 , from . size ( ) , to ) ;
2016-10-10 17:55:31 +03:00
changed = true ;
}
}
if ( changed ) {
journal - > setSelectiveSyncList ( SyncJournalDb : : SelectiveSyncBlackList , list ) ;
}
return true ;
}
2014-11-11 18:09:01 +03:00
}