rssguard/src/librssguard/services/standard/gui/formstandardimportexport.cpp
2023-08-16 20:44:38 +02:00

368 lines
14 KiB
C++

// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/standard/gui/formstandardimportexport.h"
#include "exceptions/ioexception.h"
#include "gui/guiutilities.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "services/abstract/category.h"
#include "services/standard/standardfeedsimportexportmodel.h"
#include "services/standard/standardserviceroot.h"
#include <QFileDialog>
#include <QTextStream>
FormStandardImportExport::FormStandardImportExport(StandardServiceRoot* service_root, QWidget* parent)
: QDialog(parent), m_ui(new Ui::FormStandardImportExport), m_serviceRoot(service_root) {
m_ui->setupUi(this);
m_model = new FeedsImportExportModel(service_root, m_ui->m_treeFeeds);
connect(m_model, &FeedsImportExportModel::parsingStarted, this, &FormStandardImportExport::onParsingStarted);
connect(m_model, &FeedsImportExportModel::parsingFinished, this, &FormStandardImportExport::onParsingFinished);
connect(m_model, &FeedsImportExportModel::parsingProgress, this, &FormStandardImportExport::onParsingProgress);
GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("document-export")));
m_ui->m_txtPostProcessScript->textEdit()->setTabChangesFocus(true);
m_ui->m_txtPostProcessScript->textEdit()->setPlaceholderText(tr("Full command to execute"));
m_ui->m_txtPostProcessScript->textEdit()->setToolTip(tr("You can enter full command including interpreter here."));
m_ui->m_txtPostProcessScript->setStatus(WidgetWithStatus::StatusType::Ok,
tr("Here you can enter script executaion line, including interpreter."));
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::StatusType::Error,
tr("No file is selected."),
tr("No file is selected."));
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->disconnect();
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Warning,
tr("No operation executed yet."),
tr("No operation executed yet."));
connect(m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok),
&QPushButton::clicked,
this,
&FormStandardImportExport::performAction);
connect(m_ui->m_btnSelectFile, &QPushButton::clicked, this, &FormStandardImportExport::selectFile);
connect(m_ui->m_btnCheckAllItems, &QPushButton::clicked, m_model, &FeedsImportExportModel::checkAllItems);
connect(m_ui->m_btnUncheckAllItems, &QPushButton::clicked, m_model, &FeedsImportExportModel::uncheckAllItems);
connect(m_ui->m_txtPostProcessScript->textEdit(), &QPlainTextEdit::textChanged, this, [this]() {
onPostProcessScriptChanged(m_ui->m_txtPostProcessScript->textEdit()->toPlainText());
});
onPostProcessScriptChanged({});
}
FormStandardImportExport::~FormStandardImportExport() = default;
void FormStandardImportExport::setMode(FeedsImportExportModel::Mode mode) {
m_model->setMode(mode);
m_ui->m_progressBar->setVisible(false);
switch (mode) {
case FeedsImportExportModel::Mode::Export: {
m_model->setRootItem(m_serviceRoot);
m_model->checkAllItems();
m_ui->m_treeFeeds->setModel(m_model);
m_ui->m_treeFeeds->expandAll();
m_ui->m_cmbRootNode->setVisible(false);
m_ui->m_lblRootNode->setVisible(false);
m_ui->m_groupFetchMetadata->setVisible(false);
m_ui->m_groupFile->setTitle(tr("Destination file"));
m_ui->m_groupFeeds->setTitle(tr("Source feeds && categories"));
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setText(tr("&Export to file"));
setWindowTitle(tr("Export feeds"));
setWindowIcon(qApp->icons()->fromTheme(QSL("document-export")));
selectExportFile(true);
break;
}
case FeedsImportExportModel::Mode::Import: {
m_ui->m_cbExportIcons->setVisible(false);
m_ui->m_groupFile->setTitle(tr("Source file"));
m_ui->m_groupFeeds->setTitle(tr("Target feeds && categories"));
m_ui->m_groupFeeds->setDisabled(true);
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setText(tr("&Import from file"));
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false);
// Load categories.
loadCategories(m_serviceRoot->getSubTreeCategories(), m_serviceRoot);
setWindowTitle(tr("Import feeds"));
setWindowIcon(qApp->icons()->fromTheme(QSL("document-import")));
break;
}
default:
break;
}
}
void FormStandardImportExport::selectFile() {
switch (m_model->mode()) {
case FeedsImportExportModel::Mode::Import:
selectImportFile();
break;
case FeedsImportExportModel::Mode::Export: {
selectExportFile(false);
break;
}
default:
break;
}
}
void FormStandardImportExport::onParsingStarted() {
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Progress, tr("Parsing data..."), tr("Parsing data..."));
m_ui->m_btnSelectFile->setEnabled(false);
m_ui->m_groupFeeds->setEnabled(false);
m_ui->m_groupFetchMetadata->setEnabled(false);
m_ui->m_progressBar->setValue(0);
m_ui->m_progressBar->setVisible(true);
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false);
}
void FormStandardImportExport::onParsingFinished(int count_failed, int count_succeeded) {
m_ui->m_progressBar->setVisible(false);
m_ui->m_progressBar->setValue(0);
m_model->checkAllItems();
if (count_failed > 0) {
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Warning,
tr("Some feeds were not loaded properly. Check log for more information."),
tr("Some feeds were not loaded properly. Check log for more information."));
}
else {
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Ok, tr("Feeds were loaded."), tr("Feeds were loaded."));
}
m_ui->m_groupFeeds->setEnabled(true);
m_ui->m_groupFetchMetadata->setEnabled(true);
m_ui->m_btnSelectFile->setEnabled(true);
m_ui->m_treeFeeds->setModel(m_model);
m_ui->m_treeFeeds->expandAll();
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(true);
}
void FormStandardImportExport::onParsingProgress(int completed, int total) {
m_ui->m_progressBar->setMaximum(total);
m_ui->m_progressBar->setValue(completed);
}
void FormStandardImportExport::onPostProcessScriptChanged(const QString& new_pp) {
if (QRegularExpression(QSL(SCRIPT_SOURCE_TYPE_REGEXP)).match(new_pp).hasMatch() || !new_pp.simplified().isEmpty()) {
m_ui->m_txtPostProcessScript->setStatus(LineEditWithStatus::StatusType::Ok, tr("Command is ok."));
}
else {
m_ui->m_txtPostProcessScript->setStatus(LineEditWithStatus::StatusType::Ok, tr("Command is empty."));
}
}
void FormStandardImportExport::selectExportFile(bool without_dialog) {
const QString the_file = qApp->homeFolder() + QDir::separator() +
QSL("rssguard_feeds_%1.opml").arg(QDate::currentDate().toString(Qt::DateFormat::ISODate));
QString selected_file;
QString selected_filter;
const QString filter_opml20 = tr("OPML 2.0 files (*.opml *.xml)");
const QString filter_txt_url_per_line = tr("TXT files [one URL per line] (*.txt)");
if (!without_dialog) {
QString filter;
// Add more filters here.
filter += filter_opml20;
filter += QSL(";;");
filter += filter_txt_url_per_line;
selected_file =
QFileDialog::getSaveFileName(this, tr("Select file for feeds export"), the_file, filter, &selected_filter);
}
else {
selected_file = the_file;
selected_filter = filter_opml20;
}
if (!selected_file.isEmpty()) {
if (selected_filter == filter_opml20) {
m_conversionType = ConversionType::OPML20;
if (!selected_file.endsWith(QL1S(".opml"))) {
selected_file += QL1S(".opml");
}
}
else if (selected_filter == filter_txt_url_per_line) {
m_conversionType = ConversionType::TxtUrlPerLine;
if (!selected_file.endsWith(QL1S(".txt"))) {
selected_file += QL1S(".txt");
}
}
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::StatusType::Ok,
QDir::toNativeSeparators(selected_file),
tr("File is selected."));
}
const auto is_ok = m_ui->m_lblSelectFile->status() == WidgetWithStatus::StatusType::Ok;
m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(is_ok);
}
void FormStandardImportExport::selectImportFile() {
const QString filter_opml20 = tr("OPML 2.0 files (*.opml *.xml)");
const QString filter_txt_url_per_line = tr("TXT files [one URL per line] (*.txt)");
QString filter;
QString selected_filter;
// Add more filters here.
filter += filter_opml20 + QSL(";;") + filter_txt_url_per_line;
const QString selected_file = QFileDialog::getOpenFileName(this,
tr("Select file for feeds import"),
qApp->homeFolder(),
filter,
&selected_filter);
if (!selected_file.isEmpty()) {
if (selected_filter == filter_opml20) {
m_conversionType = ConversionType::OPML20;
}
else if (selected_filter == filter_txt_url_per_line) {
m_conversionType = ConversionType::TxtUrlPerLine;
}
m_ui->m_cbDoNotFetchIcons->setEnabled(m_conversionType == ConversionType::OPML20);
m_ui->m_cbDoNotFetchTitles->setEnabled(m_conversionType == ConversionType::OPML20);
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::StatusType::Ok,
QDir::toNativeSeparators(selected_file),
tr("File is selected."));
try {
parseImportFile(selected_file,
m_ui->m_groupFetchMetadata->isChecked(),
m_ui->m_cbDoNotFetchTitles->isChecked(),
m_ui->m_cbDoNotFetchIcons->isChecked(),
m_ui->m_txtPostProcessScript->textEdit()->toPlainText());
}
catch (const ApplicationException& ex) {
m_ui->m_btnSelectFile->setEnabled(true);
m_ui->m_progressBar->setVisible(false);
m_ui->m_progressBar->setValue(0);
m_ui->m_groupFeeds->setEnabled(false);
m_ui->m_groupFetchMetadata->setEnabled(true);
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Error, ex.message(), ex.message());
}
}
}
void FormStandardImportExport::parseImportFile(const QString& file_name,
bool fetch_metadata_online,
bool do_not_fetch_titles,
bool do_not_fetch_icons,
const QString& post_process_script) {
QByteArray input_data;
QFile input_file(file_name);
if (input_file.open(QIODevice::OpenModeFlag::Text | QIODevice::OpenModeFlag::Unbuffered |
QIODevice::OpenModeFlag::ReadOnly)) {
input_data = input_file.readAll();
input_file.close();
}
else {
throw ApplicationException(tr("cannot open file"));
}
switch (m_conversionType) {
case ConversionType::OPML20:
m_model->importAsOPML20(input_data,
fetch_metadata_online,
do_not_fetch_titles,
do_not_fetch_icons,
post_process_script);
break;
case ConversionType::TxtUrlPerLine:
m_model->importAsTxtURLPerLine(input_data,
fetch_metadata_online,
m_ui->m_txtPostProcessScript->textEdit()->toPlainText());
break;
default:
return;
}
}
void FormStandardImportExport::performAction() {
switch (m_model->mode()) {
case FeedsImportExportModel::Mode::Import:
importFeeds();
break;
case FeedsImportExportModel::Mode::Export:
exportFeeds();
break;
default:
break;
}
}
void FormStandardImportExport::exportFeeds() {
QByteArray result_data;
bool result_export = false;
switch (m_conversionType) {
case ConversionType::OPML20:
result_export = m_model->exportToOMPL20(result_data, m_ui->m_cbExportIcons->isChecked());
break;
case ConversionType::TxtUrlPerLine:
result_export = m_model->exportToTxtURLPerLine(result_data);
break;
default:
break;
}
if (result_export) {
try {
IOFactory::writeFile(m_ui->m_lblSelectFile->label()->text(), result_data);
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Ok,
tr("Feeds were exported successfully."),
tr("Feeds were exported successfully."));
}
catch (IOException& ex) {
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Error,
tr("Cannot write into destination file: '%1'."),
ex.message());
}
}
else {
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Error,
tr("Critical error occurred."),
tr("Critical error occurred."));
}
}
void FormStandardImportExport::importFeeds() {
QString output_message;
RootItem* parent =
static_cast<RootItem*>(m_ui->m_cmbRootNode->itemData(m_ui->m_cmbRootNode->currentIndex()).value<void*>());
if (m_serviceRoot->mergeImportExportModel(m_model, parent, output_message)) {
m_serviceRoot->requestItemExpand(parent->getSubTree(), true);
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Ok, output_message, output_message);
}
else {
m_ui->m_lblResult->setStatus(WidgetWithStatus::StatusType::Error, output_message, output_message);
}
}
void FormStandardImportExport::loadCategories(const QList<Category*>& categories, RootItem* root_item) {
m_ui->m_cmbRootNode->addItem(root_item->icon(), root_item->title(), QVariant::fromValue((void*)root_item));
for (Category* category : categories) {
m_ui->m_cmbRootNode->addItem(category->icon(), category->title(), QVariant::fromValue((void*)category));
}
}