Двойная загрузка файла C++ Qt QFileSystemWatcher

У меня возникают проблемы, когда файл дважды загружается на сервер.

Я использую класс QFileSystemWatcher из C++ Qt в Windows XP для отправки файла при изменении папки. Файлы небольшие (1-12 КБ).

Приложение отправляет файлы, сканируя папку всякий раз, когда она изменяется (по сигналу directoryChanged), перебирает файлы и отправляет тот, который мне нужен. Сервер отвечает XML-файлом, который возвращается в ту же папку для обработки другим приложением.

По-видимому, в некоторых системах происходит 2 очень быстрых сигнала directoryChanged почти одновременно и происходят две очень быстрые загрузки файлов.

На сервере работают Apache и PHP, и на стороне PHP есть простой MUTEX, но я просто хотел добраться до корня проблемы, которая, похоже, находится на стороне Qt. Я открыт для использования другого класса, другой библиотеки или прямого С++.

Вот некоторый код, я удалил весь нерелевантный контент:

this->w = new QFileSystemWatcher();
this->w->addPath("C:/POSERA/MaitreD/DATA/INT");

QStringList directoryList = w->directories();
Q_FOREACH(QString directory, directoryList)
{
    qDebug() << "Watching Main Directory name: " << directory << endl;
}

DirectoryWatcher* dw = new DirectoryWatcher;

QObject::connect( this->w, SIGNAL(directoryChanged(const QString&)),
                  dw, SLOT(directoryChanged(const QString&)));

и DirectoryWatcher.cpp:

DirectoryWatcher::DirectoryWatcher(QWidget* parent) : QWidget(parent)
{
    lockSend = false;
}

void DirectoryWatcher::directoryChanged(const QString& str)
{
    directoryLastChanged = str;

    QByteArray byteArray = str.toUtf8();
    const char* cString = byteArray.constData();

    sendChangedFiles(cString);
}

void DirectoryWatcher::sendChangedFiles(const char* path)
{
    DIR *dir;
    struct dirent *ent;
    if ((dir = opendir (path)) != NULL)
    {
        QString str;

        while ((ent = readdir (dir)) != NULL)
        {
            str = QString("%1/%2").arg(path, ent->d_name);

            QFileInfo info(str);

            if (lockSend == false &&
               (info.completeSuffix() == "xml" || info.completeSuffix() == "XML") &&
               (info.baseName() != "") &&
               (!info.baseName().startsWith("REDM")) &&
               (!info.baseName().startsWith("REFT")))
            {
                // reset the counter.
                this->resendCounter = 0;

                sendFileAndAccept(str.toUtf8().constData());
            }
        }
        closedir (dir);
    }
    else
    {
        qDebug() << "Could not open directory" << endl;
    }
}

class QNetworkRequest;
class QNetworkReply;

void DirectoryWatcher::sendFileAndAccept(const char* path)
{
    // increment the resend counter
    this->resendCounter++;

    QFileInfo fileInfo(path);

    QNetworkAccessManager * mgr = new QNetworkAccessManager(this);
    connect(mgr,SIGNAL(finished(QNetworkReply*)),
            this,SLOT(saveResponse(QNetworkReply*)));
    connect(mgr,SIGNAL(finished(QNetworkReply*)),
            mgr,SLOT(deleteLater())); // @todo delete later

    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    QHttpPart filePart;
    filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/xml")); // @todo test
    filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"someFile\"; filename=\"" + fileInfo.baseName() + ".xml\""));

    currentFileSent = fileInfo.baseName();

    QFile *file = new QFile(path);
    file->open(QIODevice::ReadOnly);
    filePart.setBodyDevice(file);
    file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart

    multiPart->append(filePart);

    // POST request
    QNetworkReply *reply = mgr->post(QNetworkRequest(QUrl(XXXXXX)), multiPart);

    multiPart->setParent(reply); // delete the multiPart with the reply

    // lock
    lockSend = true;
}

void DirectoryWatcher::saveResponse(QNetworkReply *rep) {

    // get the response
    QByteArray bts = rep->readAll();
    QString str(bts);

    // compute new path
    QString partName = currentFileSent.mid(1, currentFileSent.length());
    QString newPath = QString("%1/A%2.xml").arg(directoryLastChanged, partName);

    qDebug() << "new path: " << newPath << endl;

    switch (rep->error()) {
        case QNetworkReply::NoError: {
            qDebug() << "NO ERROR" << endl;

            // save response to a file.

            QFile file(newPath);
            file.open(QIODevice::WriteOnly | QIODevice::Text);
            QTextStream out(&file);
            out << str;

            file.close();

            break;
        }
        default:

//        case QNetworkReply::TimeoutError :
//        case QNetworkReply::HostNotFoundError :
            qDebug() << "NETWORK REPLY ERROR" << endl;
            // resend the file if the counter is < 10
            if (this->resendCounter < 5) {

                // delay by n sec
                QTime dieTime = QTime::currentTime().addSecs(1);
                while( QTime::currentTime() < dieTime )
                    QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

                sendFileAndAccept(this->lastPathSent.toStdString().c_str());
            } else {

                // after 10 attempts, we're probably sure that the network is down
                // save the file somewhere and generate a default one to prevent timeouts.

                qDebug() << "Saving file for later..." << endl;
                if (!saveFileForLater(lastPathSent.toStdString().c_str())) {
                    qDebug() << "ERROR SAVING FILE, CHECK IF FOLDER EXISTS AND THE PERMISSIONS." << endl;
                }

                // generate a default one to prevent timeouts.
                qDebug() << "Generate a default file..." << endl;
                // ...
            }

            break;
    }

    // unlock
    lockSend = false;

    rep->deleteLater(); // prevent memory leak
}

