2013-05-03 21:11:00 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
|
|
|
|
* Copyright (C) by Klaas Freitag <freitag@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.
|
|
|
|
*/
|
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
#include "owncloudpropagator_p.h"
|
2013-10-16 13:59:54 +04:00
|
|
|
#include "syncjournaldb.h"
|
2013-10-28 13:47:10 +04:00
|
|
|
#include "syncjournalfilerecord.h"
|
2013-12-10 16:47:44 +04:00
|
|
|
#include "utility.h"
|
2013-05-03 21:11:00 +04:00
|
|
|
#include <httpbf.h>
|
|
|
|
#include <qfile.h>
|
|
|
|
#include <qdir.h>
|
|
|
|
#include <qdiriterator.h>
|
|
|
|
#include <qtemporaryfile.h>
|
2013-09-05 19:08:05 +04:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
2013-05-03 21:11:00 +04:00
|
|
|
#include <qabstractfileengine.h>
|
2013-09-05 19:08:05 +04:00
|
|
|
#else
|
|
|
|
#include <qsavefile.h>
|
|
|
|
#endif
|
2013-09-05 22:15:55 +04:00
|
|
|
#include <QDebug>
|
2013-05-05 13:14:40 +04:00
|
|
|
#include <QDateTime>
|
2013-10-28 13:47:10 +04:00
|
|
|
#include <qstack.h>
|
2014-01-31 20:29:50 +04:00
|
|
|
#include <QCoreApplication>
|
2013-05-03 21:11:00 +04:00
|
|
|
|
|
|
|
#include <neon/ne_basic.h>
|
|
|
|
#include <neon/ne_socket.h>
|
|
|
|
#include <neon/ne_session.h>
|
|
|
|
#include <neon/ne_props.h>
|
|
|
|
#include <neon/ne_auth.h>
|
|
|
|
#include <neon/ne_dates.h>
|
|
|
|
#include <neon/ne_compress.h>
|
|
|
|
#include <neon/ne_redirect.h>
|
|
|
|
|
2013-12-03 20:07:21 +04:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
#include <windef.h>
|
|
|
|
#include <winbase.h>
|
|
|
|
#endif
|
|
|
|
|
2013-12-10 20:19:25 +04:00
|
|
|
#include <time.h>
|
|
|
|
|
2013-05-08 16:31:52 +04:00
|
|
|
// We use some internals of csync:
|
|
|
|
extern "C" int c_utimes(const char *, const struct timeval *);
|
|
|
|
extern "C" void csync_win32_set_file_hidden( const char *file, bool h );
|
2013-05-06 14:09:08 +04:00
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
namespace Mirall {
|
|
|
|
|
2013-11-20 16:44:01 +04:00
|
|
|
void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorString)
|
|
|
|
{
|
|
|
|
_item._errorString = errorString;
|
|
|
|
_item._status = status;
|
|
|
|
|
|
|
|
// Blacklisting
|
|
|
|
int retries = 0;
|
|
|
|
|
2014-02-04 18:01:10 +04:00
|
|
|
if( _item._httpErrorCode == 403 ||_item._httpErrorCode == 413 || _item._httpErrorCode == 415 ) {
|
2013-12-12 14:38:41 +04:00
|
|
|
qDebug() << "Fatal Error condition" << _item._httpErrorCode << ", forbid retry!";
|
2013-11-20 16:44:01 +04:00
|
|
|
retries = -1;
|
|
|
|
} else {
|
|
|
|
retries = 3; // FIXME: good number of allowed retries?
|
|
|
|
}
|
|
|
|
SyncJournalBlacklistRecord record(_item, retries);;
|
|
|
|
|
|
|
|
switch( status ) {
|
2013-11-26 14:31:05 +04:00
|
|
|
case SyncFileItem::SoftError:
|
|
|
|
// do not blacklist in case of soft error.
|
2013-11-26 15:22:28 +04:00
|
|
|
emit progress( Progress::SoftError, _item, 0, 0 );
|
2013-11-26 14:31:05 +04:00
|
|
|
break;
|
2013-11-20 16:44:01 +04:00
|
|
|
case SyncFileItem::FatalError:
|
|
|
|
case SyncFileItem::NormalError:
|
|
|
|
_propagator->_journal->updateBlacklistEntry( record );
|
2013-11-26 15:22:28 +04:00
|
|
|
if( status == SyncFileItem::NormalError ) {
|
|
|
|
emit progress( Progress::NormalError, _item, 0, 0 );
|
|
|
|
}
|
2013-11-20 16:44:01 +04:00
|
|
|
break;
|
|
|
|
case SyncFileItem::Success:
|
|
|
|
if( _item._blacklistedInDb ) {
|
|
|
|
// wipe blacklist entry.
|
|
|
|
_propagator->_journal->wipeBlacklistEntry(_item._file);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SyncFileItem::Conflict:
|
|
|
|
case SyncFileItem::FileIgnored:
|
|
|
|
case SyncFileItem::NoStatus:
|
|
|
|
// nothing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit completed(_item);
|
|
|
|
emit finished(status);
|
|
|
|
}
|
|
|
|
|
2014-02-04 18:01:10 +04:00
|
|
|
|
2014-02-12 16:44:55 +04:00
|
|
|
/**
|
|
|
|
* For delete or remove, check that we are not removing from a shared directory.
|
|
|
|
* If we are, try to restore the file
|
|
|
|
*
|
|
|
|
* Return true if the problem is handled.
|
|
|
|
*/
|
2014-02-04 18:01:10 +04:00
|
|
|
bool PropagateItemJob::checkForProblemsWithShared()
|
|
|
|
{
|
|
|
|
QString errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
|
|
|
|
int httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
|
|
|
|
|
|
|
|
if( httpStatusCode == 403 && _propagator->isInSharedDirectory(_item._file )) {
|
2014-02-12 16:44:55 +04:00
|
|
|
if( _item._type != SyncFileItem::Directory ) {
|
|
|
|
// the file was removed locally from a read only Shared sync
|
|
|
|
// the file is gone locally and it should be recovered.
|
|
|
|
SyncFileItem downloadItem(_item);
|
|
|
|
downloadItem._instruction = CSYNC_INSTRUCTION_SYNC;
|
|
|
|
downloadItem._dir = SyncFileItem::Down;
|
|
|
|
_restoreJob.reset(new PropagateDownloadFile(_propagator, downloadItem));
|
|
|
|
} else {
|
|
|
|
// Directories are harder to recover.
|
|
|
|
// But just re-create the directory, next sync will be able to recover the files
|
|
|
|
SyncFileItem mkdirItem(_item);
|
|
|
|
mkdirItem._instruction = CSYNC_INSTRUCTION_SYNC;
|
|
|
|
mkdirItem._dir = SyncFileItem::Down;
|
|
|
|
_restoreJob.reset(new PropagateLocalMkdir(_propagator, mkdirItem));
|
|
|
|
// Also remove the inodes and fileid from the db so no further renames are tried for
|
|
|
|
// this item.
|
|
|
|
_propagator->_journal->avoidRenamesOnNextSync(_item._file);
|
|
|
|
}
|
2014-02-04 18:01:10 +04:00
|
|
|
connect(_restoreJob.data(), SIGNAL(completed(SyncFileItem)),
|
|
|
|
this, SLOT(slotRestoreJobCompleted(SyncFileItem)));
|
|
|
|
_restoreJob->start();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateItemJob::slotRestoreJobCompleted(const SyncFileItem& item )
|
|
|
|
{
|
|
|
|
if( item._status == SyncFileItem::Success ) {
|
|
|
|
done( SyncFileItem::SoftError, tr("The file was removed from a read only share. The file has been restored."));
|
|
|
|
} else {
|
|
|
|
done( item._status, tr("A file was removed from a read only share, but restoring failed: %1").arg(item._errorString) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-05 13:14:40 +04:00
|
|
|
// compare two files with given filename and return true if they have the same content
|
|
|
|
static bool fileEquals(const QString &fn1, const QString &fn2) {
|
|
|
|
QFile f1(fn1);
|
|
|
|
QFile f2(fn2);
|
|
|
|
if (!f1.open(QIODevice::ReadOnly) || !f2.open(QIODevice::ReadOnly)) {
|
|
|
|
qDebug() << "fileEquals: Failed to open " << fn1 << "or" << fn2;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f1.size() != f2.size()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int BufferSize = 16 * 1024;
|
|
|
|
char buffer1[BufferSize];
|
|
|
|
char buffer2[BufferSize];
|
|
|
|
do {
|
|
|
|
int r = f1.read(buffer1, BufferSize);
|
|
|
|
if (f2.read(buffer2, BufferSize) != r) {
|
|
|
|
// this should normaly not happen: the file are supposed to have the same size.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (r <= 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (memcmp(buffer1, buffer2, r) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} while (true);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
// Code copied from Qt5's QDir::removeRecursively
|
|
|
|
static bool removeRecursively(const QString &path)
|
|
|
|
{
|
|
|
|
bool success = true;
|
|
|
|
QDirIterator di(path, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
|
|
|
|
while (di.hasNext()) {
|
|
|
|
di.next();
|
|
|
|
const QFileInfo& fi = di.fileInfo();
|
|
|
|
bool ok;
|
|
|
|
if (fi.isDir() && !fi.isSymLink())
|
|
|
|
ok = removeRecursively(di.filePath()); // recursive
|
|
|
|
else
|
|
|
|
ok = QFile::remove(di.filePath());
|
|
|
|
if (!ok)
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
if (success)
|
|
|
|
success = QDir().rmdir(path);
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateLocalRemove::start()
|
2013-05-03 21:11:00 +04:00
|
|
|
{
|
2013-10-28 13:47:10 +04:00
|
|
|
QString filename = _propagator->_localDir + _item._file;
|
|
|
|
if (_item._isDirectory) {
|
|
|
|
if (QDir(filename).exists() && !removeRecursively(filename)) {
|
|
|
|
done(SyncFileItem::NormalError, tr("Could not remove directory %1").arg(filename));
|
|
|
|
return;
|
2013-10-04 17:13:36 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
} else {
|
|
|
|
QFile file(filename);
|
2013-10-28 13:47:10 +04:00
|
|
|
if (file.exists() && !file.remove()) {
|
|
|
|
done(SyncFileItem::NormalError, file.errorString());
|
2013-11-08 15:56:54 +04:00
|
|
|
return;
|
2013-10-04 17:13:36 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-11-26 14:31:40 +04:00
|
|
|
emit progress(Progress::StartDelete, _item, 0, _item._size);
|
2013-12-06 19:38:03 +04:00
|
|
|
_propagator->_journal->deleteFileRecord(_item._originalFile, _item._isDirectory);
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("Local remove");
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::Success);
|
2013-11-27 19:48:10 +04:00
|
|
|
emit progress(Progress::EndDelete, _item, _item._size, _item._size);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateLocalMkdir::start()
|
2013-05-03 21:11:00 +04:00
|
|
|
{
|
|
|
|
QDir d;
|
2013-10-28 13:47:10 +04:00
|
|
|
if (!d.mkpath(_propagator->_localDir + _item._file)) {
|
|
|
|
done(SyncFileItem::NormalError, tr("could not create directory %1").arg(_propagator->_localDir + _item._file));
|
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::Success);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateRemoteRemove::start()
|
2013-05-03 21:11:00 +04:00
|
|
|
{
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri(
|
|
|
|
ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
|
2013-11-26 14:31:40 +04:00
|
|
|
emit progress(Progress::StartDelete, _item, 0, _item._size);
|
2013-10-30 20:33:06 +04:00
|
|
|
qDebug() << "** DELETE " << uri.data();
|
2013-10-28 13:47:10 +04:00
|
|
|
int rc = ne_delete(_propagator->_session, uri.data());
|
2014-02-04 18:01:10 +04:00
|
|
|
|
|
|
|
if( checkForProblemsWithShared() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-30 20:33:06 +04:00
|
|
|
/* Ignore the error 404, it means it is already deleted */
|
|
|
|
if (updateErrorFromSession(rc, 0, 404)) {
|
2013-10-28 13:47:10 +04:00
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2014-02-04 18:01:10 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->deleteFileRecord(_item._originalFile, _item._isDirectory);
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("Remote Remove");
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::Success);
|
2013-11-27 19:48:10 +04:00
|
|
|
emit progress(Progress::EndDelete, _item, _item._size, _item._size);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateRemoteMkdir::start()
|
2013-05-03 21:11:00 +04:00
|
|
|
{
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri(
|
|
|
|
ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
|
2013-05-04 19:14:25 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
int rc = ne_mkcol(_propagator->_session, uri.data());
|
2013-05-04 19:14:25 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
/* Special for mkcol: it returns 405 if the directory already exists.
|
|
|
|
* Ignore that error */
|
|
|
|
if( updateErrorFromSession( rc , 0, 405 ) ) {
|
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::Success);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-11-26 15:10:52 +04:00
|
|
|
static QByteArray parseEtag(const char *header) {
|
|
|
|
if (!header)
|
|
|
|
return QByteArray();
|
|
|
|
QByteArray arr = header;
|
|
|
|
arr.replace("-gzip", ""); // https://github.com/owncloud/mirall/issues/1195
|
|
|
|
if(arr.length() >= 2 && arr.startsWith('"') && arr.endsWith('"')) {
|
|
|
|
arr = arr.mid(1, arr.length() - 2);
|
|
|
|
}
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateUploadFile::start()
|
2013-05-03 21:11:00 +04:00
|
|
|
{
|
2013-10-28 13:47:10 +04:00
|
|
|
|
|
|
|
QFile file(_propagator->_localDir + _item._file);
|
2013-05-03 21:11:00 +04:00
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::NormalError, file.errorString());
|
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri(
|
|
|
|
ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
int attempts = 0;
|
2013-09-25 16:24:31 +04:00
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
/*
|
|
|
|
* do ten tries to upload the file chunked. Check the file size and mtime
|
|
|
|
* before submitting a chunk and after having submitted the last one.
|
|
|
|
* If the file has changed, retry.
|
2013-10-28 13:47:10 +04:00
|
|
|
*/
|
2013-10-28 18:28:34 +04:00
|
|
|
qDebug() << "** PUT request to" << uri.data();
|
2013-11-22 22:45:26 +04:00
|
|
|
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item._file);
|
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
do {
|
|
|
|
Hbf_State state = HBF_SUCCESS;
|
|
|
|
QScopedPointer<hbf_transfer_t, ScopedPointerHelpers> trans(hbf_init_transfer(uri.data()));
|
2013-10-16 18:47:24 +04:00
|
|
|
trans->user_data = this;
|
2013-10-02 21:41:17 +04:00
|
|
|
hbf_set_log_callback(trans.data(), _log_callback);
|
|
|
|
hbf_set_abort_callback(trans.data(), _user_want_abort);
|
2013-10-16 18:47:24 +04:00
|
|
|
trans.data()->chunk_finished_cb = chunk_finished_cb;
|
2013-05-03 21:11:00 +04:00
|
|
|
Q_ASSERT(trans);
|
2014-01-07 18:42:21 +04:00
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
state = hbf_splitlist(trans.data(), file.handle());
|
|
|
|
|
2014-01-07 18:42:21 +04:00
|
|
|
// If the source file has changed during upload, it is detected and the
|
|
|
|
// variable _previousFileSize is set accordingly. The propagator waits a
|
|
|
|
// couple of seconds and retries.
|
|
|
|
if(_previousFileSize > 0) {
|
|
|
|
qDebug() << "File size changed underway: " << trans->stat_size - _previousFileSize;
|
|
|
|
// Report the change of the overall transmission size to the propagator
|
|
|
|
_propagator->overallTransmissionSizeChanged(qint64(trans->stat_size - _previousFileSize));
|
|
|
|
// update the item's values to the current from trans. hbf_splitlist does a stat
|
|
|
|
_item._size = trans->stat_size;
|
|
|
|
_item._modtime = trans->modtime;
|
|
|
|
|
|
|
|
}
|
|
|
|
emit progress(Progress::StartUpload, _item, 0, trans->stat_size);
|
|
|
|
|
2013-10-16 13:59:54 +04:00
|
|
|
if (progressInfo._valid) {
|
2014-01-29 14:39:14 +04:00
|
|
|
if (Utility::qDateTimeToTime_t(progressInfo._modtime) == _item._modtime) {
|
2013-10-16 13:59:54 +04:00
|
|
|
trans->start_id = progressInfo._chunk;
|
|
|
|
trans->transfer_id = progressInfo._transferid;
|
2013-05-06 20:41:56 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
ne_set_notifier(_propagator->_session, notify_status_cb, this);
|
2013-08-14 21:59:16 +04:00
|
|
|
_lastTime.restart();
|
|
|
|
_lastProgress = 0;
|
|
|
|
_chunked_done = 0;
|
2013-10-28 13:47:10 +04:00
|
|
|
_chunked_total_size = _item._size;
|
2013-10-02 17:37:30 +04:00
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
if( state == HBF_SUCCESS ) {
|
2013-10-02 17:37:30 +04:00
|
|
|
QByteArray previousEtag;
|
2013-10-28 13:47:10 +04:00
|
|
|
if (!_item._etag.isEmpty() && _item._etag != "empty_etag") {
|
2013-10-02 17:37:30 +04:00
|
|
|
// We add quotes because the owncloud server always add quotes around the etag, and
|
|
|
|
// csync_owncloud.c's owncloud_file_id always strip the quotes.
|
2013-10-28 13:47:10 +04:00
|
|
|
previousEtag = '"' + _item._etag + '"';
|
2013-10-02 17:37:30 +04:00
|
|
|
trans->previous_etag = previousEtag.data();
|
|
|
|
}
|
2013-08-14 21:59:16 +04:00
|
|
|
_chunked_total_size = trans->stat_size;
|
2013-10-28 19:26:25 +04:00
|
|
|
qDebug() << "About to upload " << _item._file << " (" << previousEtag << _item._size << " bytes )";
|
2013-05-03 21:11:00 +04:00
|
|
|
/* Transfer all the chunks through the HTTP session using PUT. */
|
2013-10-28 13:47:10 +04:00
|
|
|
state = hbf_transfer( _propagator->_session, trans.data(), "PUT" );
|
2013-09-25 16:24:31 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 18:28:34 +04:00
|
|
|
// the file id should only be empty for new files up- or downloaded
|
|
|
|
QString fid = QString::fromUtf8( hbf_transfer_file_id( trans.data() ));
|
2013-11-15 13:14:06 +04:00
|
|
|
if( !fid.isEmpty() ) {
|
|
|
|
if( !_item._fileId.isEmpty() && _item._fileId != fid ) {
|
2013-11-14 16:47:43 +04:00
|
|
|
qDebug() << "WARN: File ID changed!" << _item._fileId << fid;
|
|
|
|
}
|
2013-11-15 13:14:06 +04:00
|
|
|
_item._fileId = fid;
|
2013-10-28 18:28:34 +04:00
|
|
|
}
|
2013-10-25 15:28:48 +04:00
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
/* Handle errors. */
|
|
|
|
if ( state != HBF_SUCCESS ) {
|
|
|
|
|
|
|
|
/* If the source file changed during submission, lets try again */
|
|
|
|
if( state == HBF_SOURCE_FILE_CHANGE ) {
|
2013-11-25 22:12:13 +04:00
|
|
|
if( attempts++ < 5 ) { /* FIXME: How often do we want to try? */
|
|
|
|
qDebug("SOURCE file has changed during upload, retry #%d in %d seconds!", attempts, 2*attempts);
|
|
|
|
sleep(2*attempts);
|
2014-01-07 18:42:21 +04:00
|
|
|
if( _previousFileSize == 0 ) {
|
|
|
|
_previousFileSize = _item._size;
|
|
|
|
} else {
|
|
|
|
_previousFileSize = trans->stat_size;
|
|
|
|
}
|
2013-11-25 19:16:33 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString errMsg = tr("Local file changed during sync, syncing once it arrived completely");
|
|
|
|
done( SyncFileItem::SoftError, errMsg );
|
2013-11-25 01:26:50 +04:00
|
|
|
} else if( state == HBF_USER_ABORTED ) {
|
|
|
|
const QString errMsg = tr("Sync was aborted by user.");
|
2013-11-26 15:22:28 +04:00
|
|
|
done( SyncFileItem::SoftError, errMsg );
|
2013-11-25 01:26:50 +04:00
|
|
|
} else {
|
2013-11-25 19:16:33 +04:00
|
|
|
// Other HBF error conditions.
|
2013-11-25 01:26:50 +04:00
|
|
|
// FIXME: find out the error class.
|
|
|
|
_item._httpErrorCode = hbf_fail_http_code(trans.data());
|
|
|
|
done(SyncFileItem::NormalError, hbf_error_string(trans.data(), state));
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
ne_set_notifier(_propagator->_session, 0, 0);
|
2013-10-28 18:28:34 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
if( trans->modtime_accepted ) {
|
2013-11-26 15:10:52 +04:00
|
|
|
_item._etag = parseEtag(hbf_transfer_etag( trans.data() ));
|
2013-10-28 13:47:10 +04:00
|
|
|
} else {
|
2014-02-06 20:02:10 +04:00
|
|
|
if (!updateMTimeAndETag(uri.data(), _item._modtime))
|
|
|
|
return;
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
2013-10-16 13:59:54 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, _propagator->_localDir + _item._file));
|
|
|
|
// Remove from the progress database:
|
|
|
|
_propagator->_journal->setUploadInfo(_item._file, SyncJournalDb::UploadInfo());
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("upload file start");
|
2013-11-25 22:12:13 +04:00
|
|
|
|
|
|
|
if (hbf_validate_source_file(trans.data()) == HBF_SOURCE_FILE_CHANGE) {
|
|
|
|
/* Did the source file changed since the upload ?
|
|
|
|
This is different from the previous check because the previous check happens between
|
|
|
|
chunks while this one happens when the whole file has been uploaded.
|
|
|
|
|
|
|
|
The new etag is already stored in the database in the previous lines so in case of
|
|
|
|
crash, we won't have a conflict but we will properly do a new upload
|
|
|
|
*/
|
|
|
|
|
|
|
|
if( attempts++ < 5 ) { /* FIXME: How often do we want to try? */
|
|
|
|
qDebug("SOURCE file has changed after upload, retry #%d in %d seconds!", attempts, 2*attempts);
|
|
|
|
sleep(2*attempts);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Still the file change error, but we tried a couple of times.
|
|
|
|
// Ignore this file for now.
|
|
|
|
// Lets remove the file from the server (at least if it is new) as it is different
|
|
|
|
// from our file here.
|
|
|
|
if( _item._instruction == CSYNC_INSTRUCTION_NEW ) {
|
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri(
|
|
|
|
ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
|
|
|
|
|
|
|
|
int rc = ne_delete(_propagator->_session, uri.data());
|
|
|
|
qDebug() << "Remove the invalid file from server:" << rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString errMsg = tr("Local file changed during sync, syncing once it arrived completely");
|
|
|
|
done( SyncFileItem::SoftError, errMsg );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-11-27 19:48:10 +04:00
|
|
|
emit progress(Progress::EndUpload, _item, _item._size, _item._size);
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::Success);
|
|
|
|
return;
|
2013-10-16 13:59:54 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
} while( true );
|
2013-05-04 17:32:11 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
void PropagateUploadFile::chunk_finished_cb(hbf_transfer_s *trans, int chunk, void* userdata)
|
|
|
|
{
|
|
|
|
PropagateUploadFile *that = static_cast<PropagateUploadFile *>(userdata);
|
|
|
|
Q_ASSERT(that);
|
|
|
|
that->_chunked_done += trans->block_arr[chunk]->size;
|
|
|
|
if (trans->block_cnt > 1) {
|
|
|
|
SyncJournalDb::UploadInfo pi;
|
|
|
|
pi._valid = true;
|
|
|
|
pi._chunk = chunk + 1; // next chunk to start with
|
|
|
|
pi._transferid = trans->transfer_id;
|
2014-01-29 14:39:14 +04:00
|
|
|
pi._modtime = Utility::qDateTimeFromTime_t(trans->modtime);
|
2014-01-16 16:49:55 +04:00
|
|
|
that->_propagator->_journal->setUploadInfo(that->_item._file, pi);
|
|
|
|
that->_propagator->_journal->commit("Upload info");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFile::notify_status_cb(void* userdata, ne_session_status status,
|
|
|
|
const ne_session_status_info* info)
|
|
|
|
{
|
|
|
|
PropagateUploadFile* that = reinterpret_cast<PropagateUploadFile*>(userdata);
|
|
|
|
|
|
|
|
if (status == ne_status_sending && info->sr.total > 0) {
|
|
|
|
emit that->progress(Progress::Context, that->_item,
|
|
|
|
that->_chunked_done + info->sr.progress,
|
|
|
|
that->_chunked_total_size ? that->_chunked_total_size : info->sr.total );
|
|
|
|
|
2014-01-31 20:29:50 +04:00
|
|
|
QCoreApplication::processEvents();
|
2014-01-16 16:49:55 +04:00
|
|
|
that->limitBandwidth(that->_chunked_done + info->sr.progress, that->_propagator->_uploadLimit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-05 13:29:06 +04:00
|
|
|
|
2013-10-28 18:28:34 +04:00
|
|
|
static QString parseFileId(ne_request *req) {
|
|
|
|
QString fileId;
|
|
|
|
|
2013-10-30 20:36:11 +04:00
|
|
|
const char *header = ne_get_response_header(req, "OC-FileId");
|
2013-10-28 18:28:34 +04:00
|
|
|
if( header ) {
|
|
|
|
fileId = QString::fromUtf8(header);
|
|
|
|
}
|
|
|
|
return fileId;
|
|
|
|
}
|
|
|
|
|
2014-02-06 20:02:10 +04:00
|
|
|
bool PropagateItemJob::updateMTimeAndETag(const char* uri, time_t mtime)
|
2013-05-04 17:32:11 +04:00
|
|
|
{
|
|
|
|
QByteArray modtime = QByteArray::number(qlonglong(mtime));
|
2013-05-03 21:11:00 +04:00
|
|
|
ne_propname pname;
|
|
|
|
pname.nspace = "DAV:";
|
|
|
|
pname.name = "lastmodified";
|
|
|
|
ne_proppatch_operation ops[2];
|
|
|
|
ops[0].name = &pname;
|
|
|
|
ops[0].type = ne_propset;
|
|
|
|
ops[0].value = modtime.constData();
|
|
|
|
ops[1].name = NULL;
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
int rc = ne_proppatch( _propagator->_session, uri, ops );
|
2013-11-13 17:30:08 +04:00
|
|
|
Q_UNUSED(rc);
|
2013-10-28 13:47:10 +04:00
|
|
|
/* FIXME: error handling
|
2013-05-04 19:14:25 +04:00
|
|
|
bool error = updateErrorFromSession( rc );
|
|
|
|
if( error ) {
|
|
|
|
// FIXME: We could not set the mtime. Error or not?
|
2013-05-05 14:34:38 +04:00
|
|
|
qDebug() << "PROP-Patching of modified date failed.";
|
2013-10-28 13:47:10 +04:00
|
|
|
}*/
|
2013-05-03 21:11:00 +04:00
|
|
|
|
|
|
|
// get the etag
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_propagator->_session, "HEAD", uri));
|
2013-05-03 21:11:00 +04:00
|
|
|
int neon_stat = ne_request_dispatch(req.data());
|
2014-02-06 20:02:10 +04:00
|
|
|
if (updateErrorFromSession(neon_stat, req.data())) {
|
|
|
|
return false;
|
2013-05-03 21:11:00 +04:00
|
|
|
} else {
|
2013-11-26 15:10:52 +04:00
|
|
|
_item._etag = parseEtag(ne_get_response_header(req.data(), "etag"));
|
2013-10-28 18:28:34 +04:00
|
|
|
QString fid = parseFileId(req.data());
|
2013-10-28 19:26:25 +04:00
|
|
|
if( _item._fileId.isEmpty() ) {
|
|
|
|
_item._fileId = fid;
|
|
|
|
qDebug() << "FileID was empty, set it to " << _item._fileId;
|
2013-10-28 18:28:34 +04:00
|
|
|
} else {
|
2013-10-28 19:26:25 +04:00
|
|
|
if( !fid.isEmpty() && fid != _item._fileId ) {
|
|
|
|
qDebug() << "WARN: FileID seems to have changed: "<< fid << _item._fileId;
|
2013-10-30 20:36:58 +04:00
|
|
|
} else {
|
|
|
|
qDebug() << "FileID is " << _item._fileId;
|
2013-10-28 18:28:34 +04:00
|
|
|
}
|
|
|
|
}
|
2014-02-06 20:02:10 +04:00
|
|
|
return true;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-10-25 15:29:20 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateItemJob::limitBandwidth(qint64 progress, qint64 bandwidth_limit)
|
|
|
|
{
|
|
|
|
if (bandwidth_limit > 0) {
|
|
|
|
int64_t diff = _lastTime.nsecsElapsed() / 1000;
|
|
|
|
int64_t len = progress - _lastProgress;
|
|
|
|
if (len > 0 && diff > 0 && (1000000 * len / diff) > bandwidth_limit) {
|
|
|
|
int64_t wait_time = (1000000 * len / bandwidth_limit) - diff;
|
|
|
|
if (wait_time > 0) {
|
2013-12-10 16:47:44 +04:00
|
|
|
//qDebug() << "Limiting bandwidth to " << bandwidth_limit << "KB/s by waiting " << wait_time << " µs; ";
|
|
|
|
Mirall::Utility::usleep(wait_time);
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_lastProgress = progress;
|
|
|
|
_lastTime.start();
|
|
|
|
} else if (bandwidth_limit < 0 && bandwidth_limit > -100) {
|
|
|
|
int64_t diff = _lastTime.nsecsElapsed() / 1000;
|
|
|
|
if (diff > 0) {
|
|
|
|
// -bandwidth_limit is the % of bandwidth
|
|
|
|
int64_t wait_time = -diff * (1 + 100.0 / bandwidth_limit);
|
|
|
|
if (wait_time > 0) {
|
2013-12-10 16:47:44 +04:00
|
|
|
Mirall::Utility::usleep(wait_time);
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_lastTime.start();
|
|
|
|
}
|
|
|
|
}
|
2013-05-04 17:32:11 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
int PropagateDownloadFile::content_reader(void *userdata, const char *buf, size_t len)
|
|
|
|
{
|
|
|
|
PropagateDownloadFile *that = static_cast<PropagateDownloadFile *>(userdata);
|
|
|
|
size_t written = 0;
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
if (that->_propagator->_abortRequested->fetchAndAddRelaxed(0)) {
|
2014-01-20 20:22:29 +04:00
|
|
|
ne_set_error(that->_propagator->_session, "%s", tr("Sync was aborted by user.").toUtf8().data());
|
2014-01-16 16:49:55 +04:00
|
|
|
return NE_ERROR;
|
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
if(buf) {
|
|
|
|
written = that->_file->write(buf, len);
|
|
|
|
if( len != written || that->_file->error() != QFile::NoError) {
|
|
|
|
qDebug() << "WRN: content_reader wrote wrong num of bytes:" << len << "," << written;
|
2013-10-02 21:41:17 +04:00
|
|
|
return NE_ERROR;
|
|
|
|
}
|
2014-01-16 16:49:55 +04:00
|
|
|
return NE_OK;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2014-01-31 20:29:50 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
return NE_ERROR;
|
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
/*
|
|
|
|
* This hook is called after the response is here from the server, but before
|
|
|
|
* the response body is parsed. It decides if the response is compressed and
|
|
|
|
* if it is it installs the compression reader accordingly.
|
|
|
|
* If the response is not compressed, the normal response body reader is installed.
|
|
|
|
*/
|
|
|
|
void PropagateDownloadFile::install_content_reader( ne_request *req, void *userdata, const ne_status *status )
|
|
|
|
{
|
|
|
|
PropagateDownloadFile *that = static_cast<PropagateDownloadFile *>(userdata);
|
2013-11-20 17:59:58 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
Q_UNUSED(status);
|
2013-11-19 15:44:25 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
if( !that ) {
|
|
|
|
qDebug("Error: install_content_reader called without valid write context!");
|
|
|
|
return;
|
2013-11-19 15:44:25 +04:00
|
|
|
}
|
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
if( ne_get_status(req)->klass != 2 ) {
|
|
|
|
qDebug() << "Request class != 2, aborting.";
|
|
|
|
ne_add_response_body_reader( req, do_not_accept,
|
|
|
|
do_not_download_content_reader,
|
|
|
|
(void*) that );
|
|
|
|
return;
|
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
QByteArray reason_phrase = ne_get_status(req)->reason_phrase;
|
|
|
|
if(reason_phrase == QByteArray("Connection established")) {
|
|
|
|
ne_add_response_body_reader( req, ne_accept_2xx,
|
|
|
|
content_reader,
|
|
|
|
(void*) that );
|
|
|
|
return;
|
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
QByteArray etag = parseEtag(ne_get_response_header(req, "etag"));
|
|
|
|
if(etag.isEmpty())
|
|
|
|
etag = parseEtag(ne_get_response_header(req, "ETag"));
|
2013-12-11 15:59:01 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
if (etag.isEmpty()) {
|
|
|
|
qDebug() << Q_FUNC_INFO << "No E-Tag reply by server, considering it invalid" << ne_get_response_header(req, "etag");
|
|
|
|
that->errorString = tr("No E-Tag received from server, check Proxy/Gateway");
|
2014-01-20 20:22:29 +04:00
|
|
|
ne_set_error(that->_propagator->_session, "%s", that->errorString.toUtf8().data());
|
2014-01-16 16:49:55 +04:00
|
|
|
ne_add_response_body_reader( req, do_not_accept,
|
|
|
|
do_not_download_content_reader,
|
2013-12-11 16:50:22 +04:00
|
|
|
(void*) that );
|
2014-01-16 16:49:55 +04:00
|
|
|
return;
|
|
|
|
} else if (!that->_expectedEtagForResume.isEmpty() && that->_expectedEtagForResume != etag) {
|
|
|
|
qDebug() << Q_FUNC_INFO << "We received a different E-Tag for resuming!"
|
|
|
|
<< QString::fromLatin1(that->_expectedEtagForResume.data()) << "vs"
|
|
|
|
<< QString::fromLatin1(etag.data());
|
|
|
|
that->errorString = tr("We received a different E-Tag for resuming. Retrying next time.");
|
2014-01-20 20:22:29 +04:00
|
|
|
ne_set_error(that->_propagator->_session, "%s", that->errorString.toUtf8().data());
|
2014-01-16 16:49:55 +04:00
|
|
|
ne_add_response_body_reader( req, do_not_accept,
|
|
|
|
do_not_download_content_reader,
|
|
|
|
(void*) that );
|
|
|
|
return;
|
|
|
|
}
|
2013-11-19 15:44:25 +04:00
|
|
|
|
2013-11-25 22:00:34 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
const char *enc = ne_get_response_header( req, "Content-Encoding" );
|
|
|
|
qDebug("Content encoding ist <%s> with status %d", enc ? enc : "empty",
|
|
|
|
status ? status->code : -1 );
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
if( enc == QLatin1String("gzip") ) {
|
|
|
|
that->_decompress.reset(ne_decompress_reader( req, ne_accept_2xx,
|
|
|
|
content_reader, /* reader callback */
|
|
|
|
that )); /* userdata */
|
|
|
|
} else {
|
|
|
|
ne_add_response_body_reader( req, ne_accept_2xx,
|
|
|
|
content_reader,
|
|
|
|
(void*) that );
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
2014-01-16 16:49:55 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
|
2014-01-16 16:49:55 +04:00
|
|
|
void PropagateDownloadFile::notify_status_cb(void* userdata, ne_session_status status,
|
|
|
|
const ne_session_status_info* info)
|
|
|
|
{
|
|
|
|
PropagateDownloadFile* that = reinterpret_cast<PropagateDownloadFile*>(userdata);
|
|
|
|
if (status == ne_status_recving && info->sr.total > 0) {
|
|
|
|
emit that->progress(Progress::Context, that->_item, info->sr.progress, info->sr.total );
|
2014-01-31 20:29:50 +04:00
|
|
|
|
|
|
|
QCoreApplication::processEvents();
|
2014-01-16 16:49:55 +04:00
|
|
|
that->limitBandwidth(info->sr.progress, that->_propagator->_downloadLimit);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2014-01-16 16:49:55 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateDownloadFile::start()
|
2013-05-03 21:11:00 +04:00
|
|
|
{
|
2013-11-25 19:16:33 +04:00
|
|
|
emit progress(Progress::StartDownload, _item, 0, _item._size);
|
2013-10-28 13:47:10 +04:00
|
|
|
|
2013-05-06 18:59:11 +04:00
|
|
|
QString tmpFileName;
|
2013-10-28 13:47:10 +04:00
|
|
|
const SyncJournalDb::DownloadInfo progressInfo = _propagator->_journal->getDownloadInfo(_item._file);
|
2013-10-16 13:59:54 +04:00
|
|
|
if (progressInfo._valid) {
|
2013-10-03 14:40:48 +04:00
|
|
|
// if the etag has changed meanwhile, remove the already downloaded part.
|
2013-10-28 13:47:10 +04:00
|
|
|
if (progressInfo._etag != _item._etag) {
|
|
|
|
QFile::remove(_propagator->_localDir + progressInfo._tmpfile);
|
|
|
|
_propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
|
2013-05-06 18:59:11 +04:00
|
|
|
} else {
|
2013-10-16 13:59:54 +04:00
|
|
|
tmpFileName = progressInfo._tmpfile;
|
2013-11-25 22:00:34 +04:00
|
|
|
_expectedEtagForResume = progressInfo._etag;
|
2013-05-06 18:59:11 +04:00
|
|
|
}
|
2013-10-16 13:59:54 +04:00
|
|
|
|
2013-05-06 18:59:11 +04:00
|
|
|
}
|
2013-10-03 14:40:48 +04:00
|
|
|
|
2013-05-06 18:59:11 +04:00
|
|
|
if (tmpFileName.isEmpty()) {
|
2013-10-28 13:47:10 +04:00
|
|
|
tmpFileName = _item._file;
|
2013-05-08 16:31:52 +04:00
|
|
|
//add a dot at the begining of the filename to hide the file.
|
|
|
|
int slashPos = tmpFileName.lastIndexOf('/');
|
|
|
|
tmpFileName.insert(slashPos+1, '.');
|
|
|
|
//add the suffix
|
|
|
|
tmpFileName += ".~" + QString::number(uint(qrand()), 16);
|
2013-05-06 18:59:11 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
QFile tmpFile(_propagator->_localDir + tmpFileName);
|
|
|
|
_file = &tmpFile;
|
2013-11-25 22:22:43 +04:00
|
|
|
if (!tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::NormalError, tmpFile.errorString());
|
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2014-01-08 16:32:48 +04:00
|
|
|
csync_win32_set_file_hidden(tmpFile.fileName().toUtf8().constData(), true);
|
2013-05-06 18:59:11 +04:00
|
|
|
|
|
|
|
{
|
2013-10-16 13:59:54 +04:00
|
|
|
SyncJournalDb::DownloadInfo pi;
|
2013-10-28 13:47:10 +04:00
|
|
|
pi._etag = _item._etag;
|
2013-10-16 13:59:54 +04:00
|
|
|
pi._tmpfile = tmpFileName;
|
|
|
|
pi._valid = true;
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->setDownloadInfo(_item._file, pi);
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("download file start");
|
2013-05-06 18:59:11 +04:00
|
|
|
}
|
|
|
|
|
2013-05-03 21:11:00 +04:00
|
|
|
/* actually do the request */
|
|
|
|
int retry = 0;
|
2013-05-06 18:59:11 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri(
|
|
|
|
ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
|
2013-05-03 21:11:00 +04:00
|
|
|
|
|
|
|
do {
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_propagator->_session, "GET", uri.data()));
|
2013-05-03 21:11:00 +04:00
|
|
|
|
|
|
|
/* Allow compressed content by setting the header */
|
|
|
|
ne_add_request_header( req.data(), "Accept-Encoding", "gzip" );
|
|
|
|
|
|
|
|
if (tmpFile.size() > 0) {
|
2013-12-03 17:43:01 +04:00
|
|
|
quint64 done = tmpFile.size();
|
|
|
|
if (done == _item._size) {
|
|
|
|
qDebug() << "File is already complete, no need to download";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
QByteArray rangeRequest = "bytes=" + QByteArray::number(done) +'-';
|
|
|
|
ne_add_request_header(req.data(), "Range", rangeRequest.constData());
|
2013-05-03 21:11:00 +04:00
|
|
|
ne_add_request_header(req.data(), "Accept-Ranges", "bytes");
|
2013-12-03 17:43:01 +04:00
|
|
|
qDebug() << "Retry with range " << rangeRequest;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* hook called before the content is parsed to set the correct reader,
|
|
|
|
* either the compressed- or uncompressed reader.
|
|
|
|
*/
|
2013-10-28 13:47:10 +04:00
|
|
|
ne_hook_post_headers( _propagator->_session, install_content_reader, this);
|
|
|
|
ne_set_notifier(_propagator->_session, notify_status_cb, this);
|
2013-08-14 21:59:16 +04:00
|
|
|
_lastProgress = 0;
|
|
|
|
_lastTime.start();
|
2013-05-03 21:11:00 +04:00
|
|
|
|
|
|
|
int neon_stat = ne_request_dispatch(req.data());
|
|
|
|
|
2013-12-05 19:49:24 +04:00
|
|
|
_decompress.reset(); // Destroy the decompress after the request has been dispatched.
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
/* delete the hook again, otherwise they get chained as they are with the session */
|
|
|
|
ne_unhook_post_headers( _propagator->_session, install_content_reader, this );
|
|
|
|
ne_set_notifier(_propagator->_session, 0, 0);
|
|
|
|
|
2013-05-04 19:14:25 +04:00
|
|
|
if (neon_stat == NE_TIMEOUT && (++retry) < 3) {
|
2013-05-03 21:11:00 +04:00
|
|
|
continue;
|
2013-05-04 19:14:25 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2013-11-19 15:44:25 +04:00
|
|
|
// This one is set by install_content_reader if e.g. there is no E-Tag
|
|
|
|
if (!errorString.isEmpty()) {
|
2013-12-11 15:48:11 +04:00
|
|
|
// don't keep the temporary file as the file downloaded so far is invalid
|
|
|
|
tmpFile.close();
|
|
|
|
tmpFile.remove();
|
|
|
|
_propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
|
2013-11-19 15:44:25 +04:00
|
|
|
done(SyncFileItem::SoftError, errorString);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This one is set by neon
|
2013-05-05 13:41:31 +04:00
|
|
|
if( updateErrorFromSession(neon_stat, req.data() ) ) {
|
2013-05-03 21:11:00 +04:00
|
|
|
qDebug("Error GET: Neon: %d", neon_stat);
|
2013-05-06 18:59:11 +04:00
|
|
|
if (tmpFile.size() == 0) {
|
|
|
|
// don't keep the temporary file if it is empty.
|
|
|
|
tmpFile.close();
|
|
|
|
tmpFile.remove();
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
|
2013-05-06 18:59:11 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-11-26 15:10:52 +04:00
|
|
|
_item._etag = parseEtag(ne_get_response_header(req.data(), "etag"));
|
2013-05-03 21:11:00 +04:00
|
|
|
break;
|
|
|
|
} while (1);
|
|
|
|
|
|
|
|
tmpFile.close();
|
|
|
|
tmpFile.flush();
|
2013-10-28 13:47:10 +04:00
|
|
|
QString fn = _propagator->_localDir + _item._file;
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2013-10-30 13:46:05 +04:00
|
|
|
|
2013-10-29 15:23:51 +04:00
|
|
|
bool isConflict = _item._instruction == CSYNC_INSTRUCTION_CONFLICT
|
|
|
|
&& !fileEquals(fn, tmpFile.fileName()); // compare the files to see if there was an actual conflict.
|
2013-05-05 13:14:40 +04:00
|
|
|
//In case of conflict, make a backup of the old file
|
|
|
|
if (isConflict) {
|
|
|
|
QFile f(fn);
|
2013-11-07 19:53:22 +04:00
|
|
|
QString conflictFileName(fn);
|
2013-05-05 13:14:40 +04:00
|
|
|
// Add _conflict-XXXX before the extention.
|
2013-11-07 19:53:22 +04:00
|
|
|
int dotLocation = conflictFileName.lastIndexOf('.');
|
2013-05-05 13:14:40 +04:00
|
|
|
// If no extention, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
|
2013-11-07 19:53:22 +04:00
|
|
|
if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) {
|
|
|
|
dotLocation = conflictFileName.size();
|
2013-05-05 13:14:40 +04:00
|
|
|
}
|
2014-01-29 14:39:14 +04:00
|
|
|
QString timeString = Utility::qDateTimeFromTime_t(_item._modtime).toString("yyyyMMdd-hhmmss");
|
|
|
|
conflictFileName.insert(dotLocation, "_conflict-" + timeString);
|
2013-11-07 19:53:22 +04:00
|
|
|
if (!f.rename(conflictFileName)) {
|
2013-05-05 13:14:40 +04:00
|
|
|
//If the rename fails, don't replace it.
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::NormalError, f.errorString());
|
|
|
|
return;
|
2013-05-05 13:14:40 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-08 16:17:35 +04:00
|
|
|
QFileInfo existingFile(fn);
|
|
|
|
if(existingFile.exists() && existingFile.permissions() != tmpFile.permissions()) {
|
|
|
|
tmpFile.setPermissions(existingFile.permissions());
|
|
|
|
}
|
|
|
|
|
2014-01-08 16:32:48 +04:00
|
|
|
csync_win32_set_file_hidden(tmpFile.fileName().toUtf8().constData(), false);
|
2013-05-08 16:31:52 +04:00
|
|
|
|
2013-12-03 19:10:44 +04:00
|
|
|
#ifndef Q_OS_WIN
|
2013-09-05 19:08:05 +04:00
|
|
|
bool success;
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
2013-10-28 13:47:10 +04:00
|
|
|
success = tmpFile.fileEngine()->rename(fn);
|
2013-11-07 19:53:22 +04:00
|
|
|
// qDebug() << "Renaming " << tmpFile.fileName() << " to " << fn;
|
2013-09-05 19:08:05 +04:00
|
|
|
#else
|
|
|
|
// We want a rename that also overwite. QFile::rename does not overwite.
|
|
|
|
// Qt 5.1 has QSaveFile::renameOverwrite we cold use.
|
|
|
|
// ### FIXME
|
2013-10-28 13:47:10 +04:00
|
|
|
QFile::remove(fn);
|
|
|
|
success = tmpFile.rename(fn);
|
2013-09-05 19:08:05 +04:00
|
|
|
#endif
|
2013-10-04 18:41:15 +04:00
|
|
|
// unixoids
|
2013-09-05 19:08:05 +04:00
|
|
|
if (!success) {
|
2013-11-07 19:53:22 +04:00
|
|
|
qDebug() << "FAIL: renaming temp file to final failed: " << tmpFile.errorString();
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::NormalError, tmpFile.errorString());
|
|
|
|
return;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
2013-12-03 19:10:44 +04:00
|
|
|
#else //Q_OS_WIN
|
2013-12-03 20:07:21 +04:00
|
|
|
BOOL ok;
|
|
|
|
ok = MoveFileEx((wchar_t*)tmpFile.fileName().utf16(),
|
|
|
|
(wchar_t*)QString(_propagator->_localDir + _item._file).utf16(),
|
|
|
|
MOVEFILE_REPLACE_EXISTING+MOVEFILE_COPY_ALLOWED+MOVEFILE_WRITE_THROUGH);
|
|
|
|
if (!ok) {
|
2013-05-04 18:18:13 +04:00
|
|
|
wchar_t *string = 0;
|
|
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
|
|
|
|
NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
|
|
(LPWSTR)&string, 0, NULL);
|
2013-10-28 13:47:10 +04:00
|
|
|
|
|
|
|
done(SyncFileItem::NormalError, QString::fromWCharArray(string));
|
2013-05-04 18:18:13 +04:00
|
|
|
LocalFree((HLOCAL)string);
|
2013-10-28 13:47:10 +04:00
|
|
|
return;
|
2013-05-04 18:18:13 +04:00
|
|
|
}
|
|
|
|
#endif
|
2013-05-06 14:09:08 +04:00
|
|
|
struct timeval times[2];
|
2013-10-28 13:47:10 +04:00
|
|
|
times[0].tv_sec = times[1].tv_sec = _item._modtime;
|
2013-05-06 14:09:08 +04:00
|
|
|
times[0].tv_usec = times[1].tv_usec = 0;
|
2013-10-28 13:47:10 +04:00
|
|
|
c_utimes(fn.toUtf8().data(), times);
|
2013-05-06 14:09:08 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, fn));
|
|
|
|
_propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("download file start2");
|
2013-11-27 19:48:10 +04:00
|
|
|
emit progress(Progress::EndDownload, _item, _item._size, _item._size);
|
2013-10-28 13:47:10 +04:00
|
|
|
done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-30 20:36:58 +04:00
|
|
|
|
|
|
|
void PropagateLocalRename::start()
|
|
|
|
{
|
2013-12-06 15:32:26 +04:00
|
|
|
// if the file is a file underneath a moved dir, the _item.file is equal
|
|
|
|
// to _item.renameTarget and the file is not moved as a result.
|
2013-10-30 21:14:33 +04:00
|
|
|
if (_item._file != _item._renameTarget) {
|
2013-12-06 15:32:26 +04:00
|
|
|
emit progress(Progress::StartRename, _item, 0, _item._size);
|
2013-10-30 20:36:58 +04:00
|
|
|
qDebug() << "MOVE " << _propagator->_localDir + _item._file << " => " << _propagator->_localDir + _item._renameTarget;
|
|
|
|
QFile::rename(_propagator->_localDir + _item._file, _propagator->_localDir + _item._renameTarget);
|
2013-12-06 15:32:26 +04:00
|
|
|
emit progress(Progress::EndRename, _item, _item._size, _item._size);
|
2013-10-30 20:36:58 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
_item._instruction = CSYNC_INSTRUCTION_DELETED;
|
|
|
|
_propagator->_journal->deleteFileRecord(_item._originalFile);
|
|
|
|
|
2013-11-05 20:50:09 +04:00
|
|
|
// store the rename file name in the item.
|
|
|
|
_item._file = _item._renameTarget;
|
|
|
|
|
|
|
|
SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget);
|
2013-10-30 20:36:58 +04:00
|
|
|
record._path = _item._renameTarget;
|
2013-11-05 20:50:09 +04:00
|
|
|
|
2014-02-06 20:16:22 +04:00
|
|
|
if (!_item._isDirectory) { // Directory are saved at the end
|
|
|
|
_propagator->_journal->setFileRecord(record);
|
|
|
|
}
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("localRename");
|
2013-11-18 12:59:59 +04:00
|
|
|
|
2013-11-05 20:50:09 +04:00
|
|
|
|
2013-10-30 20:36:58 +04:00
|
|
|
done(SyncFileItem::Success);
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateRemoteRename::start()
|
2013-05-04 17:32:11 +04:00
|
|
|
{
|
2014-02-19 16:06:55 +04:00
|
|
|
qDebug() << Q_FUNC_INFO << _item._file << "-->"<< _item._renameTarget;
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
if (_item._file == _item._renameTarget) {
|
|
|
|
if (!_item._isDirectory) {
|
2013-10-17 14:57:05 +04:00
|
|
|
// The parents has been renamed already so there is nothing more to do.
|
|
|
|
// But we still need to fetch the new ETAG
|
2013-10-30 20:36:58 +04:00
|
|
|
// FIXME maybe do a recusrsive propfind after having moved the parent.
|
2013-10-17 14:57:05 +04:00
|
|
|
// Note: we also update the mtime because the server do not keep the mtime when moving files
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri2(
|
|
|
|
ne_path_escape((_propagator->_remoteDir + _item._renameTarget).toUtf8()));
|
2014-02-06 20:02:10 +04:00
|
|
|
if (!updateMTimeAndETag(uri2.data(), _item._modtime))
|
|
|
|
return;
|
2013-10-17 14:57:05 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
} else if (_item._file == QLatin1String("Shared") ) {
|
|
|
|
// Check if it is the toplevel Shared folder and do not propagate it.
|
|
|
|
if( QFile::rename( _propagator->_localDir + _item._renameTarget, _propagator->_localDir + QLatin1String("Shared")) ) {
|
|
|
|
done(SyncFileItem::NormalError, tr("This folder must not be renamed. It is renamed back to its original name."));
|
2013-10-17 12:48:24 +04:00
|
|
|
} else {
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::NormalError, tr("This folder must not be renamed. Please name it back to Shared."));
|
2013-10-17 12:48:24 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
return;
|
|
|
|
} else {
|
2013-11-25 19:16:33 +04:00
|
|
|
emit progress(Progress::StartRename, _item, 0, _item._size);
|
2013-10-17 12:48:24 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri1(ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
|
|
|
|
QScopedPointer<char, QScopedPointerPodDeleter> uri2(ne_path_escape((_propagator->_remoteDir + _item._renameTarget).toUtf8()));
|
2013-11-15 15:27:06 +04:00
|
|
|
qDebug() << "MOVE on Server: " << uri1.data() << "->" << uri2.data();
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
int rc = ne_move(_propagator->_session, 1, uri1.data(), uri2.data());
|
2014-02-04 18:01:10 +04:00
|
|
|
|
|
|
|
if( checkForProblemsWithShared()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
if (updateErrorFromSession(rc)) {
|
|
|
|
return;
|
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-02-06 20:02:10 +04:00
|
|
|
if (!updateMTimeAndETag(uri2.data(), _item._modtime))
|
|
|
|
return;
|
2013-11-27 19:48:10 +04:00
|
|
|
emit progress(Progress::EndRename, _item, _item._size, _item._size);
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
2013-05-04 17:32:11 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->deleteFileRecord(_item._originalFile);
|
|
|
|
SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget);
|
|
|
|
record._path = _item._renameTarget;
|
2013-11-05 20:50:09 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
_propagator->_journal->setFileRecord(record);
|
2013-11-21 14:13:58 +04:00
|
|
|
_propagator->_journal->commit("Remote Rename");
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::Success);
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
bool PropagateItemJob::updateErrorFromSession(int neon_code, ne_request* req, int ignoreHttpCode)
|
2013-05-04 18:12:51 +04:00
|
|
|
{
|
|
|
|
if( neon_code != NE_OK ) {
|
2013-05-05 13:41:31 +04:00
|
|
|
qDebug("Neon error code was %d", neon_code);
|
|
|
|
}
|
2013-05-04 18:12:51 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
QString errorString;
|
|
|
|
int httpStatusCode = 0;
|
|
|
|
|
2013-05-04 19:14:25 +04:00
|
|
|
switch(neon_code) {
|
|
|
|
case NE_OK: /* Success, but still the possiblity of problems */
|
2013-10-04 17:13:36 +04:00
|
|
|
if( req ) {
|
2013-05-05 13:41:31 +04:00
|
|
|
const ne_status *status = ne_get_status(req);
|
2013-11-20 17:02:02 +04:00
|
|
|
|
2013-10-04 17:13:36 +04:00
|
|
|
if (status) {
|
2013-10-28 13:47:10 +04:00
|
|
|
if ( status->klass == 2 || status->code == ignoreHttpCode) {
|
2013-10-04 17:13:36 +04:00
|
|
|
// Everything is ok, no error.
|
|
|
|
return false;
|
2013-05-05 13:41:31 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
errorString = QString::fromUtf8( status->reason_phrase );
|
|
|
|
httpStatusCode = status->code;
|
2013-11-20 17:02:02 +04:00
|
|
|
_item._httpErrorCode = httpStatusCode;
|
2013-05-05 13:41:31 +04:00
|
|
|
}
|
2013-10-04 17:55:10 +04:00
|
|
|
} else {
|
2013-10-28 13:47:10 +04:00
|
|
|
errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
|
2013-10-30 20:36:58 +04:00
|
|
|
httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
|
2013-11-20 17:02:02 +04:00
|
|
|
_item._httpErrorCode = httpStatusCode;
|
2013-10-28 13:47:10 +04:00
|
|
|
if ((httpStatusCode >= 200 && httpStatusCode < 300)
|
|
|
|
|| (httpStatusCode != 0 && httpStatusCode == ignoreHttpCode)) {
|
2013-10-04 17:55:10 +04:00
|
|
|
// No error
|
|
|
|
return false;
|
|
|
|
}
|
2013-05-04 19:14:25 +04:00
|
|
|
}
|
2013-10-04 17:13:36 +04:00
|
|
|
// FIXME: classify the error
|
2013-10-28 13:47:10 +04:00
|
|
|
done (SyncFileItem::NormalError, errorString);
|
2013-10-04 17:13:36 +04:00
|
|
|
return true;
|
2013-05-04 19:14:25 +04:00
|
|
|
case NE_ERROR: /* Generic error; use ne_get_error(session) for message */
|
2013-10-30 20:33:06 +04:00
|
|
|
errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
|
2013-11-20 17:02:02 +04:00
|
|
|
// Check if we don't need to ignore that error.
|
|
|
|
httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
|
|
|
|
_item._httpErrorCode = httpStatusCode;
|
2013-11-25 19:37:06 +04:00
|
|
|
qDebug() << Q_FUNC_INFO << "NE_ERROR" << errorString << httpStatusCode << ignoreHttpCode;
|
|
|
|
if (ignoreHttpCode && httpStatusCode == ignoreHttpCode)
|
2013-11-20 17:02:02 +04:00
|
|
|
return false;
|
|
|
|
|
2013-10-30 20:33:06 +04:00
|
|
|
done(SyncFileItem::NormalError, errorString);
|
2013-10-04 17:13:36 +04:00
|
|
|
return true;
|
2013-05-04 19:14:25 +04:00
|
|
|
case NE_LOOKUP: /* Server or proxy hostname lookup failed */
|
|
|
|
case NE_AUTH: /* User authentication failed on server */
|
|
|
|
case NE_PROXYAUTH: /* User authentication failed on proxy */
|
|
|
|
case NE_CONNECT: /* Could not connect to server */
|
|
|
|
case NE_TIMEOUT: /* Connection timed out */
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::FatalError, QString::fromUtf8(ne_get_error(_propagator->_session)));
|
|
|
|
return true;
|
2013-05-04 19:14:25 +04:00
|
|
|
case NE_FAILED: /* The precondition failed */
|
|
|
|
case NE_RETRY: /* Retry request (ne_end_request ONLY) */
|
|
|
|
case NE_REDIRECT: /* See ne_redirect.h */
|
|
|
|
default:
|
2013-10-28 13:47:10 +04:00
|
|
|
done(SyncFileItem::SoftError, QString::fromUtf8(ne_get_error(_propagator->_session)));
|
2013-10-04 17:13:36 +04:00
|
|
|
return true;
|
2013-05-04 19:14:25 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
return false;
|
2013-05-03 21:11:00 +04:00
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
PropagateItemJob* OwncloudPropagator::createJob(const SyncFileItem& item) {
|
|
|
|
switch(item._instruction) {
|
|
|
|
case CSYNC_INSTRUCTION_REMOVE:
|
|
|
|
if (item._dir == SyncFileItem::Down) return new PropagateLocalRemove(this, item);
|
|
|
|
else return new PropagateRemoteRemove(this, item);
|
|
|
|
case CSYNC_INSTRUCTION_NEW:
|
|
|
|
if (item._isDirectory) {
|
|
|
|
if (item._dir == SyncFileItem::Down) return new PropagateLocalMkdir(this, item);
|
|
|
|
else return new PropagateRemoteMkdir(this, item);
|
|
|
|
} //fall trough
|
|
|
|
case CSYNC_INSTRUCTION_SYNC:
|
|
|
|
case CSYNC_INSTRUCTION_CONFLICT:
|
|
|
|
if (item._isDirectory) {
|
|
|
|
// Should we set the mtime?
|
|
|
|
return 0;
|
|
|
|
}
|
2013-10-29 15:07:57 +04:00
|
|
|
if (item._dir != SyncFileItem::Up) return new PropagateDownloadFile(this, item);
|
2013-10-28 13:47:10 +04:00
|
|
|
else return new PropagateUploadFile(this, item);
|
|
|
|
case CSYNC_INSTRUCTION_RENAME:
|
2013-10-30 20:36:58 +04:00
|
|
|
if (item._dir == SyncFileItem::Up) {
|
|
|
|
return new PropagateRemoteRename(this, item);
|
|
|
|
} else {
|
|
|
|
return new PropagateLocalRename(this, item);
|
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
case CSYNC_INSTRUCTION_IGNORE:
|
|
|
|
return new PropagateIgnoreJob(this, item);
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2013-08-14 21:59:16 +04:00
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void OwncloudPropagator::start(const SyncFileItemVector& _syncedItems)
|
|
|
|
{
|
2013-10-28 20:00:27 +04:00
|
|
|
/* This builds all the job needed for the propagation.
|
|
|
|
* Each directories is a PropagateDirectory job, which contains the files in it.
|
|
|
|
* In order to do that we sort the items by destination. and loop over it. When we enter a
|
|
|
|
* directory, we can create the directory job and push it on the stack. */
|
2013-10-28 13:47:10 +04:00
|
|
|
SyncFileItemVector items = _syncedItems;
|
|
|
|
std::sort(items.begin(), items.end());
|
|
|
|
_rootJob.reset(new PropagateDirectory(this));
|
2013-10-28 20:00:27 +04:00
|
|
|
QStack<QPair<QString /* directory name */, PropagateDirectory* /* job */> > directories;
|
2013-10-28 13:47:10 +04:00
|
|
|
directories.push(qMakePair(QString(), _rootJob.data()));
|
2013-10-28 20:00:27 +04:00
|
|
|
QVector<PropagatorJob*> directoriesToRemove;
|
2013-10-28 13:47:10 +04:00
|
|
|
QString removedDirectory;
|
|
|
|
foreach(const SyncFileItem &item, items) {
|
|
|
|
if (item._instruction == CSYNC_INSTRUCTION_REMOVE
|
|
|
|
&& !removedDirectory.isEmpty() && item._file.startsWith(removedDirectory)) {
|
|
|
|
//already taken care of. (by the removal of the parent directory)
|
|
|
|
continue;
|
2013-08-14 21:59:16 +04:00
|
|
|
}
|
|
|
|
|
2014-02-19 16:06:55 +04:00
|
|
|
while (!item.destination().startsWith(directories.top().first)) {
|
2013-10-28 13:47:10 +04:00
|
|
|
directories.pop();
|
2013-08-14 21:59:16 +04:00
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
|
|
|
|
if (item._isDirectory) {
|
|
|
|
PropagateDirectory *dir = new PropagateDirectory(this, item);
|
|
|
|
dir->_firstJob.reset(createJob(item));
|
|
|
|
if (item._instruction == CSYNC_INSTRUCTION_REMOVE) {
|
|
|
|
//We do the removal of directories at the end
|
|
|
|
directoriesToRemove.append(dir);
|
|
|
|
removedDirectory = item._file + "/";
|
|
|
|
} else {
|
|
|
|
directories.top().second->append(dir);
|
2013-08-14 21:59:16 +04:00
|
|
|
}
|
2014-02-19 16:06:55 +04:00
|
|
|
directories.push(qMakePair(item.destination() + "/" , dir));
|
2013-10-28 13:47:10 +04:00
|
|
|
} else if (PropagateItemJob* current = createJob(item)) {
|
|
|
|
directories.top().second->append(current);
|
2013-08-14 21:59:16 +04:00
|
|
|
}
|
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
|
|
|
|
foreach(PropagatorJob* it, directoriesToRemove) {
|
|
|
|
_rootJob->append(it);
|
|
|
|
}
|
|
|
|
|
|
|
|
connect(_rootJob.data(), SIGNAL(completed(SyncFileItem)), this, SIGNAL(completed(SyncFileItem)));
|
2013-11-25 19:16:33 +04:00
|
|
|
connect(_rootJob.data(), SIGNAL(progress(Progress::Kind,SyncFileItem,quint64,quint64)), this,
|
|
|
|
SIGNAL(progress(Progress::Kind,SyncFileItem,quint64,quint64)));
|
2013-10-28 13:47:10 +04:00
|
|
|
connect(_rootJob.data(), SIGNAL(finished(SyncFileItem::Status)), this, SIGNAL(finished()));
|
2013-11-21 14:13:58 +04:00
|
|
|
|
2013-10-31 15:11:56 +04:00
|
|
|
_rootJob->start();
|
2013-08-14 21:59:16 +04:00
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-01-07 18:42:21 +04:00
|
|
|
void OwncloudPropagator::overallTransmissionSizeChanged(qint64 change)
|
|
|
|
{
|
|
|
|
emit progressChanged(change);
|
|
|
|
}
|
|
|
|
|
2014-02-04 18:01:10 +04:00
|
|
|
bool OwncloudPropagator::isInSharedDirectory(const QString& file)
|
|
|
|
{
|
|
|
|
bool re = false;
|
|
|
|
if( _remoteDir.contains("remote.php/webdav/Shared") ) {
|
|
|
|
// The Shared directory is synced as its own sync connection
|
|
|
|
re = true;
|
|
|
|
} else {
|
2014-02-19 18:23:36 +04:00
|
|
|
if( file.startsWith("Shared/") || file == "Shared" ) {
|
2014-02-04 18:01:10 +04:00
|
|
|
// The whole ownCloud is synced and Shared is always a top dir
|
|
|
|
re = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return re;
|
|
|
|
}
|
|
|
|
|
2013-11-15 20:42:27 +04:00
|
|
|
void PropagateDirectory::start()
|
|
|
|
{
|
|
|
|
_current = -1;
|
2013-11-28 13:00:12 +04:00
|
|
|
_hasError = SyncFileItem::NoStatus;
|
2013-11-15 20:42:27 +04:00
|
|
|
if (!_firstJob) {
|
|
|
|
proceedNext(SyncFileItem::Success);
|
|
|
|
} else {
|
|
|
|
startJob(_firstJob.data());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:47:10 +04:00
|
|
|
void PropagateDirectory::proceedNext(SyncFileItem::Status status)
|
|
|
|
{
|
2014-02-19 16:06:55 +04:00
|
|
|
if (status == SyncFileItem::FatalError || (_current == -1 && status != SyncFileItem::Success)) {
|
2013-10-28 13:47:10 +04:00
|
|
|
emit finished(status);
|
|
|
|
return;
|
2013-11-28 13:00:12 +04:00
|
|
|
} else if (status == SyncFileItem::NormalError || status == SyncFileItem::SoftError) {
|
|
|
|
_hasError = status;
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
_current ++;
|
2013-11-28 12:43:39 +04:00
|
|
|
if (_current < _subJobs.size() && !_propagator->_abortRequested->fetchAndAddRelaxed(0)) {
|
2013-10-28 13:47:10 +04:00
|
|
|
PropagatorJob *next = _subJobs.at(_current);
|
|
|
|
startJob(next);
|
|
|
|
} else {
|
2013-11-28 13:00:12 +04:00
|
|
|
if (!_item.isEmpty() && _hasError == SyncFileItem::NoStatus) {
|
2013-11-05 20:50:09 +04:00
|
|
|
if( !_item._renameTarget.isEmpty() ) {
|
|
|
|
_item._file = _item._renameTarget;
|
|
|
|
}
|
|
|
|
|
2014-01-08 15:51:42 +04:00
|
|
|
if (_item._should_update_etag && _item._instruction != CSYNC_INSTRUCTION_REMOVE) {
|
2013-11-15 16:53:18 +04:00
|
|
|
SyncJournalFileRecord record(_item, _propagator->_localDir + _item._file);
|
|
|
|
_propagator->_journal->setFileRecord(record);
|
|
|
|
}
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
2013-11-28 13:00:12 +04:00
|
|
|
emit finished(_hasError == SyncFileItem::NoStatus ? SyncFileItem::Success : _hasError);
|
2013-10-28 13:47:10 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-03 21:11:00 +04:00
|
|
|
|
2014-02-04 18:01:10 +04:00
|
|
|
|
2013-05-04 18:12:51 +04:00
|
|
|
}
|