[Pkg-owncloud-commits] [owncloud-client] 78/333: Merge branch '1.5'

Sandro Knauß hefee-guest at moszumanska.debian.org
Thu Apr 17 23:16:36 UTC 2014


This is an automated email from the git hooks/post-receive script.

hefee-guest pushed a commit to branch master
in repository owncloud-client.

commit 869793592d0bb1426a5a2c121ffd3880a2f1d62e
Merge: 417dbcc 02bfb4f
Author: Olivier Goffart <ogoffart at woboq.com>
Date:   Thu Feb 27 11:40:34 2014 +0100

    Merge branch '1.5'
    
    Conflicts:
    	VERSION.cmake
    	src/mirall/owncloudpropagator.cpp
    	src/mirall/owncloudpropagator.h

 ChangeLog                                          | 22 ++++++
 csync/src/csync_update.c                           |  9 +--
 csync/src/httpbf/tests/hbf_send_test.c             | 20 ++++++
 csync/src/std/c_string.c                           |  2 +-
 csync/tests/cmdline.c                              | 19 +++++
 csync/tests/csync_tests/check_csync_commit.c       | 19 +++++
 csync/tests/csync_tests/check_csync_create.c       | 19 +++++
 csync/tests/csync_tests/check_csync_exclude.c      | 19 +++++
 csync/tests/csync_tests/check_csync_init.c         | 19 +++++
 csync/tests/csync_tests/check_csync_log.c          | 19 +++++
 csync/tests/csync_tests/check_csync_misc.c         | 19 +++++
 csync/tests/csync_tests/check_csync_statedb_load.c | 19 +++++
 .../tests/csync_tests/check_csync_statedb_query.c  | 19 +++++
 csync/tests/csync_tests/check_csync_time.c         | 19 +++++
 csync/tests/csync_tests/check_csync_treewalk.c     | 19 +++++
 csync/tests/csync_tests/check_csync_update.c       | 19 +++++
 csync/tests/csync_tests/check_csync_util.c         | 19 +++++
 csync/tests/encoding_tests/check_encoding.c        | 19 +++++
 csync/tests/httpbf_tests/hbf_send_test.c           | 19 +++++
 csync/tests/log_tests/check_log.c                  | 19 +++++
 csync/tests/ownCloud/t1.pl                         | 14 ++++
 csync/tests/ownCloud/t2.pl                         | 14 ++++
 csync/tests/ownCloud/t3.pl                         | 14 ++++
 csync/tests/ownCloud/t4.pl                         | 14 ++++
 csync/tests/std_tests/check_std_c_alloc.c          | 19 +++++
 csync/tests/std_tests/check_std_c_dir.c            | 19 +++++
 csync/tests/std_tests/check_std_c_file.c           | 19 +++++
 csync/tests/std_tests/check_std_c_list.c           | 19 +++++
 csync/tests/std_tests/check_std_c_path.c           | 19 +++++
 csync/tests/std_tests/check_std_c_rbtree.c         | 19 +++++
 csync/tests/std_tests/check_std_c_str.c            | 19 +++++
 csync/tests/std_tests/check_std_c_time.c           | 19 +++++
 csync/tests/torture.c                              | 19 +++++
 csync/tests/torture.h                              | 19 +++++
 csync/tests/vio_tests/check_vio.c                  | 19 +++++
 csync/tests/vio_tests/check_vio_file_stat.c        | 19 +++++
 csync/tests/vio_tests/check_vio_handle.c           | 19 +++++
 doc/scripts/htmlhelp.sh                            | 13 ++++
 src/CMakeLists.txt                                 |  2 +
 src/creds/abstractcredentials.h                    |  4 ++
 src/creds/httpcredentials.cpp                      | 80 ++++++++++++++--------
 src/creds/httpcredentials.h                        |  1 +
 src/creds/shibboleth/shibbolethaccessmanager.cpp   |  3 +-
 src/creds/shibbolethcredentials.cpp                | 68 +++++++++++++++---
 src/creds/shibbolethcredentials.h                  |  5 ++
 src/mirall/account.cpp                             | 14 +++-
 src/mirall/account.h                               |  4 ++
 src/mirall/accountsettings.cpp                     |  9 ++-
 src/mirall/application.cpp                         | 30 +++++++-
 src/mirall/application.h                           |  3 +-
 src/mirall/folder.cpp                              |  5 +-
 src/mirall/mirallconfigfile.cpp                    |  1 -
 src/mirall/networkjobs.cpp                         | 10 +--
 src/mirall/owncloudgui.cpp                         |  8 ---
 src/mirall/owncloudgui.h                           |  4 --
 src/mirall/owncloudpropagator.cpp                  | 66 ++++++++++++++++++
 src/mirall/owncloudpropagator.h                    | 13 ++++
 src/mirall/propagator_legacy.cpp                   |  5 +-
 src/mirall/propagatorjobs.cpp                      | 51 +-------------
 src/mirall/propagatorjobs.h                        |  8 ---
 src/mirall/quotainfo.cpp                           | 49 +++++++------
 src/mirall/quotainfo.h                             |  9 ++-
 src/mirall/settingsdialog.cpp                      | 15 ++--
 src/mirall/settingsdialog.h                        |  2 +-
 src/mirall/sslbutton.cpp                           | 25 ++++---
 src/wizard/abstractcredswizardpage.cpp             | 34 +++++++++
 src/wizard/abstractcredswizardpage.h               |  3 +-
 src/wizard/owncloudshibbolethcredspage.cpp         |  4 +-
 test/manual/favlink/main.cpp                       | 12 ++++
 test/scripts/torture_create_files.pl               | 13 ++++
 test/scripts/torture_gen_layout.pl                 | 13 ++++
 translations/mirall_de.ts                          |  2 +-
 72 files changed, 1072 insertions(+), 176 deletions(-)

diff --cc src/mirall/owncloudpropagator.cpp
index 8298fdf,ccf22a4..f5f1281
--- a/src/mirall/owncloudpropagator.cpp
+++ b/src/mirall/owncloudpropagator.cpp
@@@ -72,6 -99,948 +72,72 @@@ void PropagateItemJob::done(SyncFileIte
      emit finished(status);
  }
  
 -
