~phablet-team/ubuntu-download-manager/trunk

« back to all changes in this revision

Viewing changes to ubuntu-download-manager-priv/downloads/file_download.cpp

  • Committer: CI bot
  • Author(s): Manuel de la Peña
  • Date: 2014-02-24 23:20:01 UTC
  • mfrom: (239.1.11 atomic-steps)
  • Revision ID: ps-jenkins@lists.canonical.com-20140224232001-k3bdwm4er8aj2icq
Ensure that we do no have race issues between the diff downloads. This is fixed in two ways:

1. Use a temp file.
2. USe a mutex to decide the final path of the download. Fixes: 1250612, 1277589

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
#include <QFile>
24
24
#include <QFileInfo>
25
25
#include <QSslError>
 
26
#include <ubuntu/download_manager/metadata.h>
26
27
#include <ubuntu/download_manager/system/hash_algorithm.h>
27
28
#include "downloads/file_download.h"
 
29
#include "system/filename_mutex.h"
28
30
#include "system/logger.h"
29
31
#include "system/network_reply.h"
30
32
 
34
36
namespace {
35
37
 
36
38
    const QString DATA_FILE_NAME = "data.download";
37
 
    const QString METADATA_FILE_NAME = "metadata";
38
 
    const QString METADATA_COMMAND_KEY = "post-download-command";
39
 
    const QString METADATA_COMMAND_FILE_KEY = "$file";
40
39
    const QString NETWORK_ERROR = "NETWORK ERROR";
41
40
    const QString HASH_ERROR = "HASH ERROR";
42
41
    const QString COMMAND_ERROR = "COMMAND ERROR";
43
42
    const QString SSL_ERROR = "SSL ERROR";
44
43
    const QString FILE_SYSTEM_ERROR = "FILE SYSTEM ERROR: %1";
 
44
    const QString TEMP_EXTENSION = ".tmp";
45
45
    const QString AUTH_ERROR = "AUTHENTICATION ERROR";
46
46
    const QString PROXY_AUTH_ERROR = "PROXY_AUTHENTICATION ERROR";
47
47
    const QString UNEXPECTED_ERROR = "UNEXPECTED_ERROR";
48
 
 
49
48
}
50
49
 
51
50
namespace Ubuntu {
116
115
 
117
116
    // remove current data and metadata
118
117
    cleanUpCurrentData();
 
118
    // unlock the file name so that other downloads can use it it if is
 
119
    // no used in the file system
 
120
    _fileNameMutex->unlockFileName(_filePath);
119
121
    _downloading = false;
120
122
    emit canceled(true);
121
123
}
197
199
 
198
200
    // create file that will be used to maintain the state of the
199
201
    // download when resumed.
200
 
    _currentData = FileManager::instance()->createFile(_filePath);
 
202
    _currentData = FileManager::instance()->createFile(_tempFilePath);
201
203
    bool canWrite = _currentData->open(QIODevice::ReadWrite | QFile::Append);
202
204
 
