AC
Alexandru Codreanu21 июня 2020 г. 15:04

Qt / C++ QProcess получение результата вывода данных архиватора 7z и вывод в QProgressBar

QProcess, Qt, QThread, qProgressBar

Доброго времени суток.
Пишу проект для архивации и администрировании баз данных используя архиватор 7z.
При написании проекта возникли вопросы при работе с классом QProcess .
Есть форма на которой есть таблица QTableView и модель QStandardItemModel в которую помещаю данные о базах данных (путь, наименование архива, статус БД и прогресс бар).

При действии Выполнить происходит следующее:
- проверки на существование основного файла БД и т.д.
- запуск процесса 7z + аргументы
- считывание данных вывода процесса 7z в QByteArray
- преобразование данных вывода в int для определения процента выполнения и отрисовки в QProgressBar

Суть вопроса - при выборе только одной БД процесс архивации происходит нормально (отрабатывает и прогресс бар), но как только выбираю более одной БД выводит ошибку что данный процесс уже запущен.
Фрагмент кода:

void MainWindow::onExecCopy()
{
    // проверим если установлена модель
    if (!ui->tableView->model()){
        insertMessageInListMessage("Не заполнена таблица с базами данных !!!\n"
                                   "Для продолжения необходимо выбрать одно из действий:\n"
                                   " - указать путь к папке с базами данных (кнопка \"Папка с базами данных 1С\")\n"
                                   " - определить базы данных пользователя (кнопка \"Определить базы 1С\")", ICON_MSG_ERROR);
        return;
    }

    // проверим если указан каталог где будут храниться архивы
    if (ui->lineEditPathBases->text().isEmpty()){
        insertMessageInListMessage("Не указан каталог где будут храниться архивы с базами данных.", ICON_MSG_ERROR);
        ui->tabWidget->setCurrentIndex(1);
        return;
    }

    // устанавливаем делегат для презентации прогресса
    ui->tableView->setItemDelegateForColumn(COLUMN_PROGRESS, new ProgressBarDelegate(this));

    // перебор строк
    for (int row = 0; row < ui->tableView->model()->rowCount(); row ++) {

        // переменные для формирования имени архива и путь к папке где будет храниться архивы
        QVariant checkState = model->data(model->index(row, 0), Qt::CheckStateRole);           // для определения помечен объект или нет
        QString _path = model->data(model->index(row, 0), Qt::DisplayRole).toString();         // путь к БД
        QString strDateTime = QDateTime::currentDateTime().toString("dd.MM.yyyy_hh.mm.ss");    // время и дата для архива
        QString nameBase = model->data(model->index(row, 1), Qt::DisplayRole).toString() + "_" + strDateTime; // имя архива 7z

        // проверка если базу необходимо архивировать
        if (checkState == Qt::Checked){

            // проверяем если есть папка и файл
            if (!QFile::exists(QDir::fromNativeSeparators(_path) + "/1Cv8.1CD")){ // преобразуем разделители в "/"
                qDebug() << "Не найден путь к БД: " + QDir::fromNativeSeparators(_path) + "/1Cv8.1CD";
                insertMessageInListMessage("Отсутствует основной файл \"1Cv8.CD\" базы данных в папке - " +
                                           QDir::fromNativeSeparators(_path), ICON_MSG_WARNNING);
                continue; // продолжаем
            }

            // устанавливаем значение текущуй строки
            m_currentRow = row;

            // устанавливаем элемент для вывода прогресса в модель
            QStandardItem *itemProgresBase = new QStandardItem("0");
            itemProgresBase->setFlags(itemProgresBase->flags() ^ Qt::ItemIsEditable);
            model->setItem(row, COLUMN_PROGRESS, itemProgresBase);

            /*** QStringList arguments; *************************************
             **
             ** arguments << "a";          // добавить к архиву
             ** arguments << "-bsp1";      // Установить поток вывода для строки вывода / ошибки / хода выполнения
             ** arguments << "-ssw";       // принудительная упаковка файлов, которые в данный момент открыты для записи
             ** arguments << "-m0=lzma2";  // метод сжатия
             ** arguments << "-mx9";       // высокая степень сжатия (7), ультра высокая степень сжатия (9)
             ** arguments << "-r0";        // исключения, которые будут прописаны
             **
             ****************************************************************/

            // переменные для старта выполнения архивации и пути к архивам
            QString strExec7za = "7z.exe a -bsp1 -ssw -m0=lzma2 -mx9 -r0 ";      // команды 7za
            QString strPath    = "\"" + _path + "\\1Cv8.1CD\" ";                 // путь и файл БД
            QString strFolder  = ui->lineEditPathBases->text();                  // путь к папке где будут храниться архивы
            QString nameArhive = "\"" + strFolder + "\\" + nameBase + ".7z\" ";  // имя архива 7z

            // подключаемся и считываем вывод
            connect(process, &QProcess::readyReadStandardOutput, this, &MainWindow::_readyReadStandardOutput);
            connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
                    this, QOverload<int>::of(&MainWindow::processFinished));

            // стартуем процесс 7z
            process->start(strExec7za + nameArhive + strPath);

        }
    }
}