+ /**
+  * 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.
+  */
+ bool PropagateItemJob::checkForProblemsWithShared(const QString& msg)
+ {
+     QString errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
+     int httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
+ 
+     PropagateItemJob *newJob = NULL;
+ 
+     if( httpStatusCode == 403 && _propagator->isInSharedDirectory(_item._file )) {
+         if( _item._type != SyncFileItem::Directory ) {
+             SyncFileItem downloadItem(_item);
+             if (downloadItem._instruction == CSYNC_INSTRUCTION_NEW) {
+                 // don't try to recover pushing new files
+                 return false;
+             } else if (downloadItem._instruction == CSYNC_INSTRUCTION_SYNC) {
+                 // we modified the file locally, jsut create a conflict then
+                 downloadItem._instruction = CSYNC_INSTRUCTION_CONFLICT;
+             } else {
+                 // the file was removed or renamed, just recover the old one
+                 downloadItem._instruction = CSYNC_INSTRUCTION_SYNC;
+             }
+             downloadItem._dir = SyncFileItem::Down;
 -            newJob = new PropagateDownloadFile(_propagator, downloadItem);
++            newJob = new PropagateDownloadFileLegacy(_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;
+             newJob = 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);
+         }
+         if( newJob )  {
+             newJob->setRestoreJobMsg(msg);
+             _restoreJob.reset(newJob);
+             connect(_restoreJob.data(), SIGNAL(completed(SyncFileItem)),
+                     this, SLOT(slotRestoreJobCompleted(SyncFileItem)));
+             _restoreJob->start();
+         }
+         return true;
+     }
+     return false;
+ }
+ 
+ void PropagateItemJob::slotRestoreJobCompleted(const SyncFileItem& item )
+ {
+     QString msg;
+     if(_restoreJob) {
+         msg = _restoreJob->restoreJobMsg();
+         _restoreJob->setRestoreJobMsg();
+     }
+ 
+     if( item._status == SyncFileItem::Success ||  item._status == SyncFileItem::Conflict) {
+         done( SyncFileItem::SoftError, msg);
+     } else {
+         done( item._status, tr("A file or directory was removed from a read only share, but restoring failed: %1").arg(item._errorString) );
+     }
+ }
+ 
 -
 -QString PropagateItemJob::restoreJobMsg()
 -{
 -    return _restoreJobMsg;
 -}
 -
 -void PropagateItemJob::setRestoreJobMsg( const QString& msg )
 -{
 -    _restoreJobMsg = msg;
 -}
 -
 -// 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;
 -}
 -
 -// 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;
 -}
 -
 -void PropagateLocalRemove::start()
 -{
 -    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;
 -        }
 -    } else {
 -        QFile file(filename);
 -        if (file.exists() && !file.remove()) {
 -            done(SyncFileItem::NormalError, file.errorString());
 -            return;
 -        }
 -    }
 -    emit progress(Progress::StartDelete, _item, 0, _item._size);
 -    _propagator->_journal->deleteFileRecord(_item._originalFile, _item._isDirectory);
 -    _propagator->_journal->commit("Local remove");
 -    done(SyncFileItem::Success);
 -    emit progress(Progress::EndDelete, _item, _item._size, _item._size);
 -}
 -
 -void PropagateLocalMkdir::start()
 -{
 -    QDir d;
 -    if (!d.mkpath(_propagator->_localDir +  _item._file)) {
 -        done(SyncFileItem::NormalError, tr("could not create directory %1").arg(_propagator->_localDir +  _item._file));
 -        return;
 -    }
 -    done(SyncFileItem::Success);
 -}
 -
 -void PropagateRemoteRemove::start()
 -{
 -    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 -        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 -    emit progress(Progress::StartDelete, _item, 0, _item._size);
 -    qDebug() << "** DELETE " << uri.data();
 -    int rc = ne_delete(_propagator->_session, uri.data());
 -
 -    if( checkForProblemsWithShared(tr("The file has been removed from a read only share. It was restored.")) ) {
 -        return;
 -    }
 -
 -    /* Ignore the error 404,  it means it is already deleted */
 -    if (updateErrorFromSession(rc, 0, 404)) {
 -        return;
 -    }
 -
 -    _propagator->_journal->deleteFileRecord(_item._originalFile, _item._isDirectory);
 -    _propagator->_journal->commit("Remote Remove");
 -    done(SyncFileItem::Success);
 -    emit progress(Progress::EndDelete, _item, _item._size, _item._size);
 -}
 -
 -void PropagateRemoteMkdir::start()
 -{
 -    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 -        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 -
 -    int rc = ne_mkcol(_propagator->_session, uri.data());
 -
 -    /* Special for mkcol: it returns 405 if the directory already exists.
 -     * Ignore that error */
 -    if( updateErrorFromSession( rc , 0, 405 ) ) {
 -        return;
 -    }
 -    done(SyncFileItem::Success);
 -}
 -
 -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;
 -}
 -
 -void PropagateUploadFile::start()
 -{
 -
 -    QFile file(_propagator->_localDir + _item._file);
 -    if (!file.open(QIODevice::ReadOnly)) {
 -        done(SyncFileItem::NormalError, file.errorString());
 -        return;
 -    }
 -    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 -        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 -
 -    int attempts = 0;
 -
 -    /*
 -     * 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.
 -     */
 -    qDebug() << "** PUT request to" << uri.data();
 -    const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item._file);
 -
 -    do {
 -        Hbf_State state = HBF_SUCCESS;
 -        QScopedPointer<hbf_transfer_t, ScopedPointerHelpers> trans(hbf_init_transfer(uri.data()));
 -        trans->user_data = this;
 -        hbf_set_log_callback(trans.data(), _log_callback);
 -        hbf_set_abort_callback(trans.data(), _user_want_abort);
 -        trans.data()->chunk_finished_cb = chunk_finished_cb;
 -        Q_ASSERT(trans);
 -
 -        state = hbf_splitlist(trans.data(), file.handle());
 -
 -        // 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);
 -
 -        if (progressInfo._valid) {
 -            if (Utility::qDateTimeToTime_t(progressInfo._modtime) == _item._modtime) {
 -                trans->start_id = progressInfo._chunk;
 -                trans->transfer_id = progressInfo._transferid;
 -            }
 -        }
 -
 -        ne_set_notifier(_propagator->_session, notify_status_cb, this);
 -        _lastTime.restart();
 -        _lastProgress = 0;
 -        _chunked_done = 0;
 -        _chunked_total_size = _item._size;
 -
 -        if( state == HBF_SUCCESS ) {
 -            QByteArray previousEtag;
 -            if (!_item._etag.isEmpty() && _item._etag != "empty_etag") {
 -                // 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.
 -                previousEtag = '"' + _item._etag + '"';
 -                trans->previous_etag = previousEtag.data();
 -            }
 -            _chunked_total_size = trans->stat_size;
 -            qDebug() << "About to upload " << _item._file << "  (" << previousEtag << _item._size << " bytes )";
 -            /* Transfer all the chunks through the HTTP session using PUT. */
 -            state = hbf_transfer( _propagator->_session, trans.data(), "PUT" );
 -        }
 -
 -        // the file id should only be empty for new files up- or downloaded
 -        QString fid = QString::fromUtf8( hbf_transfer_file_id( trans.data() ));
 -        if( !fid.isEmpty() ) {
 -            if( !_item._fileId.isEmpty() && _item._fileId != fid ) {
 -                qDebug() << "WARN: File ID changed!" << _item._fileId << fid;
 -            }
 -            _item._fileId = fid;
 -        }
 -
 -        /* Handle errors. */
 -        if ( state != HBF_SUCCESS ) {
 -
 -            /* If the source file changed during submission, lets try again */
 -            if( state == HBF_SOURCE_FILE_CHANGE ) {
 -                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);
 -                    if( _previousFileSize == 0 ) {
 -                        _previousFileSize = _item._size;
 -                    } else {
 -                        _previousFileSize = trans->stat_size;
 -                    }
 -                    continue;
 -                }
 -
 -                const QString errMsg = tr("Local file changed during sync, syncing once it arrived completely");
 -                done( SyncFileItem::SoftError, errMsg );
 -            } else if( state == HBF_USER_ABORTED ) {
 -                const QString errMsg = tr("Sync was aborted by user.");
 -                done( SyncFileItem::SoftError, errMsg );
 -            } else {
 -                // Other HBF error conditions.
 -                if(checkForProblemsWithShared(tr("The file was edited locally but is part of a read only share. "
 -                                                 "It is restored and your edit is in the conflict file.")))
 -                    return;
 -                done(SyncFileItem::NormalError, hbf_error_string(trans.data(), state));
 -            }
 -            return;
 -        }
 -
 -        ne_set_notifier(_propagator->_session, 0, 0);
 -
 -        if( trans->modtime_accepted ) {
 -            _item._etag = parseEtag(hbf_transfer_etag( trans.data() ));
 -        } else {
 -            if (!updateMTimeAndETag(uri.data(), _item._modtime))
 -                return;
 -        }
 -
 -        _propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, _propagator->_localDir + _item._file));
 -        // Remove from the progress database:
 -        _propagator->_journal->setUploadInfo(_item._file, SyncJournalDb::UploadInfo());
 -        _propagator->_journal->commit("upload file start");
 -
 -        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;
 -        }
 -
 -
 -
 -        emit progress(Progress::EndUpload, _item, _item._size, _item._size);
 -        done(SyncFileItem::Success);
 -        return;
 -
 -    } while( true );
 -}
 -
 -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;
 -    pi._modtime =  Utility::qDateTimeFromTime_t(trans->modtime);
 -    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 );
 -
 -     QCoreApplication::processEvents();
 -    that->limitBandwidth(that->_chunked_done + info->sr.progress,  that->_propagator->_uploadLimit);
 -  }
 -}
 -
 -
 -
 -static QString parseFileId(ne_request *req) {
 -    QString fileId;
 -
 -    const char *header = ne_get_response_header(req, "OC-FileId");
 -    if( header ) {
 -        fileId = QString::fromUtf8(header);
 -    }
 -    return fileId;
 -}
 -
 -bool PropagateItemJob::updateMTimeAndETag(const char* uri, time_t mtime)
 -{
 -    QByteArray modtime = QByteArray::number(qlonglong(mtime));
 -    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;
 -
 -    int rc = ne_proppatch( _propagator->_session, uri, ops );
 -    Q_UNUSED(rc);
 -    /* FIXME: error handling
 -    bool error = updateErrorFromSession( rc );
 -    if( error ) {
 -        // FIXME: We could not set the mtime. Error or not?
 -        qDebug() << "PROP-Patching of modified date failed.";
 -    }*/
 -
 -    // get the etag
 -    QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_propagator->_session, "HEAD", uri));
 -    int neon_stat = ne_request_dispatch(req.data());
 -    if (updateErrorFromSession(neon_stat, req.data())) {
 -        return false;
 -    } else {
 -        _item._etag = parseEtag(ne_get_response_header(req.data(), "etag"));
 -        QString fid = parseFileId(req.data());
 -        if( _item._fileId.isEmpty() ) {
 -            _item._fileId = fid;
 -            qDebug() << "FileID was empty, set it to " << _item._fileId;
 -        } else {
 -            if( !fid.isEmpty() && fid != _item._fileId ) {
 -                qDebug() << "WARN: FileID seems to have changed: "<< fid << _item._fileId;
 -            } else {
 -                qDebug() << "FileID is " << _item._fileId;
 -            }
 -        }
 -        return true;
 -    }
 -}
 -
 -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) {
 -                //qDebug() << "Limiting bandwidth to " << bandwidth_limit << "KB/s by waiting " << wait_time << " µs; ";
 -                Mirall::Utility::usleep(wait_time);
 -            }
 -        }
 -        _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) {
 -                Mirall::Utility::usleep(wait_time);
 -
 -            }
 -        }
 -        _lastTime.start();
 -    }
 -}
 -
 -int PropagateDownloadFile::content_reader(void *userdata, const char *buf, size_t len)
 -{
 -    PropagateDownloadFile *that = static_cast<PropagateDownloadFile *>(userdata);
 -    size_t written = 0;
 -
 -    if (that->_propagator->_abortRequested->fetchAndAddRelaxed(0)) {
 -        ne_set_error(that->_propagator->_session, "%s", tr("Sync was aborted by user.").toUtf8().data());
 -        return NE_ERROR;
 -    }
 -
 -    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;
 -            return NE_ERROR;
 -        }
 -        return NE_OK;
 -    }
 -
 -    return NE_ERROR;
 -}
 -
 -/*
 - * 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);
 -
 -    Q_UNUSED(status);
 -
 -    if( !that ) {
 -        qDebug("Error: install_content_reader called without valid write context!");
 -        return;
 -    }
 -
 -    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;
 -    }
 -
 -    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;
 -    }
 -
 -    QByteArray etag = parseEtag(ne_get_response_header(req, "etag"));
 -    if(etag.isEmpty())
 -        etag = parseEtag(ne_get_response_header(req, "ETag"));
 -
 -    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");
 -        ne_set_error(that->_propagator->_session, "%s", that->errorString.toUtf8().data());
 -        ne_add_response_body_reader( req, do_not_accept,
 -                                        do_not_download_content_reader,
 -                                        (void*) that );
 -        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.");
 -        ne_set_error(that->_propagator->_session, "%s", that->errorString.toUtf8().data());
 -        ne_add_response_body_reader( req, do_not_accept,
 -                                        do_not_download_content_reader,
 -                                        (void*) that );
 -        return;
 -    }
 -
 -
 -    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 );
 -
 -    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 );
 -    }
 -}
 -
 -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 );
 -
 -        QCoreApplication::processEvents();
 -        that->limitBandwidth(info->sr.progress,  that->_propagator->_downloadLimit);
 -    }
 -}
 -
 -void PropagateDownloadFile::start()
 -{
 -    emit progress(Progress::StartDownload, _item, 0, _item._size);
 -
 -    QString tmpFileName;
 -    const SyncJournalDb::DownloadInfo progressInfo = _propagator->_journal->getDownloadInfo(_item._file);
 -    if (progressInfo._valid) {
 -        // if the etag has changed meanwhile, remove the already downloaded part.
 -        if (progressInfo._etag != _item._etag) {
 -            QFile::remove(_propagator->_localDir + progressInfo._tmpfile);
 -            _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
 -        } else {
 -            tmpFileName = progressInfo._tmpfile;
 -            _expectedEtagForResume = progressInfo._etag;
 -        }
 -
 -    }
 -
 -    if (tmpFileName.isEmpty()) {
 -        tmpFileName = _item._file;
 -        //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);
 -    }
 -
 -    QFile tmpFile(_propagator->_localDir + tmpFileName);
 -    _file = &tmpFile;
 -    if (!tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
 -        done(SyncFileItem::NormalError, tmpFile.errorString());
 -        return;
 -    }
 -
 -    csync_win32_set_file_hidden(tmpFile.fileName().toUtf8().constData(), true);
 -
 -    {
 -        SyncJournalDb::DownloadInfo pi;
 -        pi._etag = _item._etag;
 -        pi._tmpfile = tmpFileName;
 -        pi._valid = true;
 -        _propagator->_journal->setDownloadInfo(_item._file, pi);
 -        _propagator->_journal->commit("download file start");
 -    }
 -
 -    /* actually do the request */
 -    int retry = 0;
 -
 -    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 -        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 -
 -    do {
 -        QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_propagator->_session, "GET", uri.data()));
 -
 -        /* Allow compressed content by setting the header */
 -        ne_add_request_header( req.data(), "Accept-Encoding", "gzip" );
 -
 -        if (tmpFile.size() > 0) {
 -            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());
 -            ne_add_request_header(req.data(), "Accept-Ranges", "bytes");
 -            qDebug() << "Retry with range " << rangeRequest;
 -        }
 -
 -        /* hook called before the content is parsed to set the correct reader,
 -         * either the compressed- or uncompressed reader.
 -         */
 -        ne_hook_post_headers( _propagator->_session, install_content_reader, this);
 -        ne_set_notifier(_propagator->_session, notify_status_cb, this);
 -        _lastProgress = 0;
 -        _lastTime.start();
 -
 -        int neon_stat = ne_request_dispatch(req.data());
 -
 -        _decompress.reset(); // Destroy the decompress after the request has been dispatched.
 -
 -        /* 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);
 -
 -        if (neon_stat == NE_TIMEOUT && (++retry) < 3) {
 -            continue;
 -        }
 -
 -        // This one is set by install_content_reader if e.g. there is no E-Tag
 -        if (!errorString.isEmpty()) {
 -            // 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());
 -            done(SyncFileItem::SoftError, errorString);
 -            return;
 -        }
 -
 -        // This one is set by neon
 -        if( updateErrorFromSession(neon_stat, req.data() ) ) {
 -            qDebug("Error GET: Neon: %d", neon_stat);
 -            if (tmpFile.size() == 0) {
 -                // don't keep the temporary file if it is empty.
 -                tmpFile.close();
 -                tmpFile.remove();
 -                _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
 -            }
 -            return;
 -        }
 -        _item._etag = parseEtag(ne_get_response_header(req.data(), "etag"));
 -        break;
 -    } while (1);
 -
 -    tmpFile.close();
 -    tmpFile.flush();
 -    QString fn = _propagator->_localDir + _item._file;
 -
 -
 -    bool isConflict = _item._instruction == CSYNC_INSTRUCTION_CONFLICT
 -            && !fileEquals(fn, tmpFile.fileName()); // compare the files to see if there was an actual conflict.
 -    //In case of conflict, make a backup of the old file
 -    if (isConflict) {
 -        QFile f(fn);
 -        QString conflictFileName(fn);
 -        // Add _conflict-XXXX  before the extention.
 -        int dotLocation = conflictFileName.lastIndexOf('.');
 -        // If no extention, add it at the end  (take care of cases like foo/.hidden or foo.bar/file)
 -        if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) {
 -            dotLocation = conflictFileName.size();
 -        }
 -        QString timeString = Utility::qDateTimeFromTime_t(_item._modtime).toString("yyyyMMdd-hhmmss");
 -        conflictFileName.insert(dotLocation, "_conflict-" + timeString);
 -        if (!f.rename(conflictFileName)) {
 -            //If the rename fails, don't replace it.
 -            done(SyncFileItem::NormalError, f.errorString());
 -            return;
 -        }
 -    }
 -
 -    QFileInfo existingFile(fn);
 -    if(existingFile.exists() && existingFile.permissions() != tmpFile.permissions()) {
 -        tmpFile.setPermissions(existingFile.permissions());
 -    }
 -
 -    csync_win32_set_file_hidden(tmpFile.fileName().toUtf8().constData(), false);
 -
 -#ifndef Q_OS_WIN
 -    bool success;
 -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
 -    success = tmpFile.fileEngine()->rename(fn);
 -    // qDebug() << "Renaming " << tmpFile.fileName() << " to " << fn;
 -#else
 -    // We want a rename that also overwite.  QFile::rename does not overwite.
 -    // Qt 5.1 has QSaveFile::renameOverwrite we cold use.
 -    // ### FIXME
 -    QFile::remove(fn);
 -    success = tmpFile.rename(fn);
 -#endif
 -    // unixoids
 -    if (!success) {
 -        qDebug() << "FAIL: renaming temp file to final failed: " << tmpFile.errorString();
 -        done(SyncFileItem::NormalError, tmpFile.errorString());
 -        return;
 -    }
 -#else //Q_OS_WIN
 -    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) {
 -        wchar_t *string = 0;
 -        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
 -                      NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
 -                      (LPWSTR)&string, 0, NULL);
 -
 -        done(SyncFileItem::NormalError, QString::fromWCharArray(string));
 -        LocalFree((HLOCAL)string);
 -        return;
 -    }
 -#endif
 -    struct timeval times[2];
 -    times[0].tv_sec = times[1].tv_sec = _item._modtime;
 -    times[0].tv_usec = times[1].tv_usec = 0;
 -    c_utimes(fn.toUtf8().data(), times);
 -
 -    _propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, fn));
 -    _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
 -    _propagator->_journal->commit("download file start2");
 -    emit progress(Progress::EndDownload, _item, _item._size, _item._size);
 -    done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success);
 -}
 -
 -
 -void PropagateLocalRename::start()
 -{
 -    // 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.
 -    if (_item._file != _item._renameTarget) {
 -        emit progress(Progress::StartRename, _item, 0, _item._size);
 -        qDebug() << "MOVE " << _propagator->_localDir + _item._file << " => " << _propagator->_localDir + _item._renameTarget;
 -        QFile::rename(_propagator->_localDir + _item._file, _propagator->_localDir + _item._renameTarget);
 -        emit progress(Progress::EndRename, _item, _item._size, _item._size);
 -    }
 -
 -    _item._instruction = CSYNC_INSTRUCTION_DELETED;
 -    _propagator->_journal->deleteFileRecord(_item._originalFile);
 -
 -    // store the rename file name in the item.
 -    _item._file = _item._renameTarget;
 -
 -    SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget);
 -    record._path = _item._renameTarget;
 -
 -    if (!_item._isDirectory) { // Directory are saved at the end
 -        _propagator->_journal->setFileRecord(record);
 -    }
 -    _propagator->_journal->commit("localRename");
 -
 -
 -    done(SyncFileItem::Success);
 -}
 -
 -void PropagateRemoteRename::start()
 -{
 -    qDebug() << Q_FUNC_INFO << _item._file << "-->"<< _item._renameTarget;
 -
 -    if (_item._file == _item._renameTarget) {
 -        if (!_item._isDirectory) {
 -            // The parents has been renamed already so there is nothing more to do.
 -            // But we still need to fetch the new ETAG
 -            // FIXME   maybe do a recusrsive propfind after having moved the parent.
 -            // Note: we also update the mtime because the server do not keep the mtime when moving files
 -            QScopedPointer<char, QScopedPointerPodDeleter> uri2(
 -                ne_path_escape((_propagator->_remoteDir + _item._renameTarget).toUtf8()));
 -            if (!updateMTimeAndETag(uri2.data(), _item._modtime))
 -                return;
 -        }
 -    } 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."));
 -        } else {
 -            done(SyncFileItem::NormalError, tr("This folder must not be renamed. Please name it back to Shared."));
 -        }
 -        return;
 -    } else {
 -        emit progress(Progress::StartRename, _item, 0, _item._size);
 -
 -        QScopedPointer<char, QScopedPointerPodDeleter> uri1(ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 -        QScopedPointer<char, QScopedPointerPodDeleter> uri2(ne_path_escape((_propagator->_remoteDir + _item._renameTarget).toUtf8()));
 -        qDebug() << "MOVE on Server: " << uri1.data() << "->" << uri2.data();
 -
 -        int rc = ne_move(_propagator->_session, 1, uri1.data(), uri2.data());
 -
 -        if( checkForProblemsWithShared(tr("The file was renamed but is part of a read only share. The original file was restored."))) {
 -            return;
 -        }
 -
 -        if (updateErrorFromSession(rc)) {
 -            return;
 -        }
 -
 -        if (!updateMTimeAndETag(uri2.data(), _item._modtime))
 -            return;
 -        emit progress(Progress::EndRename, _item, _item._size, _item._size);
 -    }
 -
 -    _propagator->_journal->deleteFileRecord(_item._originalFile);
 -    SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget);
 -    record._path = _item._renameTarget;
 -
 -    _propagator->_journal->setFileRecord(record);
 -    _propagator->_journal->commit("Remote Rename");
 -    done(SyncFileItem::Success);
 -}
 -
 -bool PropagateItemJob::updateErrorFromSession(int neon_code, ne_request* req, int ignoreHttpCode)
 -{
 -    if( neon_code != NE_OK ) {
 -        qDebug("Neon error code was %d", neon_code);
 -    }
 -
 -    QString errorString;
 -    int httpStatusCode = 0;
 -
 -    switch(neon_code) {
 -    case NE_OK:     /* Success, but still the possiblity of problems */
 -        if( req ) {
 -            const ne_status *status = ne_get_status(req);
 -
 -            if (status) {
 -                if ( status->klass == 2 || status->code == ignoreHttpCode) {
 -                    // Everything is ok, no error.
 -                    return false;
 -                }
 -                errorString = QString::fromUtf8( status->reason_phrase );
 -                httpStatusCode = status->code;
 -                _item._httpErrorCode = httpStatusCode;
 -            }
 -        } else {
 -            errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
 -            httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
 -            _item._httpErrorCode = httpStatusCode;
 -            if ((httpStatusCode >= 200 && httpStatusCode < 300)
 -                || (httpStatusCode != 0 && httpStatusCode == ignoreHttpCode)) {
 -                // No error
 -                return false;
 -            }
 -        }
 -        // FIXME: classify the error
 -        done (SyncFileItem::NormalError, errorString);
 -        return true;
 -    case NE_ERROR:  /* Generic error; use ne_get_error(session) for message */
 -        errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
 -        // Check if we don't need to ignore that error.
 -        httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
 -        _item._httpErrorCode = httpStatusCode;
 -        qDebug() << Q_FUNC_INFO << "NE_ERROR" << errorString << httpStatusCode << ignoreHttpCode;
 -        if (ignoreHttpCode && httpStatusCode == ignoreHttpCode)
 -            return false;
 -
 -        done(SyncFileItem::NormalError, errorString);
 -        return true;
 -    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 */
 -        done(SyncFileItem::FatalError, QString::fromUtf8(ne_get_error(_propagator->_session)));
 -        return true;
 -    case NE_FAILED:   /* The precondition failed */
 -    case NE_RETRY:    /* Retry request (ne_end_request ONLY) */
 -    case NE_REDIRECT: /* See ne_redirect.h */
 -    default:
 -        done(SyncFileItem::SoftError, QString::fromUtf8(ne_get_error(_propagator->_session)));
 -        return true;
 -    }
 -    return false;
 -}
 -
  PropagateItemJob* OwncloudPropagator::createJob(const SyncFileItem& item) {
      switch(item._instruction) {
          case CSYNC_INSTRUCTION_REMOVE:
diff --cc src/mirall/owncloudpropagator.h
index b61200d,55666f2..d9216c7
--- a/src/mirall/owncloudpropagator.h
+++ b/src/mirall/owncloudpropagator.h
@@@ -135,7 -101,41 +135,20 @@@ class PropagateItemJob : public Propaga
  protected:
      void done(SyncFileItem::Status status, const QString &errorString = QString());
  
 -    /* Issue a PROPPATCH and PROPFIND to update the mtime, and fetch the etag
 -     * Return true in case of success, and false if the PROPFIND failed and the
 -     * error has been reported
 -     */
 -    bool updateMTimeAndETag(const char* uri, time_t mtime);
 -
 -    /* fetch the error code and string from the session
 -       in case of error, calls done with the error and returns true.
 -
 -       If the HTTP error code is ignoreHTTPError,  the error is ignored
 -     */
 -    bool updateErrorFromSession(int neon_code = 0, ne_request *req = 0, int ignoreHTTPError = 0);
 -
 -    /*
 -     * to be called by the progress callback and will wait the amount of time needed.
 -     */
 -    void limitBandwidth(qint64 progress, qint64 limit);
 -
+     bool checkForProblemsWithShared(const QString& msg);
+ 
+     /*
+      * set a custom restore job message that is used if the restore job succeeded.
+      * It is displayed in the activity view.
+      */
 -    QString restoreJobMsg();
 -    void setRestoreJobMsg( const QString& msg = QString() );
++    QString restoreJobMsg() const { return _restoreJobMsg; }
++    void setRestoreJobMsg( const QString& msg = QString() ) { _restoreJobMsg = msg; }
+ 
 -    QElapsedTimer _lastTime;
 -    qint64        _lastProgress;
 -    int           _httpStatusCode;
      SyncFileItem  _item;
+     QString       _restoreJobMsg;
+ 
+ protected slots:
+     void slotRestoreJobCompleted(const SyncFileItem& );
  
  private:
      QScopedPointer<PropagateItemJob> _restoreJob;
diff --cc src/mirall/propagator_legacy.cpp
index e3b7d01,0000000..551408a
mode 100644,000000..100644
--- a/src/mirall/propagator_legacy.cpp
+++ b/src/mirall/propagator_legacy.cpp
@@@ -1,602 -1,0 +1,605 @@@
 +/*
 + * Copyright (C) by Olivier Goffart <ogoffart at owncloud.com>
 + * Copyright (C) by Klaas Freitag <freitag at 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 "propagator_legacy.h"
 +#include "owncloudpropagator_p.h"
 +
 +#include "utility.h"
 +#include "syncjournaldb.h"
 +#include "syncjournalfilerecord.h"
 +#include "filesystem.h"
 +#include <httpbf.h>
 +#include <qfile.h>
 +#include <qdir.h>
 +#include <qdiriterator.h>
 +#include <qtemporaryfile.h>
 +#include <QDebug>
 +#include <QDateTime>
 +#include <qstack.h>
 +#include <QCoreApplication>
 +
 +#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>
 +
 +#include <time.h>
 +
 +
 +namespace Mirall {
 +
 +
 +void PropagateUploadFileLegacy::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    QFile file(_propagator->_localDir + _item._file);
 +    if (!file.open(QIODevice::ReadOnly)) {
 +        done(SyncFileItem::NormalError, file.errorString());
 +        return;
 +    }
 +    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 +        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 +
 +    int attempts = 0;
 +
 +    /*
 +     * 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.
 +     */
 +    qDebug() << "** PUT request to" << uri.data();
 +    const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item._file);
 +
 +    do {
 +        Hbf_State state = HBF_SUCCESS;
 +        QScopedPointer<hbf_transfer_t, ScopedPointerHelpers> trans(hbf_init_transfer(uri.data()));
 +        trans->user_data = this;
 +        hbf_set_log_callback(trans.data(), _log_callback);
 +        hbf_set_abort_callback(trans.data(), _user_want_abort);
 +        trans.data()->chunk_finished_cb = chunk_finished_cb;
 +        Q_ASSERT(trans);
 +
 +        state = hbf_splitlist(trans.data(), file.handle());
 +
 +        // 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);
 +
 +        if (progressInfo._valid) {
 +            if (Utility::qDateTimeToTime_t(progressInfo._modtime) == _item._modtime) {
 +                trans->start_id = progressInfo._chunk;
 +                trans->transfer_id = progressInfo._transferid;
 +            }
 +        }
 +
 +        ne_set_notifier(_propagator->_session, notify_status_cb, this);
 +        _lastTime.restart();
 +        _lastProgress = 0;
 +        _chunked_done = 0;
 +        _chunked_total_size = _item._size;
 +
 +        if( state == HBF_SUCCESS ) {
 +            QByteArray previousEtag;
 +            if (!_item._etag.isEmpty() && _item._etag != "empty_etag") {
 +                // 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.
 +                previousEtag = '"' + _item._etag + '"';
 +                trans->previous_etag = previousEtag.data();
 +            }
 +            _chunked_total_size = trans->stat_size;
 +            qDebug() << "About to upload " << _item._file << "  (" << previousEtag << _item._size << " bytes )";
 +            /* Transfer all the chunks through the HTTP session using PUT. */
 +            state = hbf_transfer( _propagator->_session, trans.data(), "PUT" );
 +        }
 +
 +        // the file id should only be empty for new files up- or downloaded
 +        QString fid = QString::fromUtf8( hbf_transfer_file_id( trans.data() ));
 +        if( !fid.isEmpty() ) {
 +            if( !_item._fileId.isEmpty() && _item._fileId != fid ) {
 +                qDebug() << "WARN: File ID changed!" << _item._fileId << fid;
 +            }
 +            _item._fileId = fid;
 +        }
 +
 +        /* Handle errors. */
 +        if ( state != HBF_SUCCESS ) {
 +
 +            /* If the source file changed during submission, lets try again */
 +            if( state == HBF_SOURCE_FILE_CHANGE ) {
 +                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);
 +                    Utility::sleep(2*attempts);
 +                    if( _previousFileSize == 0 ) {
 +                        _previousFileSize = _item._size;
 +                    } else {
 +                        _previousFileSize = trans->stat_size;
 +                    }
 +                    continue;
 +                }
 +
 +                const QString errMsg = tr("Local file changed during sync, syncing once it arrived completely");
 +                done( SyncFileItem::SoftError, errMsg );
 +            } else if( state == HBF_USER_ABORTED ) {
 +                const QString errMsg = tr("Sync was aborted by user.");
 +                done( SyncFileItem::SoftError, errMsg );
 +            } else {
 +                // Other HBF error conditions.
-                 // FIXME: find out the error class.
 +                _item._httpErrorCode = hbf_fail_http_code(trans.data());
++                if(checkForProblemsWithShared(tr("The file was edited locally but is part of a read only share. "
++                                                 "It is restored and your edit is in the conflict file.")))
++                    return;
++
 +                done(SyncFileItem::NormalError, hbf_error_string(trans.data(), state));
 +            }
 +            return;
 +        }
 +
 +        ne_set_notifier(_propagator->_session, 0, 0);
 +
 +        if( trans->modtime_accepted ) {
 +            _item._etag = parseEtag(hbf_transfer_etag( trans.data() ));
 +        } else {
 +            if (!updateMTimeAndETag(uri.data(), _item._modtime))
 +                return;
 +        }
 +
 +        _propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, _propagator->_localDir + _item._file));
 +        // Remove from the progress database:
 +        _propagator->_journal->setUploadInfo(_item._file, SyncJournalDb::UploadInfo());
 +        _propagator->_journal->commit("upload file start");
 +
 +        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);
 +                Utility::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;
 +        }
 +
 +
 +
 +        emit progress(Progress::EndUpload, _item, _item._size, _item._size);
 +        done(SyncFileItem::Success);
 +        return;
 +
 +    } while( true );
 +}
 +
 +void PropagateUploadFileLegacy::chunk_finished_cb(hbf_transfer_s *trans, int chunk, void* userdata)
 +{
 +    PropagateUploadFileLegacy *that = static_cast<PropagateUploadFileLegacy *>(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;
 +        pi._modtime =  Utility::qDateTimeFromTime_t(trans->modtime);
 +        that->_propagator->_journal->setUploadInfo(that->_item._file, pi);
 +        that->_propagator->_journal->commit("Upload info");
 +    }
 +}
 +
 +void PropagateUploadFileLegacy::notify_status_cb(void* userdata, ne_session_status status,
 +                                           const ne_session_status_info* info)
 +{
 +    PropagateUploadFileLegacy* that = reinterpret_cast<PropagateUploadFileLegacy*>(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 );
 +
 +        that->limitBandwidth(that->_chunked_done + info->sr.progress,  that->_propagator->_uploadLimit.fetchAndAddAcquire(0));
 +    }
 +}
 +
 +
 +
 +static QString parseFileId(ne_request *req) {
 +    QString fileId;
 +
 +    const char *header = ne_get_response_header(req, "OC-FileId");
 +    if( header ) {
 +        fileId = QString::fromUtf8(header);
 +    }
 +    return fileId;
 +}
 +
 +bool PropagateNeonJob::updateMTimeAndETag(const char* uri, time_t mtime)
 +{
 +    QByteArray modtime = QByteArray::number(qlonglong(mtime));
 +    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;
 +
 +    int rc = ne_proppatch( _propagator->_session, uri, ops );
 +    Q_UNUSED(rc);
 +    /* FIXME: error handling
 +     *    bool error = updateErrorFromSession( rc );
 +     *    if( error ) {
 +     *        // FIXME: We could not set the mtime. Error or not?
 +     *        qDebug() << "PROP-Patching of modified date failed.";
 +}*/
 +
 +    // get the etag
 +    QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_propagator->_session, "HEAD", uri));
 +    int neon_stat = ne_request_dispatch(req.data());
 +    if (updateErrorFromSession(neon_stat, req.data())) {
 +        return false;
 +    } else {
 +        _item._etag = parseEtag(ne_get_response_header(req.data(), "etag"));
 +        QString fid = parseFileId(req.data());
 +        if( _item._fileId.isEmpty() ) {
 +            _item._fileId = fid;
 +            qDebug() << "FileID was empty, set it to " << _item._fileId;
 +        } else {
 +            if( !fid.isEmpty() && fid != _item._fileId ) {
 +                qDebug() << "WARN: FileID seems to have changed: "<< fid << _item._fileId;
 +            } else {
 +                qDebug() << "FileID is " << _item._fileId;
 +            }
 +        }
 +        return true;
 +    }
 +}
 +
 +void PropagateNeonJob::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) {
 +                //qDebug() << "Limiting bandwidth to " << bandwidth_limit << "KB/s by waiting " << wait_time << " µs; ";
 +                Mirall::Utility::usleep(wait_time);
 +            }
 +        }
 +        _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) {
 +                Mirall::Utility::usleep(wait_time);
 +
 +            }
 +        }
 +        _lastTime.start();
 +    }
 +}
 +
 +int PropagateDownloadFileLegacy::content_reader(void *userdata, const char *buf, size_t len)
 +{
 +    PropagateDownloadFileLegacy *that = static_cast<PropagateDownloadFileLegacy *>(userdata);
 +    size_t written = 0;
 +
 +    if (that->_propagator->_abortRequested.fetchAndAddRelaxed(0)) {
 +        ne_set_error(that->_propagator->_session, "%s", tr("Sync was aborted by user.").toUtf8().data());
 +        return NE_ERROR;
 +    }
 +
 +    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;
 +            return NE_ERROR;
 +        }
 +        return NE_OK;
 +    }
 +
 +    return NE_ERROR;
 +}
 +
 +/*
 + * 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 PropagateDownloadFileLegacy::install_content_reader( ne_request *req, void *userdata, const ne_status *status )
 +{
 +    PropagateDownloadFileLegacy *that = static_cast<PropagateDownloadFileLegacy *>(userdata);
 +
 +    Q_UNUSED(status);
 +
 +    if( !that ) {
 +        qDebug("Error: install_content_reader called without valid write context!");
 +        return;
 +    }
 +
 +    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;
 +    }
 +
 +    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;
 +    }
 +
 +    QByteArray etag = parseEtag(ne_get_response_header(req, "etag"));
 +    if(etag.isEmpty())
 +        etag = parseEtag(ne_get_response_header(req, "ETag"));
 +
 +    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");
 +        ne_set_error(that->_propagator->_session, "%s", that->errorString.toUtf8().data());
 +        ne_add_response_body_reader( req, do_not_accept,
 +                                     do_not_download_content_reader,
 +                                     (void*) that );
 +        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.");
 +        ne_set_error(that->_propagator->_session, "%s", that->errorString.toUtf8().data());
 +        ne_add_response_body_reader( req, do_not_accept,
 +                                     do_not_download_content_reader,
 +                                     (void*) that );
 +        return;
 +    }
 +
 +
 +    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 );
 +
 +    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 );
 +    }
 +}
 +
 +void PropagateDownloadFileLegacy::notify_status_cb(void* userdata, ne_session_status status,
 +                                             const ne_session_status_info* info)
 +{
 +    PropagateDownloadFileLegacy* that = reinterpret_cast<PropagateDownloadFileLegacy*>(userdata);
 +    if (status == ne_status_recving && info->sr.total > 0) {
 +        emit that->progress(Progress::Context, that->_item, info->sr.progress, info->sr.total );
 +
 +        that->limitBandwidth(info->sr.progress,  that->_propagator->_downloadLimit.fetchAndAddAcquire(0));
 +    }
 +}
 +
 +void PropagateDownloadFileLegacy::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    emit progress(Progress::StartDownload, _item, 0, _item._size);
 +
 +    QString tmpFileName;
 +    const SyncJournalDb::DownloadInfo progressInfo = _propagator->_journal->getDownloadInfo(_item._file);
 +    if (progressInfo._valid) {
 +        // if the etag has changed meanwhile, remove the already downloaded part.
 +        if (progressInfo._etag != _item._etag) {
 +            QFile::remove(_propagator->_localDir + progressInfo._tmpfile);
 +            _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
 +        } else {
 +            tmpFileName = progressInfo._tmpfile;
 +            _expectedEtagForResume = progressInfo._etag;
 +        }
 +
 +    }
 +
 +    if (tmpFileName.isEmpty()) {
 +        tmpFileName = _item._file;
 +        //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);
 +    }
 +
 +    QFile tmpFile(_propagator->_localDir + tmpFileName);
 +    _file = &tmpFile;
 +    if (!tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
 +        done(SyncFileItem::NormalError, tmpFile.errorString());
 +        return;
 +    }
 +
 +    FileSystem::setFileHidden(tmpFile.fileName(), true);
 +
 +    {
 +        SyncJournalDb::DownloadInfo pi;
 +        pi._etag = _item._etag;
 +        pi._tmpfile = tmpFileName;
 +        pi._valid = true;
 +        _propagator->_journal->setDownloadInfo(_item._file, pi);
 +        _propagator->_journal->commit("download file start");
 +    }
 +
 +    /* actually do the request */
 +    int retry = 0;
 +
 +    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 +        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 +
 +    do {
 +        QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_propagator->_session, "GET", uri.data()));
 +
 +        /* Allow compressed content by setting the header */
 +        ne_add_request_header( req.data(), "Accept-Encoding", "gzip" );
 +
 +        if (tmpFile.size() > 0) {
 +            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());
 +            ne_add_request_header(req.data(), "Accept-Ranges", "bytes");
 +            qDebug() << "Retry with range " << rangeRequest;
 +        }
 +
 +        /* hook called before the content is parsed to set the correct reader,
 +         * either the compressed- or uncompressed reader.
 +         */
 +        ne_hook_post_headers( _propagator->_session, install_content_reader, this);
 +        ne_set_notifier(_propagator->_session, notify_status_cb, this);
 +        _lastProgress = 0;
 +        _lastTime.start();
 +
 +        int neon_stat = ne_request_dispatch(req.data());
 +
 +        _decompress.reset(); // Destroy the decompress after the request has been dispatched.
 +
 +        /* 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);
 +
 +        if (neon_stat == NE_TIMEOUT && (++retry) < 3) {
 +            continue;
 +        }
 +
 +        // This one is set by install_content_reader if e.g. there is no E-Tag
 +        if (!errorString.isEmpty()) {
 +            // 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());
 +            done(SyncFileItem::SoftError, errorString);
 +            return;
 +        }
 +
 +        // This one is set by neon
 +        if( updateErrorFromSession(neon_stat, req.data() ) ) {
 +            qDebug("Error GET: Neon: %d", neon_stat);
 +            if (tmpFile.size() == 0) {
 +                // don't keep the temporary file if it is empty.
 +                tmpFile.close();
 +                tmpFile.remove();
 +                _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
 +            }
 +            return;
 +        }
 +        _item._etag = parseEtag(ne_get_response_header(req.data(), "etag"));
 +        break;
 +    } while (1);
 +
 +    tmpFile.close();
 +    tmpFile.flush();
 +    QString fn = _propagator->_localDir + _item._file;
 +
 +
 +    bool isConflict = _item._instruction == CSYNC_INSTRUCTION_CONFLICT
 +        && !FileSystem::fileEquals(fn, tmpFile.fileName()); // compare the files to see if there was an actual conflict.
 +    //In case of conflict, make a backup of the old file
 +    if (isConflict) {
 +        QFile f(fn);
 +        QString conflictFileName(fn);
 +        // Add _conflict-XXXX  before the extention.
 +        int dotLocation = conflictFileName.lastIndexOf('.');
 +        // If no extention, add it at the end  (take care of cases like foo/.hidden or foo.bar/file)
 +        if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) {
 +            dotLocation = conflictFileName.size();
 +        }
 +        QString timeString = Utility::qDateTimeFromTime_t(_item._modtime).toString("yyyyMMdd-hhmmss");
 +        conflictFileName.insert(dotLocation, "_conflict-" + timeString);
 +        if (!f.rename(conflictFileName)) {
 +            //If the rename fails, don't replace it.
 +            done(SyncFileItem::NormalError, f.errorString());
 +            return;
 +        }
 +    }
 +
 +    QFileInfo existingFile(fn);
 +    if(existingFile.exists() && existingFile.permissions() != tmpFile.permissions()) {
 +        tmpFile.setPermissions(existingFile.permissions());
 +    }
 +
 +    FileSystem::setFileHidden(tmpFile.fileName(), false);
 +
 +    QString error;
 +    if (!FileSystem::renameReplace(tmpFile.fileName(), fn, &error)) {
 +        done(SyncFileItem::NormalError, error);
 +        return;
 +    }
 +
 +    FileSystem::setModTime(fn, _item._modtime);
 +
 +    _propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, fn));
 +    _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
 +    _propagator->_journal->commit("download file start2");
 +    emit progress(Progress::EndDownload, _item, _item._size, _item._size);
 +    done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success);
 +}
 +
 +
 +}
diff --cc src/mirall/propagatorjobs.cpp
index 035ee91,0000000..2102505
mode 100644,000000..100644
--- a/src/mirall/propagatorjobs.cpp
+++ b/src/mirall/propagatorjobs.cpp
@@@ -1,360 -1,0 +1,313 @@@
 +/*
 + * Copyright (C) by Olivier Goffart <ogoffart at owncloud.com>
 + * Copyright (C) by Klaas Freitag <freitag at 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 "propagatorjobs.h"
 +#include "owncloudpropagator_p.h"
 +#include "propagator_legacy.h"
 +
 +#include "utility.h"
 +#include "syncjournaldb.h"
 +#include "syncjournalfilerecord.h"
 +#include <httpbf.h>
 +#include <qfile.h>
 +#include <qdir.h>
 +#include <qdiriterator.h>
 +#include <qtemporaryfile.h>
 +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
 +#include <qabstractfileengine.h>
 +#else
 +#include <qsavefile.h>
 +#endif
 +#include <QDebug>
 +#include <QDateTime>
 +#include <qstack.h>
 +#include <QCoreApplication>
 +
 +#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>
 +
 +#ifdef Q_OS_WIN
 +#include <windef.h>
 +#include <winbase.h>
 +#endif
 +
 +#include <time.h>
 +
 +
 +namespace Mirall {
 +
- /**
-  * 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.
-  */
- bool PropagateNeonJob::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 )) {
-         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 PropagateDownloadFileLegacy(_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);
-         }
-         connect(_restoreJob.data(), SIGNAL(completed(SyncFileItem)),
-                 this, SLOT(slotRestoreJobCompleted(SyncFileItem)));
-         QMetaObject::invokeMethod(_restoreJob.data(), "start");
-         return true;
-     }
-     return false;
- }
- 
- void PropagateNeonJob::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) );
-     }
- }
- 
 +// 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;
 +}
 +
 +void PropagateLocalRemove::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    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;
 +        }
 +    } else {
 +        QFile file(filename);
 +        if (file.exists() && !file.remove()) {
 +            done(SyncFileItem::NormalError, file.errorString());
 +            return;
 +        }
 +    }
 +    emit progress(Progress::StartDelete, _item, 0, _item._size);
 +    _propagator->_journal->deleteFileRecord(_item._originalFile, _item._isDirectory);
 +    _propagator->_journal->commit("Local remove");
 +    done(SyncFileItem::Success);
 +    emit progress(Progress::EndDelete, _item, _item._size, _item._size);
 +}
 +
 +void PropagateLocalMkdir::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    QDir d;
 +    if (!d.mkpath(_propagator->_localDir +  _item._file)) {
 +        done(SyncFileItem::NormalError, tr("could not create directory %1").arg(_propagator->_localDir +  _item._file));
 +        return;
 +    }
 +    done(SyncFileItem::Success);
 +}
 +
 +void PropagateRemoteRemove::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 +        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 +    emit progress(Progress::StartDelete, _item, 0, _item._size);
 +    qDebug() << "** DELETE " << uri.data();
 +    int rc = ne_delete(_propagator->_session, uri.data());
 +