203
205
    if (!canWrite) {
326
328
    _reply->deleteLater();
327
329
    _reply = nullptr;
328
330
 
329
 
    // clean the local file
 
331
    // clean the local file without unlocking the file the reason for
 
332
    // this is that the file pat is going to be reused to store the
 
333
    // data from the redirect
330
334
    cleanUpCurrentData();
331
335
 
332
336
    // perform again the request but do not emit started signal
333
 
    _currentData = FileManager::instance()->createFile(_filePath);
 
337
    _currentData = FileManager::instance()->createFile(_tempFilePath);
334
338
    bool canWrite = _currentData->open(QIODevice::ReadWrite | QFile::Append);
335
339
 
336
340
    if (!canWrite) {
369
373
    // means we are done here else we execute the command AND raise the
370
374
    // finish signals once the command was done (or an error occurred in
371
375
    // the command execution.
372
 
    if (metadata().contains(METADATA_COMMAND_KEY)) {
 
376
    if (metadata().contains(Metadata::COMMAND_KEY)) {
373
377
        // just emit processing if we DO NOT have a hash because else we
374
378
        // already emitted it.
375
379
        if (_hash.isEmpty()) {
377
381
        }
378
382
        // toStringList will return an empty list if it cannot be converted
379
383
        QStringList commandData =
380
 
            metadata()[METADATA_COMMAND_KEY].toStringList();
 
384
            metadata()[Metadata::COMMAND_KEY].toStringList();
381
385
        if (commandData.count() == 0) {
382
386
            DOWN_LOG(ERROR) << "COMMAND DATA MISSING";
383
387
            emitError(COMMAND_ERROR);
390
394
            QStringList args;
391
395
 
392
396
            foreach(const QString& arg, commandData) {
393
 
                if (arg == METADATA_COMMAND_FILE_KEY)
 
397
                if (arg == Metadata::COMMAND_FILE_KEY)
394
398
                    args << filePath();
395
399
                else
396
400
                    args << arg;
412
416
            return;
413
417
        }
414
418
    } else {
415
 
        setState(Download::FINISH);
416
 
        DOWN_LOG(INFO) << "EMIT finished" << filePath();
417
 
        emit finished(filePath());
 
419
        emitFinished();
418
420
    }
419
421
 
420
422
    // clean the reply
467
469
    if (exitCode == 0 && exitStatus == QProcess::NormalExit) {
468
470
        // remove the file since we are done with it
469
471
        cleanUpCurrentData();
470
 
        setState(Download::FINISH);
471
 
        DOWN_LOG(INFO) << "EMIT finished" << filePath();
472
 
        emit finished(filePath());
 
472
        emitFinished();
 
473
        // remove the file because that is the contract that we have with
 
474
        // the clients
 
475
        auto fileMan = FileManager::instance();
 
476
 
 
477
        if (fileMan->exists(_tempFilePath)) {
 
478
            LOG(INFO) << "Removing '" << _tempFilePath << "'";
 
479
            fileMan->remove(_tempFilePath);
 
480
        }
 
481
 
 
482
        if (fileMan->exists(_filePath)) {
 
483
            LOG(INFO) << "Removing '" << _filePath << "'";
 
484
            fileMan->remove(_filePath);
 
485
        }
473
486
    } else {
474
487
        auto standardOut = p->readAllStandardOutput();
475
488
        auto standardErr = p->readAllStandardError();
507
520
void
508
521
FileDownload::init() {
509
522
    _requestFactory = RequestFactory::instance();
 
523
    _fileNameMutex = System::FileNameMutex::instance();
510
524
    SystemNetworkInfo* networkInfo = SystemNetworkInfo::instance();
511
525
    _connected = networkInfo->isOnline();
512
526
    _downloading = false;
515
529
    connect(networkInfo, &SystemNetworkInfo::onlineStateChanged,
516
530
        this, &FileDownload::onOnlineStateChanged);
517
531
 
518
 
    _filePath = getSaveFileName();
 
532
    initFileNames();
519
533
 
520
534
    // ensure that the download is valid
521
535
    if (!_url.isValid()) {
563
577
    return flushed;
564
578
}
565
579
 
566
 
QString
567
 
FileDownload::getSaveFileName() {
 
580
void
 
581
FileDownload::initFileNames() {
 
582
    // the mutex will ensure that we do not have race conditions about
 
583
    // the file names in the download manager
568
584
    QString path = _url.path();
569
585
    QString basename = QFileInfo(path).fileName();
570
586
 
571
587
    if (basename.isEmpty())
572
588
        basename = DATA_FILE_NAME;
573
589
 
574
 
    QVariantMap metadataMap = metadata();
575
 
    QString finalPath;
 
590
    auto metadataMap = metadata();
576
591
 
577
 
    if (!isConfined() && metadataMap.contains(LOCAL_PATH_KEY)) {
578
 
        finalPath = metadataMap[LOCAL_PATH_KEY].toString();
 
592
    if (!isConfined() && metadataMap.contains(Metadata::LOCAL_PATH_KEY)) {
 
593
        _filePath = metadataMap[Metadata::LOCAL_PATH_KEY].toString();
 
594
        _tempFilePath = _fileNameMutex->lockFileName(
 
595
            _filePath + TEMP_EXTENSION);
579
596
 
580
597
        // in this case and because the app is not confined we are
581
598
        // going to check if the file exists, if it does we will
582
599
        // raise an error
583
 
        if (QFile::exists(finalPath)) {
 
600
        if (QFile::exists(_filePath)) {
584
601
            setIsValid(false);
585
602
            setLastError(QString("File already exists at: '%1'").arg(
586
 
                finalPath));
 
603
                _filePath));
587
604
        }
588
605
    } else {
589
 
        finalPath = rootPath() + QDir::separator() + basename;
590
 
        if (QFile::exists(finalPath)) {
591
 
            finalPath = uniqueFilePath(finalPath);
592
 
        }
593
 
 
 
606
        auto desiredPath = rootPath() + QDir::separator() + basename;
 
607
        _filePath = _fileNameMutex->lockFileName(desiredPath);
 
608
        _tempFilePath = _filePath + TEMP_EXTENSION;
594
609
    }
595
 
 
596
 
    return finalPath;
597
610
}
598
611
 
599
 
QString
600
 
FileDownload::uniqueFilePath(QString path) {
601
 
    QFileInfo fileInfo(path);
602
 
 
603
 
    // Split the file into 2 parts - dot+extension, and everything else. For
604
 
    // example, "path/file.tar.gz" becomes "path/file"+".tar.gz", while
605
 
    // "path/file" (note lack of extension) becomes "path/file"+"".
606
 
    auto secondPart = fileInfo.completeSuffix();
607
 
    auto firstPart = path;
608
 
 
609
 
    if (!secondPart.isEmpty()) {
610
 
        secondPart = "." + secondPart;
611
 
        firstPart = path.left(path.size() - secondPart.size());
 
612
void
 
613
FileDownload::emitFinished() {
 
614
    auto fileMan = FileManager::instance();
 
615
 
 
616
    LOG(INFO) << "EMIT finished" << filePath();
 
617
    setState(Download::FINISH);
 
618
 
 
619
    if (fileMan->exists(_tempFilePath)) {
 
620
        LOG(INFO) << "Rename '" << _tempFilePath << "' to '"
 
621
            << _filePath << "'";
 
622
        fileMan->rename(_tempFilePath, _filePath);
612
623
    }
613
 
 
614
 
    // Try with an ever-increasing number suffix, until we've reached a file
615
 
    // that does not yet exist.
616
 
    for (int ii = 1; ; ii++) {
617
 
        // Construct the new file name by adding the unique number between the
618
 
        // first and second part.
619
 
        auto finalPath = QString("%1 (%2)%3").arg(firstPart).arg(ii).arg(secondPart);
620
 
        // If no file exists with the new name, return it.
621
 
        if (!QFile::exists(finalPath)) {
622
 
            return finalPath;
623
 
        }
624
 
    }  // for
625
 
    return path;
 
624
    _fileNameMutex->unlockFileName(_filePath);
 
625
    emit finished(_filePath);
626
626
}
627
627
 
628
628
void
638
638
        _currentData->deleteLater();
639
639
        _currentData = nullptr;
640
640
    } else {
641
 
        QScopedPointer<QFile> tempFile(new QFile(_filePath));
 
641
        QScopedPointer<QFile> tempFile(new QFile(_tempFilePath));
642
642
        success = tempFile->remove();
643
643
        if (!success)
644
644
            error = tempFile->error();
674
674
    _reply->deleteLater();
675
675
    _reply = nullptr;
676
676
    cleanUpCurrentData();
 
677
    // let other downloads use the same file name
 
678
    _fileNameMutex->unlockFileName(_filePath);
677
679
    Download::emitError(error);
678
680
}
679
681