int MainWindow::getPercentFromOutput(QByteArray processData)
{
    if (!processData.isEmpty()){

        QString strCurrnet = QString(processData);       // преобразуем QByteArray в QString

        QRegExp reg("[0-9]{1,}\\%");                     // маска поиска
        int indexLast = strCurrnet.lastIndexOf(reg) - 1; // находим последнюю позицию -1 (-1 это "%")

        if (indexLast != -1){
            QString subString = strCurrnet.mid(indexLast, 2);
//            qDebug() << subString;
            return subString.toInt(); //возвращаем процент выполения
        }
    }
    return 0;
}

void MainWindow::_readyReadStandardOutput()
{
    // получаем процент выполнения
    m_currentProgress = getPercentFromOutput(process->readAllStandardOutput());
    auto item = model->item(m_currentRow, COLUMN_PROGRESS);
    item->setData(m_currentProgress, Qt::DisplayRole);
}

Попыталься следующим образом, но блокируется весь интерфейс и не обновляется процесс выполнения архивации в ячейки.

            if (process->waitForStarted(-1)){
                while (process->waitForReadyRead(-1)) {
                    int outPrecent = getPercentFromOutput(process.readAllStandardOutput());
                }
            }

Понимаю, что основной процесс(или поток - если это правильное выражение) уже запущен.
Как быть в данной ситуации ?

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

10
Александр Панюшкин
  • 22 июня 2020 г. 0:09