bool DirectoryWatcher::saveFileForLater(const char* pathToRequestFile) {

    QFile file(pathToRequestFile);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug() << "readonly and text" << endl;
        return false;
    }

    QString path(pathToRequestFile);
    QFileInfo fileinfo(path);

    QString newPath = "C:\\data\\offline\\" + fileinfo.fileName();

    return file.copy(newPath);

}

Спасибо за вашу помощь.


person Trevor Donahue    schedule 22.08.2013    source источник
comment
Сомневаюсь, что вы найдете решение. Я могу подтвердить это поведение. Хотя у меня так было с большими файлами (>500mb). Наблюдатель за файловой системой срабатывал несколько раз в процессе копирования. Мне также пришлось реализовать какой-то «мьютекс». Но мне все еще немного интересно, что вы можете увидеть это с такими маленькими файлами, как ваш.   -  person Greenflow    schedule 23.08.2013
comment
Я не понимаю вопроса. Вы хотите только 1 сигнал directoryChanged для обоих файлов (реализуйте дроссель на известных часах). Или вы хотите обрабатывать каждый файл отдельно?   -  person rileyberton    schedule 23.08.2013
comment
спасибо за ваши ответы, ребята. @Greenflow Я думаю, что наоборот, маленькие файлы более подвержены ошибкам в сценариях загрузки файлов.   -  person Trevor Donahue    schedule 23.08.2013
comment
@rileyberton в описанном мной случае нужно отправить только один файл, но приложение отправляет один и тот же файл 2 раза. PHP интерпретирует их как 2 отдельных потока, которые иногда могут входить в один и тот же критический раздел (без реализации мьютекса). Я хочу отправить файл, а затем дождаться ответа (за это должен отвечать lockSend). Я уверен, что это возможно, если такие приложения, как dropbox, делают это без проблем, то я не думаю, что мне следует изобретать велосипед с помощью какого-то решения «сделай сам», и я немного разочарован тем, что фреймворк, такой как Qt, не предотвращает это от происходящего   -  person Trevor Donahue    schedule 23.08.2013
comment
Таким образом, вы можете сохранить недолговечный набор, который отслеживает файлы в полете и не допускает дублирования на основе имени. Когда загрузка завершится, просто удалите файл из набора. Это предотвращает мьютекс вокруг sendChangedFiles.   -  person rileyberton    schedule 23.08.2013
comment
Я пробовал это некоторое время назад. Не сработало, потому что в некоторых случаях имя файла было таким же. Стороннее приложение генерирует XML-запрос и ожидает XML-ответа. При создании XML-файла ответа он перемещает и запрос, и ответ в другую папку. Таким образом, в некоторых случаях приложение может сгенерировать другой запрос с тем же именем файла. Это вне моего контроля.   -  person Trevor Donahue    schedule 23.08.2013
comment
в основном я хочу: 1) Захватить измененный файл 2) Отправить его 3) Дождаться ответа, тем самым заблокировав все остальные отправки 4) Получить ответ 5) Создать файл в папке с содержимым ответа. Проблема с QFileSystemWatcher заключается в том, что он отправляет сигнал, когда каталог изменился, и мне нужно проанализировать каталог, чтобы найти нужные файлы. Было бы намного проще, если бы я мог получить имя файла, который изменился и/или был создан в указанной папке.   -  person Trevor Donahue    schedule 23.08.2013
comment
Что касается вашего последнего пункта, Тревор, есть сигнал на QFileSystemWatcher под названием fileChanged(). Вы пробовали это вместо directoryChanged()?   -  person RobbieE    schedule 19.10.2013


Ответы (1)


Наиболее возможная причина 2-х вылетов directoryChanged в том, что обычный редактор при сохранении изменений удаляет и записывает на диск новую версию файла. Вот почему есть один сигнал, когда файл удаляется, и один, когда он создается заново.

person user2087932    schedule 20.05.2014
comment
Да, это было так. Спасибо. - person Trevor Donahue; 07.07.2015