-     if( checkForProblemsWithShared() ) {
++    if( checkForProblemsWithShared(tr("The file has been removed from a read only share. It was restored.")) ) {
 +        return;
 +    }
 +
 +    /* Ignore the error 404,  it means it is already deleted */
 +    if (updateErrorFromSession(rc, 0, 404)) {
 +        return;
 +    }
 +
 +    _propagator->_journal->deleteFileRecord(_item._originalFile, _item._isDirectory);
 +    _propagator->_journal->commit("Remote Remove");
 +    done(SyncFileItem::Success);
 +    emit progress(Progress::EndDelete, _item, _item._size, _item._size);
 +}
 +
 +void PropagateRemoteMkdir::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    QScopedPointer<char, QScopedPointerPodDeleter> uri(
 +        ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 +
 +    int rc = ne_mkcol(_propagator->_session, uri.data());
 +
 +    /* Special for mkcol: it returns 405 if the directory already exists.
 +     * Ignore that error */
 +    if( updateErrorFromSession( rc , 0, 405 ) ) {
 +        return;
 +    }
 +    done(SyncFileItem::Success);
 +}
 +
 +
 +void PropagateLocalRename::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    // 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.
 +    if (_item._file != _item._renameTarget) {
 +        emit progress(Progress::StartRename, _item, 0, _item._size);
 +        qDebug() << "MOVE " << _propagator->_localDir + _item._file << " => " << _propagator->_localDir + _item._renameTarget;
 +        QFile::rename(_propagator->_localDir + _item._file, _propagator->_localDir + _item._renameTarget);
 +        emit progress(Progress::EndRename, _item, _item._size, _item._size);
 +    }
 +
 +    _item._instruction = CSYNC_INSTRUCTION_DELETED;
 +    _propagator->_journal->deleteFileRecord(_item._originalFile);
 +
 +    // store the rename file name in the item.
 +    _item._file = _item._renameTarget;
 +
 +    SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget);
 +    record._path = _item._renameTarget;
 +
 +    if (!_item._isDirectory) { // Directory are saved at the end
 +        _propagator->_journal->setFileRecord(record);
 +    }
 +    _propagator->_journal->commit("localRename");
 +
 +
 +    done(SyncFileItem::Success);
 +}
 +
 +void PropagateRemoteRename::start()
 +{
 +    if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
 +        return;
 +
 +    if (_item._file == _item._renameTarget) {
 +        if (!_item._isDirectory) {
 +            // The parents has been renamed already so there is nothing more to do.
 +            // But we still need to fetch the new ETAG
 +            // FIXME   maybe do a recusrsive propfind after having moved the parent.
 +            // Note: we also update the mtime because the server do not keep the mtime when moving files
 +            QScopedPointer<char, QScopedPointerPodDeleter> uri2(
 +                ne_path_escape((_propagator->_remoteDir + _item._renameTarget).toUtf8()));
 +            if (!updateMTimeAndETag(uri2.data(), _item._modtime))
 +                return;
 +        }
 +    } 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."));
 +        } else {
 +            done(SyncFileItem::NormalError, tr("This folder must not be renamed. Please name it back to Shared."));
 +        }
 +        return;
 +    } else {
 +        emit progress(Progress::StartRename, _item, 0, _item._size);
 +
 +        QScopedPointer<char, QScopedPointerPodDeleter> uri1(ne_path_escape((_propagator->_remoteDir + _item._file).toUtf8()));
 +        QScopedPointer<char, QScopedPointerPodDeleter> uri2(ne_path_escape((_propagator->_remoteDir + _item._renameTarget).toUtf8()));
 +        qDebug() << "MOVE on Server: " << uri1.data() << "->" << uri2.data();
 +
 +        int rc = ne_move(_propagator->_session, 1, uri1.data(), uri2.data());
 +
-         if( checkForProblemsWithShared()) {
++        if( checkForProblemsWithShared(tr("The file was renamed but is part of a read only share. The original file was restored."))) {
 +            return;
 +        }
 +
 +        if (updateErrorFromSession(rc)) {
 +            return;
 +        }
 +
 +        if (!updateMTimeAndETag(uri2.data(), _item._modtime))
 +            return;
 +        emit progress(Progress::EndRename, _item, _item._size, _item._size);
 +    }
 +
 +    _propagator->_journal->deleteFileRecord(_item._originalFile);
 +    SyncJournalFileRecord record(_item, _propagator->_localDir + _item._renameTarget);
 +    record._path = _item._renameTarget;
 +
 +    _propagator->_journal->setFileRecord(record);
 +    _propagator->_journal->commit("Remote Rename");
 +    done(SyncFileItem::Success);
 +}
 +
 +bool PropagateNeonJob::updateErrorFromSession(int neon_code, ne_request* req, int ignoreHttpCode)
 +{
 +    if( neon_code != NE_OK ) {
 +        qDebug("Neon error code was %d", neon_code);
 +    }
 +
 +    QString errorString;
 +    int httpStatusCode = 0;
 +
 +    switch(neon_code) {
 +        case NE_OK:     /* Success, but still the possiblity of problems */
 +            if( req ) {
 +                const ne_status *status = ne_get_status(req);
 +
 +                if (status) {
 +                    if ( status->klass == 2 || status->code == ignoreHttpCode) {
 +                        // Everything is ok, no error.
 +                        return false;
 +                    }
 +                    errorString = QString::fromUtf8( status->reason_phrase );
 +                    httpStatusCode = status->code;
 +                    _item._httpErrorCode = httpStatusCode;
 +                }
 +            } else {
 +                errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
 +                httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
 +                _item._httpErrorCode = httpStatusCode;
 +                if ((httpStatusCode >= 200 && httpStatusCode < 300)
 +                    || (httpStatusCode != 0 && httpStatusCode == ignoreHttpCode)) {
 +                    // No error
 +                    return false;
 +                    }
 +            }
 +            // FIXME: classify the error
 +            done (SyncFileItem::NormalError, errorString);
 +            return true;
 +        case NE_ERROR:  /* Generic error; use ne_get_error(session) for message */
 +            errorString = QString::fromUtf8(ne_get_error(_propagator->_session));
 +            // Check if we don't need to ignore that error.
 +            httpStatusCode = errorString.mid(0, errorString.indexOf(QChar(' '))).toInt();
 +            _item._httpErrorCode = httpStatusCode;
 +            qDebug() << Q_FUNC_INFO << "NE_ERROR" << errorString << httpStatusCode << ignoreHttpCode;
 +            if (ignoreHttpCode && httpStatusCode == ignoreHttpCode)
 +                return false;
 +
 +            done(SyncFileItem::NormalError, errorString);
 +            return true;
 +        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 */
 +            done(SyncFileItem::FatalError, QString::fromUtf8(ne_get_error(_propagator->_session)));
 +            return true;
 +        case NE_FAILED:   /* The precondition failed */
 +        case NE_RETRY:    /* Retry request (ne_end_request ONLY) */
 +        case NE_REDIRECT: /* See ne_redirect.h */
 +        default:
 +            done(SyncFileItem::SoftError, QString::fromUtf8(ne_get_error(_propagator->_session)));
 +            return true;
 +    }
 +    return false;
 +}
 +
 +
 +}
