"Fossies" - the Fresh Open Source Software Archive

Member "seafile-client-7.0.4/src/filebrowser/tasks.cpp" (19 Nov 2019, 25472 Bytes) of package /linux/www/seafile-client-7.0.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "tasks.cpp" see the Fossies "Dox" file reference documentation.

    1 #include <QThread>
    2 #include <QNetworkAccessManager>
    3 #include <QNetworkRequest>
    4 #include <QNetworkReply>
    5 #include <QHttpMultiPart>
    6 #include <QHttpPart>
    7 #include <QFile>
    8 #include <QFileInfo>
    9 #include <QTemporaryFile>
   10 #include <QSslError>
   11 #include <QSslConfiguration>
   12 #include <QSslCertificate>
   13 #include <QDirIterator>
   14 #include <QTimer>
   15 #include <QApplication>
   16 #include <QMutexLocker>
   17 #include <QNetworkConfigurationManager>
   18 
   19 #include "utils/utils.h"
   20 #include "utils/file-utils.h"
   21 #include "seafile-applet.h"
   22 #include "certs-mgr.h"
   23 #include "api/api-error.h"
   24 #include "configurator.h"
   25 #include "network-mgr.h"
   26 #include "reliable-upload.h"
   27 #include "tasks.h"
   28 
   29 namespace {
   30 
   31 const char *kFileDownloadTmpDirName = "fcachetmp";
   32 
   33 const int kMaxRedirects = 3;
   34 const int kFileServerTaskMaxRetry = 3;
   35 const int kFileServerTaskRetryIntervalSecs = 10;
   36 
   37 class QNAMWrapper {
   38 public:
   39     QNAMWrapper() {
   40         should_reset_qnam_ = false;
   41         network_mgr_ = nullptr;
   42     }
   43 
   44     QNetworkAccessManager *getQNAM() {
   45         QMutexLocker lock(&network_mgr_lock_);
   46 
   47         if (!network_mgr_) {
   48             network_mgr_ = createQNAM();
   49         } else if (should_reset_qnam_) {
   50             network_mgr_->deleteLater();
   51             network_mgr_ = createQNAM();
   52             should_reset_qnam_ = false;
   53         }
   54         return network_mgr_;
   55     }
   56 
   57     QNetworkAccessManager *createQNAM() {
   58         QNetworkAccessManager *manager = new QNetworkAccessManager;
   59         NetworkManager::instance()->addWatch(manager);
   60         manager->setConfiguration(
   61             QNetworkConfigurationManager().defaultConfiguration());
   62         return manager;
   63     }
   64 
   65     void resetQNAM() {
   66         QMutexLocker lock(&network_mgr_lock_);
   67         should_reset_qnam_ = true;
   68     }
   69 
   70 private:
   71     QNetworkAccessManager* network_mgr_;
   72     QMutex network_mgr_lock_;
   73     bool should_reset_qnam_;
   74 };
   75 
   76 QNAMWrapper* qnam_wrapper_ = new QNAMWrapper();
   77 
   78 } // namesapce
   79 
   80 QThread* FileNetworkTask::worker_thread_;
   81 
   82 FileNetworkTask::FileNetworkTask(const Account& account,
   83                                  const QString& repo_id,
   84                                  const QString& path,
   85                                  const QString& local_path)
   86     : account_(account),
   87       repo_id_(repo_id),
   88       path_(path),
   89       local_path_(local_path),
   90       canceled_(false),
   91       progress_(0, 0)
   92 {
   93     fileserver_task_ = NULL;
   94     get_link_req_ = NULL;
   95     http_error_code_ = 0;
   96     auto_delete_ = true;
   97 }
   98 
   99 FileNetworkTask::~FileNetworkTask()
  100 {
  101     // printf ("destructor called for FileNetworkTask\n");
  102     if (get_link_req_) {
  103         get_link_req_->deleteLater();
  104         get_link_req_ = nullptr;
  105     }
  106     if (fileserver_task_) {
  107         fileserver_task_->deleteLater();
  108         fileserver_task_ = nullptr;
  109     }
  110 }
  111 
  112 QString FileNetworkTask::fileName() const
  113 {
  114     return QFileInfo(path_).fileName();
  115 }
  116 
  117 void FileNetworkTask::start()
  118 {
  119     createGetLinkRequest();
  120     connect(get_link_req_, SIGNAL(success(const QString&)),
  121             this, SLOT(onLinkGet(const QString&)));
  122     connect(get_link_req_, SIGNAL(failed(const ApiError&)),
  123             this, SLOT(onGetLinkFailed(const ApiError&)));
  124     get_link_req_->send();
  125 }
  126 
  127 void FileNetworkTask::onFileServerTaskProgressUpdate(qint64 transferred, qint64 total)
  128 {
  129     progress_.transferred = transferred;
  130     progress_.total = total;
  131     emit progressUpdate(transferred, total);
  132 }
  133 
  134 void FileNetworkTask::onFileServerTaskNameUpdate(QString current_name)
  135 {
  136     emit nameUpdate(current_name);
  137 }
  138 
  139 void FileNetworkTask::onLinkGet(const QString& link)
  140 {
  141     startFileServerTask(link);
  142 }
  143 
  144 void FileNetworkTask::startFileServerTask(const QString& link)
  145 {
  146     createFileServerTask(link);
  147 
  148     connect(fileserver_task_, SIGNAL(progressUpdate(qint64, qint64)),
  149             this, SLOT(onFileServerTaskProgressUpdate(qint64, qint64)));
  150     connect(fileserver_task_, SIGNAL(nameUpdate(QString)),
  151             this, SLOT(onFileServerTaskNameUpdate(QString)));
  152     connect(fileserver_task_, SIGNAL(finished(bool)),
  153             this, SLOT(onFileServerTaskFinished(bool)));
  154     connect(fileserver_task_, SIGNAL(retried(int)),
  155             this, SIGNAL(retried(int)));
  156 
  157     if (!worker_thread_) {
  158         worker_thread_ = new QThread;
  159         worker_thread_->start();
  160     }
  161 
  162     if (type() == Download) {
  163         // From now on the this task would run in the worker thread
  164         fileserver_task_->moveToThread(worker_thread_);
  165         QMetaObject::invokeMethod(fileserver_task_, "start");
  166     } else {
  167         // ReliablePostFileTask is a bit complicated and it would manage the
  168         // thread-affinity itself.
  169         fileserver_task_->start();
  170     }
  171 }
  172 
  173 void FileNetworkTask::cancel()
  174 {
  175     canceled_ = true;
  176     if (get_link_req_) {
  177         get_link_req_->deleteLater();
  178         get_link_req_ = 0;
  179     }
  180     if (fileserver_task_) {
  181         QMetaObject::invokeMethod(fileserver_task_, "cancel");
  182     }
  183     error_ = TaskCanceled;
  184     error_string_ = tr("Operation canceled");
  185     onFinished(false);
  186 }
  187 
  188 void FileNetworkTask::onFileServerTaskFinished(bool success)
  189 {
  190     if (canceled_) {
  191         return;
  192     }
  193     if (!fileserver_task_->canceled() && !success) {
  194         error_ = fileserver_task_->error();
  195         error_string_ = fileserver_task_->errorString();
  196         http_error_code_ = fileserver_task_->httpErrorCode();
  197         failed_path_ = fileserver_task_->failedPath();
  198     }
  199     oid_ = fileserver_task_->oid();
  200     url_ = fileserver_task_->url();
  201     onFinished(success);
  202 }
  203 
  204 void FileNetworkTask::onFinished(bool success)
  205 {
  206     emit finished(success);
  207     if (auto_delete_) {
  208         deleteLater();
  209     }
  210 }
  211 
  212 void FileNetworkTask::onGetLinkFailed(const ApiError& error)
  213 {
  214     error_ = ApiRequestError;
  215     error_string_ = error.toString();
  216     if (error.type() == ApiError::HTTP_ERROR) {
  217         http_error_code_ = error.httpErrorCode();
  218     }
  219     onFinished(false);
  220 }
  221 
  222 FileNetworkTask::Progress::Progress(qint64 transferred, qint64 total)
  223 {
  224     this->transferred = transferred;
  225     this->total = total;
  226 }
  227 
  228 QString FileNetworkTask::Progress::toString() const
  229 {
  230     if (total > 0) {
  231         return QString::number(100 * transferred / total) + "%";
  232     }
  233     return tr("pending");
  234 }
  235 
  236 FileDownloadTask::FileDownloadTask(const Account& account,
  237                                    const QString& repo_id,
  238                                    const QString& path,
  239                                    const QString& local_path,
  240                                    bool is_save_as_task)
  241     : FileNetworkTask(account, repo_id, path, local_path), is_save_as_task_(is_save_as_task)
  242 {
  243 }
  244 
  245 void FileDownloadTask::createGetLinkRequest()
  246 {
  247     if (get_link_req_) {
  248         get_link_req_->deleteLater();
  249     }
  250     get_link_req_ = new GetFileDownloadLinkRequest(account_, repo_id_, path_);
  251 }
  252 
  253 void FileDownloadTask::onLinkGet(const QString& link)
  254 {
  255     GetFileDownloadLinkRequest *req = (GetFileDownloadLinkRequest *)get_link_req_;
  256     file_id_ = req->fileId();
  257     FileNetworkTask::onLinkGet(link);
  258 }
  259 
  260 void FileDownloadTask::createFileServerTask(const QString& link)
  261 {
  262     fileserver_task_ = new GetFileTask(link, local_path_);
  263 }
  264 
  265 FileUploadTask::FileUploadTask(const Account& account,
  266                                const QString& repo_id,
  267                                const QString& path,
  268                                const QString& local_path,
  269                                const QString& name,
  270                                const bool use_upload,
  271                                const bool accept_user_confirmation)
  272     : FileNetworkTask(account, repo_id, path, local_path),
  273       name_(name),
  274       use_upload_(use_upload),
  275       accept_user_confirmation_(accept_user_confirmation)
  276 {
  277 }
  278 
  279 FileUploadTask::FileUploadTask(const FileUploadTask& rhs)
  280     : FileNetworkTask(rhs.account_, rhs.repo_id_, rhs.path_, rhs.local_path_),
  281       name_(rhs.name_),
  282       use_upload_(rhs.use_upload_),
  283       accept_user_confirmation_(rhs.accept_user_confirmation_)
  284 {
  285 }
  286 
  287 void FileUploadTask::createGetLinkRequest()
  288 {
  289     get_link_req_ = new GetFileUploadLinkRequest(account_, repo_id_, path_, use_upload_);
  290 }
  291 
  292 void FileUploadTask::createFileServerTask(const QString& link)
  293 {
  294     fileserver_task_ = new ReliablePostFileTask(account_, repo_id_, link, path_, local_path_,
  295                                                 name_, use_upload_, accept_user_confirmation_);
  296 }
  297 
  298 void FileUploadTask::startFileServerTask(const QString& link)
  299 {
  300     FileNetworkTask::startFileServerTask(link);
  301     connect(fileserver_task_, SIGNAL(oneFileFailed(const QString&, bool)),
  302             this, SLOT(onOneFileFailed(const QString&, bool)));
  303 }
  304 
  305 void FileUploadTask::onOneFileFailed(const QString& filename, bool single_file)
  306 {
  307     emit oneFileFailed(filename, single_file);
  308 }
  309 
  310 void FileUploadTask::continueWithFailedFile(bool retry)
  311 {
  312     retry_ = retry;
  313     createGetLinkRequest();
  314     connect(get_link_req_, SIGNAL(success(const QString&)),
  315             this, SLOT(onLinkGetAgain(const QString&)));
  316     connect(get_link_req_, SIGNAL(failed(const ApiError&)),
  317             this, SLOT(onGetLinkFailed(const ApiError&)));
  318     get_link_req_->send();
  319 }
  320 
  321 void FileUploadTask::onLinkGetAgain(const QString& link)
  322 {
  323     fileserver_task_->continueWithFailedFile(retry_, link);
  324 }
  325 
  326 FileUploadMultipleTask::FileUploadMultipleTask(const Account& account,
  327                                                const QString& repo_id,
  328                                                const QString& path,
  329                                                const QString& local_path,
  330                                                const QStringList& names,
  331                                                bool use_upload)
  332   : FileUploadTask(account, repo_id, path, local_path, QString(), use_upload),
  333   names_(names)
  334 {
  335 }
  336 
  337 void FileUploadMultipleTask::createFileServerTask(const QString& link)
  338 {
  339     fileserver_task_ = new PostFilesTask(link, path_, local_path_, names_, false);
  340 }
  341 
  342 const QStringList& FileUploadMultipleTask::successfulNames()
  343 {
  344     return ((PostFilesTask *)fileserver_task_)->successfulNames();
  345 }
  346 
  347 
  348 FileUploadDirectoryTask::FileUploadDirectoryTask(const Account& account,
  349                                                  const QString& repo_id,
  350                                                  const QString& path,
  351                                                  const QString& local_path,
  352                                                  const QString& name)
  353   : FileUploadTask(account, repo_id, path, local_path, name)
  354 {
  355 }
  356 
  357 void FileUploadDirectoryTask::createFileServerTask(const QString& link)
  358 {
  359     QStringList names;
  360 
  361     if (local_path_ == "/")
  362         qWarning("attempt to upload the root directory, you should avoid it\n");
  363     QDir dir(local_path_);
  364 
  365     QDirIterator iterator(dir.absolutePath(), QDirIterator::Subdirectories);
  366     // XXX (lins05): Move these operations into a thread
  367     while (iterator.hasNext()) {
  368         iterator.next();
  369         QString file_path = iterator.filePath();
  370         QString relative_path = dir.relativeFilePath(file_path);
  371         QString base_name = ::getBaseName(file_path);
  372         if (base_name == "." || base_name == "..") {
  373             continue;
  374         }
  375         if (!iterator.fileInfo().isDir()) {
  376             names.push_back(relative_path);
  377         } else {
  378             if (account_.isAtLeastVersion(4, 4, 0)) {
  379                 // printf("a folder: %s\n", file_path.toUtf8().data());
  380                 if (QDir(file_path).entryList().length() == 2) {
  381                     // only contains . and .., so an empty folder
  382                     // printf("an empty folder: %s\n", file_path.toUtf8().data());
  383                     empty_subfolders_.append(::pathJoin(::getBaseName(local_path_), relative_path));
  384                 }
  385             }
  386         }
  387     }
  388 
  389     if (names.isEmpty() && empty_subfolders_.isEmpty()) {
  390         // The folder dragged into cloud file browser is an empty one. We use
  391         // the special name "." to represent it.
  392         empty_subfolders_.append(".");
  393     }
  394 
  395     // printf("total empty folders: %d for %s\n", empty_subfolders_.length(), dir.absolutePath().toUtf8().data());
  396     fileserver_task_ = new PostFilesTask(link, path_, dir.absolutePath(), names, true);
  397 }
  398 
  399 void FileUploadDirectoryTask::onFinished(bool success)
  400 {
  401     if (!success || (empty_subfolders_.empty())) {
  402         FileUploadTask::onFinished(success);
  403         return;
  404     }
  405 
  406     nextEmptyFolder();
  407 }
  408 
  409 void FileUploadDirectoryTask::nextEmptyFolder()
  410 {
  411     if (empty_subfolders_.isEmpty()) {
  412         FileUploadDirectoryTask::onFinished(true);
  413         return;
  414     }
  415 
  416     QString folder = empty_subfolders_.takeFirst();
  417 
  418     bool create_parents = true;
  419     if (folder == ".") {
  420         // This is the case of an empty top-level folder.
  421         create_parents = false;
  422         folder = ::getBaseName(local_path_);
  423     }
  424 
  425     create_dir_req_.reset(new CreateDirectoryRequest(
  426                                 account_, repo_id_, ::pathJoin(path_, folder), create_parents));
  427 
  428     connect(create_dir_req_.data(), SIGNAL(success(const QString&)),
  429             this, SLOT(nextEmptyFolder()));
  430     connect(create_dir_req_.data(), SIGNAL(failed(const ApiError&)),
  431             this, SLOT(onCreateDirFailed(const ApiError&)));
  432     create_dir_req_->send();
  433 }
  434 
  435 void FileUploadDirectoryTask::onCreateDirFailed(const ApiError &error)
  436 {
  437     error_ = ApiRequestError;
  438     error_string_ = error.toString();
  439     http_error_code_ = error.httpErrorCode();
  440     FileUploadDirectoryTask::onFinished(false);
  441 }
  442 
  443 FileServerTask::FileServerTask(const QUrl& url, const QString& local_path)
  444     : url_(url),
  445       local_path_(local_path),
  446       reply_(nullptr),
  447       canceled_(false),
  448       redirect_count_(0),
  449       retry_count_(0),
  450       http_error_code_(0)
  451 {
  452 }
  453 
  454 FileServerTask::~FileServerTask()
  455 {
  456 }
  457 
  458 void FileServerTask::resetQNAM()
  459 {
  460     qnam_wrapper_->resetQNAM();
  461 }
  462 
  463 QNetworkAccessManager *FileServerTask::getQNAM()
  464 {
  465     QNetworkAccessManager *qnam = qnam_wrapper_->getQNAM();
  466     connect(qnam, SIGNAL(destroyed(QObject *)), this, SLOT(doAbort()));
  467     return qnam;
  468 }
  469 
  470 void FileServerTask::doAbort()
  471 {
  472     if (reply_ && reply_->isRunning()) {
  473         qWarning("aborting FileServerTask %s on network error", toCStr(reply_->url().toString()));
  474         reply_->abort();
  475     }
  476 }
  477 
  478 void FileServerTask::onSslErrors(const QList<QSslError>& errors)
  479 {
  480     if (canceled_) {
  481         return;
  482     }
  483     QUrl url = reply_->url();
  484     QSslCertificate cert = reply_->sslConfiguration().peerCertificate();
  485     CertsManager *mgr = seafApplet->certsManager();
  486     if (!cert.isNull() && cert == mgr->getCertificate(url.toString())) {
  487         reply_->ignoreSslErrors();
  488         return;
  489     }
  490 }
  491 
  492 void FileServerTask::start()
  493 {
  494     prepare();
  495     sendRequest();
  496 }
  497 
  498 void FileServerTask::cancel()
  499 {
  500     canceled_ = true;
  501     if (reply_) {
  502         reply_->abort();
  503     }
  504 }
  505 
  506 bool FileServerTask::retryEnabled()
  507 {
  508     return false;
  509 }
  510 
  511 void FileServerTask::retry()
  512 {
  513     if (canceled_) {
  514         qWarning("[file server task] stop retrying because task is cancelled\n");
  515         return;
  516     }
  517     qDebug("[file server task] now retry the file server task for the %d time\n", retry_count_);
  518     emit retried(retry_count_);
  519     start();
  520 }
  521 
  522 bool FileServerTask::maybeRetry()
  523 {
  524     if (canceled_ || !retryEnabled()) {
  525         return false;
  526     }
  527     if (retry_count_ >= kFileServerTaskMaxRetry) {
  528         return false;
  529     } else {
  530         retry_count_++;
  531         qDebug("[file server task] schedule file server task retry for the %d time\n", retry_count_);
  532         QTimer::singleShot(kFileServerTaskRetryIntervalSecs * 1000, this, SLOT(retry()));
  533         return true;
  534     }
  535 }
  536 
  537 void FileServerTask::httpRequestFinished()
  538 {
  539     if (canceled_) {
  540         setError(FileNetworkTask::TaskCanceled, tr("task cancelled"));
  541         emit finished(false);
  542         return;
  543     }
  544 
  545     int code = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  546     if (code == 0 && reply_->error() != QNetworkReply::NoError) {
  547 
  548         NetworkStatusDetector::instance()->setNetworkFailure(reply_->error());
  549 
  550         qWarning("[file server task] network error: %s\n", toCStr(reply_->errorString()));
  551         if (!maybeRetry()) {
  552             setError(FileNetworkTask::ApiRequestError, reply_->errorString());
  553             emit finished(false);
  554             return;
  555         }
  556         return;
  557     }
  558 
  559     if (handleHttpRedirect()) {
  560         return;
  561     }
  562 
  563     if ((code / 100) == 4 || (code / 100) == 5) {
  564         qWarning("request failed for %s: status code %d\n",
  565                toCStr(reply_->url().toString()), code);
  566         // Response code 400 means the link may have already expired.
  567         if (code == 400 || !maybeRetry()) {
  568             setHttpError(code);
  569             emit finished(false);
  570             return;
  571         }
  572         return;
  573     }
  574 
  575     onHttpRequestFinished();
  576 }
  577 
  578 bool FileServerTask::handleHttpRedirect()
  579 {
  580     QVariant redirect_attr = reply_->attribute(QNetworkRequest::RedirectionTargetAttribute);
  581     if (redirect_attr.isNull()) {
  582         return false;
  583     }
  584 
  585     if (redirect_count_++ > kMaxRedirects) {
  586         // simply treat too many redirects as server side error
  587         setHttpError(500);
  588         emit finished(false);
  589         qWarning("too many redirects for %s\n",
  590                toCStr(reply_->url().toString()));
  591         return true;
  592     }
  593 
  594     url_ = redirect_attr.toUrl();
  595     if (url_.isRelative()) {
  596         url_ = reply_->url().resolved(url_);
  597     }
  598     qWarning("redirect to %s (from %s)\n", toCStr(url_.toString()),
  599            toCStr(reply_->url().toString()));
  600     reply_->deleteLater();
  601     sendRequest();
  602     return true;
  603 }
  604 
  605 
  606 GetFileTask::GetFileTask(const QUrl& url, const QString& local_path)
  607     : FileServerTask(url, local_path),
  608       tmp_file_(NULL)
  609 {
  610 }
  611 
  612 GetFileTask::~GetFileTask()
  613 {
  614     if (tmp_file_) {
  615         tmp_file_->remove();
  616         delete tmp_file_;
  617     }
  618 }
  619 
  620 void GetFileTask::prepare()
  621 {
  622     QString download_tmp_dir = ::pathJoin(
  623         seafApplet->configurator()->seafileDir(), kFileDownloadTmpDirName);
  624     if (!::createDirIfNotExists(download_tmp_dir)) {
  625         setError(FileNetworkTask::FileIOError, tr("Failed to create folders"));
  626         emit finished(false);
  627         return;
  628     }
  629 
  630     QString tmpf_path = ::pathJoin(download_tmp_dir, "seaf-XXXXXX");
  631     tmp_file_ = new QTemporaryFile(tmpf_path);
  632     tmp_file_->setAutoRemove(false);
  633     if (!tmp_file_->open()) {
  634         setError(FileNetworkTask::FileIOError, tr("Failed to create temporary files"));
  635         emit finished(false);
  636     }
  637 }
  638 
  639 void GetFileTask::sendRequest()
  640 {
  641     QNetworkRequest request(url_);
  642     reply_ = getQNAM()->get(request);
  643 
  644     connect(reply_, SIGNAL(sslErrors(const QList<QSslError>&)),
  645             this, SLOT(onSslErrors(const QList<QSslError>&)));
  646 
  647     connect(reply_, SIGNAL(readyRead()), this, SLOT(httpReadyRead()));
  648     connect(reply_, SIGNAL(downloadProgress(qint64, qint64)),
  649             this, SIGNAL(progressUpdate(qint64, qint64)));
  650     connect(reply_, SIGNAL(finished()), this, SLOT(httpRequestFinished()));
  651 }
  652 
  653 void GetFileTask::httpReadyRead()
  654 {
  655     if (canceled_) {
  656         return;
  657     }
  658     // TODO: read in blocks (e.g 64k) instead of readAll
  659     // TODO: check http status code
  660     QByteArray chunk = reply_->readAll();
  661     if (!chunk.isEmpty()) {
  662         if (tmp_file_->write(chunk) <= 0) {
  663             setError(FileNetworkTask::FileIOError, tr("Failed to create folders"));
  664             emit finished(false);
  665         }
  666     }
  667 }
  668 
  669 void GetFileTask::onHttpRequestFinished()
  670 {
  671     if (canceled_) {
  672         return;
  673     }
  674     tmp_file_->close();
  675 
  676     QString parent_dir = ::getParentPath(local_path_);
  677     if (!::createDirIfNotExists(parent_dir)) {
  678         setError(FileNetworkTask::FileIOError, tr("Failed to write file to disk"));
  679         emit finished(false);
  680     }
  681 
  682     QFile oldfile(local_path_);
  683     if (oldfile.exists() && !oldfile.remove()) {
  684         setError(FileNetworkTask::FileIOError, tr("Failed to remove the older version of the downloaded file"));
  685         emit finished(false);
  686         return;
  687     }
  688 
  689     if (!tmp_file_->rename(local_path_)) {
  690         setError(FileNetworkTask::FileIOError, tr("Failed to move file"));
  691         emit finished(false);
  692         return;
  693     }
  694 
  695     delete tmp_file_;
  696     tmp_file_ = 0;
  697     emit finished(true);
  698 }
  699 
  700 PostFilesTask::PostFilesTask(const QUrl& url,
  701                              const QString& parent_dir,
  702                              const QString& local_path,
  703                              const QStringList& names,
  704                              const bool use_relative)
  705     : FileServerTask(url, local_path),
  706       // work around with server
  707       parent_dir_(parent_dir.endsWith('/') ? parent_dir : parent_dir + "/"),
  708       name_(QFileInfo(local_path_).fileName()),
  709       names_(names),
  710       current_num_(-1),
  711       progress_update_timer_(new QTimer(this)),
  712       use_relative_(use_relative)
  713 {
  714     // never used, set it to NULL to avoid segment fault
  715     reply_ = NULL;
  716     connect(progress_update_timer_, SIGNAL(timeout()), this, SLOT(onProgressUpdate()));
  717 }
  718 
  719 PostFilesTask::~PostFilesTask()
  720 {
  721 }
  722 
  723 void PostFilesTask::prepare()
  724 {
  725     current_bytes_ = 0;
  726     transferred_bytes_ = 0;
  727     total_bytes_ = 0;
  728 
  729     file_sizes_.reserve(names_.size());
  730     Q_FOREACH(const QString &name, names_)
  731     {
  732         QString local_path = ::pathJoin(local_path_, name);
  733         // approximate the bytes used by http protocol (e.g. the bytes of
  734         // header)
  735         qint64 file_size = QFileInfo(local_path).size() + 1024;
  736         file_sizes_.push_back(file_size);
  737         total_bytes_ += file_size;
  738     }
  739 }
  740 
  741 void PostFilesTask::cancel()
  742 {
  743     if (canceled_) {
  744         return;
  745     }
  746     progress_update_timer_->stop();
  747     canceled_ = true;
  748     task_->cancel();
  749 }
  750 
  751 void PostFilesTask::sendRequest()
  752 {
  753     startNext();
  754 }
  755 
  756 void PostFilesTask::onProgressUpdate()
  757 {
  758     emit progressUpdate(current_bytes_ + transferred_bytes_, total_bytes_);
  759 }
  760 
  761 void PostFilesTask::onPostTaskProgressUpdate(qint64 bytes, qint64 /* sum_bytes */)
  762 {
  763     current_bytes_ = bytes;
  764 }
  765 
  766 void PostFilesTask::onPostTaskFinished(bool success)
  767 {
  768     if (canceled_) {
  769         return;
  770     }
  771 
  772     if (!success) {
  773         error_ = task_->error();
  774         error_string_ = task_->errorString();
  775         http_error_code_ = task_->httpErrorCode();
  776         progress_update_timer_->stop();
  777 
  778         const QString& file_path = names_[current_num_];
  779         QString file_name = QFileInfo(file_path).fileName();
  780         failed_path_ = file_name;
  781 
  782         emit oneFileFailed(failed_path_, false);
  783         return;
  784     } else {
  785         error_ = FileNetworkTask::NoError;
  786         error_string_ = "";
  787         http_error_code_ = 0;
  788         progress_update_timer_->stop();
  789         successful_names_ << names_[current_num_];
  790     }
  791 
  792     transferred_bytes_ += file_sizes_[current_num_];
  793     startNext();
  794 }
  795 
  796 void PostFilesTask::startNext(const QString& link)
  797 {
  798     progress_update_timer_->stop();
  799     if (++current_num_ == names_.size()) {
  800         emit finished(true);
  801         return;
  802     }
  803     const QString& file_path = names_[current_num_];
  804     QString file_name = QFileInfo(file_path).fileName();
  805     current_name_ = file_name;
  806     emit nameUpdate(current_name_);
  807     QString relative_path;
  808     if (use_relative_)
  809         relative_path = ::pathJoin(QFileInfo(local_path_).fileName(), ::getParentPath(file_path));
  810 
  811     // relative_path might be empty, and should be safe to use as well
  812     if (!link.isEmpty()) {
  813         url_ = link;
  814     }
  815     task_.reset(new ReliablePostFileTask(url_,
  816         parent_dir_,
  817         ::pathJoin(local_path_, file_path),
  818         file_name,
  819         relative_path));
  820     connect(task_.data(), SIGNAL(progressUpdate(qint64, qint64)),
  821             this, SLOT(onPostTaskProgressUpdate(qint64, qint64)));
  822     connect(task_.data(), SIGNAL(finished(bool)),
  823             this, SLOT(onPostTaskFinished(bool)));
  824     current_bytes_ = 0;
  825     progress_update_timer_->start(100);
  826     task_->start();
  827 }
  828 
  829 void PostFilesTask::continueWithFailedFile(bool retry, const QString& link)
  830 {
  831     if (retry) {
  832         current_num_--;
  833     } else {
  834         // The user chooses to skip the failed file, but in order to keep the
  835         // progress consistent, we need to pretend the file is already uploaded
  836         // successfully.
  837         transferred_bytes_ += file_sizes_[current_num_];
  838     }
  839     startNext(link);
  840 }
  841 
  842 void FileServerTask::setError(FileNetworkTask::TaskError error,
  843                               const QString& error_string)
  844 {
  845     qWarning("[file server task] error: %s\n", toCStr(error_string));
  846     error_ = error;
  847     error_string_ = error_string;
  848 }
  849 
  850 void FileServerTask::setHttpError(int code)
  851 {
  852     error_ = FileNetworkTask::ApiRequestError;
  853     http_error_code_ = code;
  854     if (code == 500) {
  855         error_string_ = tr("Internal Server Error");
  856     } else if (code == 443 || code == 520) {
  857         // Handle the case when the storage quote is exceeded. It may happen in two cases:
  858         //
  859         // First, the quota has been used up before the upload. In such case
  860         // seahub would return 520 to the generate-link request.
  861         // See https://github.com/haiwen/seahub/blob/v6.0.7-server/seahub/api2/views.py#L133
  862         //
  863         // Second, the quota is not exceeded before the upload, but would exceed
  864         // after the upload. In such case httpserver would return 443 to the
  865         // multipart upload request.
  866         // See https://github.com/haiwen/seafile-server/blob/v6.0.7-server/server/http-status-codes.h#L10
  867         error_string_ = tr("The storage quota has been used up");
  868     }
  869 }