доброе утро
я с телефона, поэтому могу ошибиться
я правильно понимаю, что у вас один процесс, который вы создаете в конструкторе?
Если так - тогда ответ на поверхности - создавайте для каждой архивации отдельный процесс.
А так вы и коннеты задваиваете (при назатии на кнопку у вас создаются дополнительные коннекты, а старые не убиваются)

    Да вы правы у меня только один процесс.
    Первоначально хочу добиться чтобы архивация происходила поочередно (архивация 1БД -> архивация 2БД и т.д.).
    На будущее хочу добавить настройку "Количество потоков" для одновременной архивации, но это не сейчас.
    Насколько я понимаю мне необходимо класс QTimer для периодического повторение считывания _readyReadStandardOutput() , но тогда вопрос - необходимо установить значение интервала запуска в миллисекундах. Как узнать значение интервала ?

      Александр Панюшкин
      • 22 июня 2020 г. 1:24

      Я бы добавил отслеживание сигнала QProcess:finished(). Ну, и ошибки отслеживать - QProcess::readyReadStandardError().
      И перенёс все коннекты в место определения переменной.
      Тогда и костыли в виде QTimer будут не нужны.

      А вообще, я бы вынес выполнение всех процессов в отдельный класс. И в него отправлял бы задачи. И уже в этом классе реализовывал задуманное о кол-ве потоков. Т.е. отделил бы одно от другого. Но это можно сделать на следующем этапе.

        По поводу отслеживание сигнала QProcess:finished() и QProcess::readyReadStandardError() согласен что нужно добавить.
        Также согласен по поводу отдельного класса для выполнения всех процессов и кол.потоков (на будущее) - удобнее будет.
        Не понимаю:

        И перенёс все коннекты в место определения переменной.

          Александр Панюшкин
          • 22 июня 2020 г. 1:48

          У вас при нажатии на кнопку вызывается функция onExecCopy() (поправьте, если не прав). В этой функции вы создаёте коннекты.
          Т.е. каждый раз, когда вы нажимаете кнопку, создаются дополнительные коннекты, что приводит множественному вызову слотов.
          Коннекты имеет смысл устанавливать там же, где вы создаёте (определяете) переменную (по крайней мере, я стараюсь делать так).

            При нажатии кнопки "Выполнить" вызывается функция onExecCopy()
            В функции происходит перебор строк таблицы (цикл):
            - проверка если БД помечена для архивации

            checkState == Qt::Checked

            - запуск процесса архивации только для БД которая прошла проверку
            - создание коннектов для считывания вывода
            Я нажимаю только один раз кнопку Выполнить .
            У меня создаются коннекты внутри цикла и мне нужно дождаться выполнения считывания архивации (к примеру БД1), а после этого продолжить цикл для проверки следующей базы если она помечена для архивации и т.д.

              AC
              • 22 июня 2020 г. 10:05

              Я понимаю что для каждой архивации необходимо создать отдельный процесс (это легче сделать и на будущее), но я хочу добиться следующего(в процессе переборки строк в цикле):
              1. инициализация процесса архивации БД1 и не продолжать цикл до тех пор пока QProcess::finished - что-то наподоби функции process->waitForReadyRead(-1) (которая блокирует весь интерфейс программы и не обновляется процесс выполнения архивации)
              2. инициализация процесса архивации БД2 ... и т.д

              По поводу ...

              Коннекты имеет смысл устанавливать там же, где вы создаёте (определяете) переменную (по крайней мере, я стараюсь делать так).

              ... вы правы исправил.

                Александр Панюшкин
                • 22 июня 2020 г. 10:15

                Создайте массив, в который добавляйте задания (например, в виде адресов на базы данных).
                Добавляйте туда сколько нужно заданий - одной или десять, или сто тысяч.

                Создайте метод, который будет брать элемент из массива и удалять его.
                И полученное значение отправлять в процесс.
                Когда процесс завершился - метод вызывается снова (из слота, который у вас вызывается по QProcess::finished). И так до тех пор, пока массив с заданиями не опустеет.

                Так вы сможете отправлять задания в любой момент (даже тогда, когда уже что-то архивируется - они просто встанут в очередь).

                Ну, и с некоторыми доработками вы сможете добавить возможность архивации в несколько процессов.

                И, повторюсь, этот функционал лучше вынести в отдельную сущность, в отдельный класс.

                Надеюсь, понятно объяснил.

                  Спасибо, объяснили очень понятно.
                  Огромное спасибо за идею с массивом.
                  Думаю что должно выйти (в теории все ОК).
                  Как только реализую отпишусь и выложу код.

                    Александр Панюшкин
                    • 22 июня 2020 г. 12:44

                    удачи! :)

                      Комментарии

                      Только авторизованные пользователи могут публиковать комментарии.
                      Пожалуйста, авторизуйтесь или зарегистрируйтесь
                      B

                      C++ - Тест 002. Константы

                      • Результат:16баллов,
                      • Очки рейтинга-10
                      B

                      C++ - Тест 001. Первая программа и типы данных

                      • Результат:46баллов,
                      • Очки рейтинга-6
                      FL

                      C++ - Тест 006. Перечисления

                      • Результат:80баллов,
                      • Очки рейтинга4
                      Последние комментарии
                      k
                      kmssr9 февраля 2024 г. 5:43
                      Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                      АК
                      Анатолий Кононенко5 февраля 2024 г. 12:50
                      Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                      EVA
                      EVA25 декабря 2023 г. 21:30
                      Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                      J
                      JonnyJo25 декабря 2023 г. 19:38
                      Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                      G
                      Gvozdik19 декабря 2023 г. 8:01
                      Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                      Сейчас обсуждают на форуме
                      P
                      Pisych27 февраля 2023 г. 15:04
                      Как получить в массив значения из связанной модели? Спасибо, разобрался:))
                      AC
                      Alexandru Codreanu19 января 2024 г. 22:57
                      QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
                      BlinCT
                      BlinCT27 декабря 2023 г. 19:57
                      Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
                      Дмитрий
                      Дмитрий10 января 2024 г. 15:18
                      Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
                      Evgenii Legotckoi
                      Evgenii Legotckoi12 декабря 2023 г. 17:48
                      Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

                      Следите за нами в социальных сетях