diff --cc src/mirall/propagatorjobs.h
index 3be7aa4,0000000..6505206
mode 100644,000000..100644
--- a/src/mirall/propagatorjobs.h
+++ b/src/mirall/propagatorjobs.h
@@@ -1,119 -1,0 +1,111 @@@
 +/*
 + * Copyright (C) by Olivier Goffart <ogoffart at owncloud.com>
 + * Copyright (C) by Klaas Freitag <freitag at 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.
 + */
 +
 +#pragma once
 +
 +#include "owncloudpropagator.h"
 +#include <httpbf.h>
 +#include <neon/ne_compress.h>
 +#include <QFile>
 +#include <qdebug.h>
 +
 +namespace Mirall {
 +
 +/* Helper for QScopedPointer<>, to be used as the deleter.
 + * QScopePointer will call the right overload of cleanup for the pointer it holds
 + */
 +struct ScopedPointerHelpers {
 +    static inline void cleanup(hbf_transfer_t *pointer) { if (pointer) hbf_free_transfer(pointer); }
 +    static inline void cleanup(ne_request *pointer) { if (pointer) ne_request_destroy(pointer); }
 +    static inline void cleanup(ne_decompress *pointer) { if (pointer) ne_decompress_destroy(pointer); }
 +//     static inline void cleanup(ne_propfind_handler *pointer) { if (pointer) ne_propfind_destroy(pointer); }
 +};
 +
 +/*
 + * Abstract class for neon job.  Lives in the neon thread
 + */
 +class PropagateNeonJob : public PropagateItemJob {
 +    Q_OBJECT
 +protected:
 +
 +    /* Issue a PROPPATCH and PROPFIND to update the mtime, and fetch the etag
 +     * Return true in case of success, and false if the PROPFIND failed and the
 +     * error has been reported
 +     */
 +    bool updateMTimeAndETag(const char *uri, time_t);
 +
 +    /* fetch the error code and string from the session
 +       in case of error, calls done with the error and returns true.
 +
 +       If the HTTP error code is ignoreHTTPError,  the error is ignored
 +     */
 +    bool updateErrorFromSession(int neon_code = 0, ne_request *req = 0, int ignoreHTTPError = 0);
 +
 +    /*
 +     * to be called by the progress callback and will wait the amount of time needed.
 +     */
 +    void limitBandwidth(qint64 progress, qint64 limit);
 +
-     bool checkForProblemsWithShared();
- 
 +    QElapsedTimer _lastTime;
 +    qint64        _lastProgress;
 +    int           _httpStatusCode;
 +
- protected slots:
-     void slotRestoreJobCompleted(const SyncFileItem& );
- 
- private:
-     QScopedPointer<PropagateItemJob> _restoreJob;
- 
 +public:
 +    PropagateNeonJob(OwncloudPropagator* propagator, const SyncFileItem &item)
 +        : PropagateItemJob(propagator, item), _lastProgress(0), _httpStatusCode(0) {
 +            moveToThread(propagator->_neonThread);
 +        }
 +
 +};
 +
 +class PropagateLocalRemove : public PropagateItemJob {
 +    Q_OBJECT
 +public:
 +    PropagateLocalRemove (OwncloudPropagator* propagator,const SyncFileItem& item)  : PropagateItemJob(propagator, item) {}
 +    void start();
 +};
 +class PropagateLocalMkdir : public PropagateItemJob {
 +    Q_OBJECT
 +public:
 +    PropagateLocalMkdir (OwncloudPropagator* propagator,const SyncFileItem& item)  : PropagateItemJob(propagator, item) {}
 +    void start();
 +};
 +class PropagateRemoteRemove : public PropagateNeonJob {
 +    Q_OBJECT
 +public:
 +    PropagateRemoteRemove (OwncloudPropagator* propagator,const SyncFileItem& item)  : PropagateNeonJob(propagator, item) {}
 +    void start();
 +};
 +class PropagateRemoteMkdir : public PropagateNeonJob {
 +    Q_OBJECT
 +public:
 +    PropagateRemoteMkdir (OwncloudPropagator* propagator,const SyncFileItem& item)  : PropagateNeonJob(propagator, item) {}
 +    void start();
 +};
 +class PropagateLocalRename : public PropagateItemJob {
 +    Q_OBJECT
 +public:
 +    PropagateLocalRename (OwncloudPropagator* propagator,const SyncFileItem& item)  : PropagateItemJob(propagator, item) {}
 +    void start();
 +};
 +class PropagateRemoteRename : public PropagateNeonJob {
 +    Q_OBJECT
 +public:
 +    PropagateRemoteRename (OwncloudPropagator* propagator,const SyncFileItem& item)  : PropagateNeonJob(propagator, item) {}
 +    void start();
 +};
 +
 +
 +}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud-client.git



More information about the Pkg-owncloud-commits mailing list