/
waterstone
/
file_processor
Обзор
Документация
Войти
/
waterstone
/
file_processor
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/main_view.cpp
485 строк
14 KB
Peter Sakhno
Initial commit
19 янв 2026, 11:08
19 янв 2026, 11:08
1ffa16c
Код
Авторство
О чём код?
#include "main_view.h" #include <chrono> #include <QDir> #include <QFileDialog> #include <QMessageBox> #include <QApplication> #include <QInputDialog> #include <QtConcurrentRun> #include <QFutureSynchronizer> #include "table_model.h" #include "ui_main_view.h" namespace { using Promise = QPromise<XorFileProcessor::Result>; void XorFileProcessorTask(Promise& promise, XorFileProcessor::Context ctx) { struct Controller : public XorFileProcessor::IRunControl { Promise& promise; XorFileProcessor::ErrorCode error_code{ XorFileProcessor::ErrorCode::OkNoError }; std::string error_message; Controller(Promise& pr) : promise(pr) { promise.setProgressRange(0, 100); } bool IsCanceled() const override { return promise.isCanceled(); }; void SetProgress(size_t processed, size_t total) override { promise.setProgressValue( static_cast<int>(static_cast<double>(processed * 100) / total)); } void SetErrorCode(XorFileProcessor::ErrorCode code) override { error_code = code; } void SetErrorText(std::string&& message) override { error_message = message; } } ctrl(promise); bool res = XorFileProcessor::ProcessFile(ctx, ctrl); if (promise.isCanceled()) { QFileInfo ofi{ QString::fromStdWString(ctx.output_file_path) }; if (ofi.exists()) { QDir dir{ ofi.path() }; if (dir.exists()) { dir.remove(ofi.fileName()); } } } promise.addResult(std::make_tuple(res, ctrl.error_code, ctrl.error_message)); promise.finish(); } } MainView::MainView(QWidget* parent) : QWidget(parent), m_ui (new Ui::MainView) { QObject::connect(QApplication::instance(), &QApplication::aboutToQuit, this, &MainView::OnAppAboutToQuit); m_ui->setupUi(this); m_ui->selectProcessingMode->setCurrentIndex(MainView::ManualProcessingMode); m_ui->stackProcessingMode->setCurrentIndex(MainView::ManualProcessingMode); m_ui->stackedTimeButton->setCurrentIndex(MainView::TimeButtonStart); m_ui->tableView->setModel(new TableModel(m_ui->tableView)); QPointer<QHeaderView> hhv = m_ui->tableView->horizontalHeader(); if (hhv) { hhv->setSectionResizeMode(QHeaderView::ResizeToContents); hhv->setStretchLastSection(true); } QPointer<QHeaderView> vhv = m_ui->tableView->verticalHeader(); if (vhv) vhv->setSectionResizeMode(QHeaderView::ResizeToContents); } MainView::~MainView() { } bool MainView::GetPath(QString caption, QLineEdit* edit) { if (edit == nullptr) return false; QString cur_path = m_ui->editInputPath->text(); QString new_path = QFileDialog::getExistingDirectory(this, caption, cur_path); if (new_path.isEmpty()) return false; if (cur_path == new_path) return false; edit->setText(new_path); return true; } void MainView::OnProcessFiles() { QString input_path = m_ui->editInputPath->text(); if (input_path.isEmpty()) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify input path to look for files to process")); m_ui->editInputPath->setFocus(); return; } QDir input_dir(input_path); if (!input_dir.exists()) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify valid and existing input path")); m_ui->editInputPath->selectAll(); m_ui->editInputPath->setFocus(); return; } if (!m_ui->editDataProcessingKey->hasAcceptableInput()) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify 8-bytes key to process files")); m_ui->editDataProcessingKey->setFocus(); return; } QString key_sting = m_ui->editDataProcessingKey->text().replace( QRegularExpression("\\s+"), {}); if (key_sting.isEmpty()) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify 8-bytes key to process files")); m_ui->editDataProcessingKey->setFocus(); return; } bool ok = false; uint64_t key = key_sting.toULongLong(&ok, 16); if (!ok) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify correct 8-bytes key to process files:") % QString("\n") % tr(" - provide 16 charactes") % QString("\n") % tr(" - use following characters only: A-F, a-f, 0-9")); m_ui->editDataProcessingKey->setFocus(); return; } QString output_path = m_ui->editOutputPath->text(); if (output_path.isEmpty()) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify output path to store processed files")); m_ui->editOutputPath->setFocus(); return; } QDir output_dir(output_path); if (!output_dir.exists()) { QMessageBox::warning(this, tr("Processing files"), tr("Please, specify valid and existing output path")); m_ui->editOutputPath->selectAll(); m_ui->editOutputPath->setFocus(); return; } if (input_dir == output_dir) { auto res = QMessageBox::question(this, tr("Processing files"), tr("Output path is same as input path") % QString("\n") % tr("This may be not safe for input files") % QString("\n") % tr("Do you want to process files anyway?")); if (QMessageBox::Yes != res and QMessageBox::Ok != res) return; } input_dir.setFilter(QDir::Files | QDir::Readable); const auto mask = GetInputFileMask(); input_dir.setNameFilters(mask); auto input_file_names = input_dir.entryInfoList(); if (input_file_names.isEmpty()) { QMessageBox::warning(this, tr("Processing files"), tr("No files found to process")); return; } for (const auto& s : input_file_names) ProcessFile(key, s); } void MainView::OnSelectInputPath() { GetPath(tr("Input path"), m_ui->editInputPath); } void MainView::OnSelectOutputPath() { GetPath(tr("Output path"), m_ui->editOutputPath); } void MainView::OnProcessAndStartTimer() { OnProcessFiles(); m_automation_timer.start( std::chrono::seconds(m_ui->editTimerPeriod->value()), this); m_ui->stackedTimeButton->setCurrentIndex(MainView::TimeButtonStop); } void MainView::OnStopTimer() { m_automation_timer.stop(); m_ui->stackedTimeButton->setCurrentIndex(MainView::TimeButtonStart); } void MainView::OnTimerPeriodChanged() { if (m_automation_timer.isActive()) { m_automation_timer.start( std::chrono::seconds(m_ui->editTimerPeriod->value()), this); } } void MainView::OnProcessingModeChanged() { const int mode = m_ui->selectProcessingMode->currentIndex(); m_ui->stackProcessingMode->setCurrentIndex(mode); if (mode == ManualProcessingMode) { // Switched from automatic mode; if (m_automation_timer.isActive()) m_automation_timer.stop(); } } void MainView::OnAppAboutToQuit() { QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); StopAllTasks(); QGuiApplication::restoreOverrideCursor(); } QStringList MainView::GetInputFileMask() const { QString raw_str = m_ui->editInputFileMask->text(); if (raw_str.isEmpty()) return {}; QStringList out; for (const auto& s : raw_str.split(QRegularExpression("[,;]"), Qt::SkipEmptyParts)) { const auto tmp = s.trimmed(); if (!tmp.isEmpty()) out.push_back(tmp); } return out; } void MainView::ProcessFile(uint64_t key, const QFileInfo& input_file_info) { const auto output_file_path = GenerateOutputFilePath(input_file_info); if (output_file_path.isEmpty()) { QMessageBox::warning(this, tr("File processing"), tr("Failed to generate output file path for %1").arg( input_file_info.fileName())); return; } // Check if output file is already being written std::wstring output_file_path_wstr = output_file_path.toStdWString(); auto cit = std::find_if(m_task_watchers.cbegin(), m_task_watchers.cend(), [&output_file_path_wstr](const auto& i) { return 0 == output_file_path_wstr.compare(i.second->outputFile()); }); if (cit != m_task_watchers.cend()) { TableModelQP model = qobject_cast<TableModel*>(m_ui->tableView->model()); if (model) { model->AddTaskWithResult(m_process_task_id_counter++, input_file_info.filePath().toStdWString(), output_file_path_wstr, false, XorFileProcessor::ErrorCode::CantStartProcess, tr("Output file is currently being written").toStdString()); } return; } // Create task context XorFileProcessor::Context ctx; ctx.id = m_process_task_id_counter++; ctx.xor_key = key; ctx.input_file_path = input_file_info.filePath().toStdWString(); ctx.output_file_path = output_file_path_wstr; ctx.delete_input_on_success = m_ui->checkDeleteInputFiles->isChecked(); // Create new task watcher for the task TaskWatcherQP watcher = new TaskWatcher(ctx, this); // Listen for task watcher signals QObject::connect(watcher, &TaskWatcher::progressValueChanged, this, &MainView::OnTaskProgress); QObject::connect(watcher, &TaskWatcher::started, this, &MainView::OnTaskStarted); QObject::connect(watcher, &TaskWatcher::finished, this, &MainView::OnTaskFinished); // Store task watcher m_task_watchers.insert({ ctx.id, watcher }).first; // Lauhch file process watcher->setFuture( QtConcurrent::run(XorFileProcessorTask, ctx)); if (m_process_task_id_counter >= std::numeric_limits<decltype(m_process_task_id_counter)>::max()) { // Not very reallistic, but who knows // Just reset counter m_process_task_id_counter = 1; } } QString MainView::GenerateOutputFilePath(const QFileInfo& input_file_info) { if (m_ui->editOutputPath->text().isEmpty()) return {}; QDir out_dir(m_ui->editOutputPath->text()); if (!out_dir.exists()) return {}; QFileInfo out_file_path(QDir(out_dir.canonicalPath()).filePath( input_file_info.fileName())); if (out_file_path.exists()) { int action = m_ui->selectExistinOutputMode->currentIndex(); if (MainView::ExistingOutputAskForAdvice == action) { QStringList items = { tr("Overwrite"), tr("Add counter to the name"), tr("Abort file processing") }; bool ok = false; auto item = QInputDialog::getItem(this, tr("Output file exists"), tr("What to do with existing output file %1?").arg( out_file_path.fileName()), items, 0, false, &ok); if (!ok or item.isEmpty()) return {}; action = static_cast<decltype(action)>(items.indexOf(item)); } switch (action) { case MainView::ExistingOutputOverride: return out_file_path.filePath(); case MainView::ExistingOutputAbortProcessing: return {}; case MainView::ExistingOutputAddCounter: { uint64_t counter = 1; const QString base_name = out_file_path.baseName(); const QString suffix = out_file_path.completeSuffix(); const QDir out_dir(out_file_path.path()); QFileInfo path; do { path.setFile(out_dir.filePath(QString("%1_%2.%3"). arg(base_name).arg(counter++).arg(suffix))); } while (path.exists() and counter < std::numeric_limits<decltype(counter)>::max()); if (path.exists()) { QMessageBox::warning(this, tr("Output file exists"), tr("Failed to generate unique output file name in directory %1 " "for input file %2 ").arg(out_file_path.path()).arg( input_file_info.fileName())); return {}; } return path.filePath(); } break; } } return out_file_path.filePath(); } void MainView::OnTaskStarted() { // Get watcher instance TaskWatcherQP watcher = qobject_cast<TaskWatcher*>(sender()); if (!watcher) return; // Get table model TableModelQP model = qobject_cast<TableModel*>(m_ui->tableView->model()); if (!model) return; // Add task row to table model->AddTask(watcher->taskId(), watcher->inputFile(), watcher->outputFile()); } void MainView::OnTaskProgress() { // Get watcher instance TaskWatcherQP watcher = qobject_cast<TaskWatcher*>(sender()); if (!watcher) return; // Get table model TableModelQP model = qobject_cast<TableModel*>(m_ui->tableView->model()); if (!model) return; // Update task progress in table model->UpdateProgress(watcher->taskId(), watcher->progressValue()); } void MainView::OnTaskFinished() { // Get watcher instance TaskWatcherQP watcher = qobject_cast<TaskWatcher*>(sender()); if (!watcher) return; // Get task id auto task_id = watcher->taskId(); // Get table model TableModelQP model = qobject_cast<TableModel*>(m_ui->tableView->model()); if (!model) return; // Update task state in table auto res = watcher->result(); model->UpdateResult(watcher->taskId(), std::get<0>(res), std::get<1>(res), std::get<2>(res)); // Delete task DeleteTaskWatcher(task_id); } void MainView::DeleteTaskWatcher(uint64_t task_id) { auto watcher_it = m_task_watchers.find(task_id); if (watcher_it != m_task_watchers.end()) { if (watcher_it->second) { if (watcher_it->second->isRunning()) watcher_it->second->cancel(); else watcher_it->second->deleteLater(); } m_task_watchers.erase(watcher_it); } } void MainView::StopAllTasks() { if (m_task_watchers.empty()) return; QFutureSynchronizer<XorFileProcessor::Result> synchronizer; synchronizer.setCancelOnWait(true); for (const auto& [_, watcher] : m_task_watchers) { if (watcher && watcher->isRunning()) synchronizer.addFuture(watcher->future()); } } void MainView::closeEvent(QCloseEvent* event) { auto cit = std::find_if(m_task_watchers.cbegin(), m_task_watchers.cend(), [](const auto& i) { return i.second->isRunning(); }); if (cit != m_task_watchers.cend()) { const auto res = QMessageBox::question(this, tr("Application quit"), tr("Some tasks may still be active") % QString("\n") % tr("It may take some time to stop all tasks") % QString("\n") % tr("Do you really want do quit?")); if (QMessageBox::Ok != res and QMessageBox::Yes != res) { event->ignore(); return; } } return QWidget::closeEvent(event); } void MainView::timerEvent(QTimerEvent* event) { if (nullptr == event) return; if (!m_automation_timer.isActive() or m_automation_timer.id() != event->id()) return; OnProcessFiles(); }