Work on better parsing of input import data.

This commit is contained in:
Martin Rotter 2016-01-05 08:12:15 +01:00
parent 22a6da1604
commit 8e86655beb
9 changed files with 122 additions and 43 deletions

View File

@ -29,6 +29,7 @@ WidgetWithStatus::WidgetWithStatus(QWidget *parent)
m_btnStatus = new PlainToolButton(this); m_btnStatus = new PlainToolButton(this);
m_btnStatus->setFocusPolicy(Qt::NoFocus); m_btnStatus->setFocusPolicy(Qt::NoFocus);
m_iconProgress = qApp->icons()->fromTheme(QSL("item-sync"));
m_iconInformation = qApp->icons()->fromTheme(QSL("dialog-information")); m_iconInformation = qApp->icons()->fromTheme(QSL("dialog-information"));
m_iconWarning = qApp->icons()->fromTheme(QSL("dialog-warning")); m_iconWarning = qApp->icons()->fromTheme(QSL("dialog-warning"));
m_iconError = qApp->icons()->fromTheme(QSL("dialog-error")); m_iconError = qApp->icons()->fromTheme(QSL("dialog-error"));
@ -52,6 +53,10 @@ void WidgetWithStatus::setStatus(WidgetWithStatus::StatusType status, const QStr
m_btnStatus->setIcon(m_iconInformation); m_btnStatus->setIcon(m_iconInformation);
break; break;
case Progress:
m_btnStatus->setIcon(m_iconProgress);
break;
case Warning: case Warning:
m_btnStatus->setIcon(m_iconWarning); m_btnStatus->setIcon(m_iconWarning);
break; break;

4
src/gui/widgetwithstatus.h Normal file → Executable file
View File

@ -33,7 +33,8 @@ class WidgetWithStatus : public QWidget {
Information, Information,
Warning, Warning,
Error, Error,
Ok Ok,
Progress
}; };
// Constructors and destructors. // Constructors and destructors.
@ -53,6 +54,7 @@ class WidgetWithStatus : public QWidget {
PlainToolButton *m_btnStatus; PlainToolButton *m_btnStatus;
QHBoxLayout *m_layout; QHBoxLayout *m_layout;
QIcon m_iconProgress;
QIcon m_iconInformation; QIcon m_iconInformation;
QIcon m_iconWarning; QIcon m_iconWarning;
QIcon m_iconError; QIcon m_iconError;

View File

@ -36,6 +36,10 @@ FormStandardImportExport::FormStandardImportExport(StandardServiceRoot *service_
m_ui->setupUi(this); m_ui->setupUi(this);
m_model = new FeedsImportExportModel(m_ui->m_treeFeeds); m_model = new FeedsImportExportModel(m_ui->m_treeFeeds);
connect(m_model, SIGNAL(parsingStarted()), this, SLOT(onParsingStarted()));
connect(m_model, SIGNAL(parsingFinished(int,int,bool)), this, SLOT(onParsingFinished(int,int,bool)));
connect(m_model, SIGNAL(parsingProgress(int,int)), this, SLOT(onParsingProgress(int,int)));
setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::Dialog | Qt::WindowSystemMenuHint); setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::Dialog | Qt::WindowSystemMenuHint);
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Error, tr("No file is selected."), tr("No file is selected.")); m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Error, tr("No file is selected."), tr("No file is selected."));
@ -100,6 +104,41 @@ void FormStandardImportExport::selectFile() {
} }
} }
void FormStandardImportExport::onParsingStarted() {
m_ui->m_lblResult->setStatus(WidgetWithStatus::Progress, tr("Parsing data..."), tr("Parsing data..."));
m_ui->m_btnSelectFile->setEnabled(false);
m_ui->m_progressBar->setEnabled(true);
m_ui->m_progressBar->setValue(0);
}
void FormStandardImportExport::onParsingFinished(int count_failed, int count_succeeded, bool parsing_error) {
Q_UNUSED(count_failed)
Q_UNUSED(count_succeeded)
m_ui->m_progressBar->setValue(0);
m_ui->m_progressBar->setEnabled(false);
m_model->checkAllItems();
if (!parsing_error) {
m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, tr("Feeds were loaded."), tr("Feeds were loaded."));
m_ui->m_groupFeeds->setEnabled(true);
m_ui->m_btnSelectFile->setEnabled(true);
m_ui->m_treeFeeds->setModel(m_model);
m_ui->m_treeFeeds->expandAll();
}
else {
m_ui->m_lblResult->setStatus(WidgetWithStatus::Error, tr("Error, file is not well-formed. Select another file."),
tr("Error occurred. File is not well-formed. Select another file."));
}
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!parsing_error);
}
void FormStandardImportExport::onParsingProgress(int completed, int total) {
m_ui->m_progressBar->setMaximum(total);
m_ui->m_progressBar->setValue(completed);
}
void FormStandardImportExport::selectExportFile() { void FormStandardImportExport::selectExportFile() {
QString filter_opml20 = tr("OPML 2.0 files (*.opml)"); QString filter_opml20 = tr("OPML 2.0 files (*.opml)");
QString filter_txt_url_per_line = tr("TXT files (one URL per line) (*.txt)"); QString filter_txt_url_per_line = tr("TXT files (one URL per line) (*.txt)");
@ -162,7 +201,6 @@ void FormStandardImportExport::selectImportFile() {
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected.")); m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected."));
parseImportFile(selected_file); parseImportFile(selected_file);
m_model->checkAllItems();
} }
} }
@ -179,15 +217,13 @@ void FormStandardImportExport::parseImportFile(const QString &file_name) {
return; return;
} }
bool parsing_result;
switch (m_conversionType) { switch (m_conversionType) {
case OPML20: case OPML20:
parsing_result = m_model->importAsOPML20(input_data); m_model->importAsOPML20(input_data);
break; break;
case TXTUrlPerLine: case TXTUrlPerLine:
parsing_result = m_model->importAsTxtURLPerLine(input_data); m_model->importAsTxtURLPerLine(input_data);
break; break;
// TODO: V celém kódu nově zavést pořádně všude const, i v lokálních metodových proměnných // TODO: V celém kódu nově zavést pořádně všude const, i v lokálních metodových proměnných
@ -198,19 +234,6 @@ void FormStandardImportExport::parseImportFile(const QString &file_name) {
default: default:
return; return;
} }
if (parsing_result) {
m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, tr("Feeds were loaded."), tr("Feeds were loaded."));
m_ui->m_groupFeeds->setEnabled(true);
m_ui->m_treeFeeds->setModel(m_model);
m_ui->m_treeFeeds->expandAll();
}
else {
m_ui->m_lblResult->setStatus(WidgetWithStatus::Error, tr("Error, file is not well-formed. Select another file."),
tr("Error occurred. File is not well-formed. Select another file."));
}
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parsing_result);
} }
void FormStandardImportExport::performAction() { void FormStandardImportExport::performAction() {

View File

@ -48,6 +48,10 @@ class FormStandardImportExport : public QDialog {
void performAction(); void performAction();
void selectFile(); void selectFile();
void onParsingStarted();
void onParsingFinished(int count_failed, int count_succeeded, bool parsing_error);
void onParsingProgress(int completed, int total);
private: private:
void selectExportFile(); void selectExportFile();
void selectImportFile(); void selectImportFile();

View File

@ -114,6 +114,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="2">
<widget class="QProgressBar" name="m_progressBar">
<property name="enabled">
<bool>false</bool>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -282,7 +282,9 @@ void StandardFeed::fetchMetadataForItself() {
} }
} }
QPair<StandardFeed*,QNetworkReply::NetworkError> StandardFeed::guessFeed(const QString &url, const QString &username, const QString &password) { QPair<StandardFeed*,QNetworkReply::NetworkError> StandardFeed::guessFeed(const QString &url,
const QString &username,
const QString &password) {
QPair<StandardFeed*,QNetworkReply::NetworkError> result; result.first = NULL; QPair<StandardFeed*,QNetworkReply::NetworkError> result; result.first = NULL;
QByteArray feed_contents; QByteArray feed_contents;

View File

@ -145,7 +145,9 @@ class StandardFeed : public Feed {
// Returns pointer to guessed feed (if at least partially // Returns pointer to guessed feed (if at least partially
// guessed) and retrieved error/status code from network layer // guessed) and retrieved error/status code from network layer
// or NULL feed. // or NULL feed.
static QPair<StandardFeed*,QNetworkReply::NetworkError> guessFeed(const QString &url, const QString &username, const QString &password); static QPair<StandardFeed*,QNetworkReply::NetworkError> guessFeed(const QString &url,
const QString &username = QString(),
const QString &password = QString());
// Converts particular feed type to string. // Converts particular feed type to string.
static QString typeToString(Type type); static QString typeToString(Type type);

View File

@ -154,19 +154,22 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray &result) {
return true; return true;
} }
bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) { void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
emit parsingStarted();
QDomDocument opml_document; QDomDocument opml_document;
if (!opml_document.setContent(data)) { if (!opml_document.setContent(data)) {
return false; emit parsingFinished(0, 0, true);
} }
if (opml_document.documentElement().isNull() || opml_document.documentElement().tagName() != QSL("opml") || if (opml_document.documentElement().isNull() || opml_document.documentElement().tagName() != QSL("opml") ||
opml_document.documentElement().elementsByTagName(QSL("body")).size() != 1) { opml_document.documentElement().elementsByTagName(QSL("body")).size() != 1) {
// This really is not an OPML file. // This really is not an OPML file.
return false; emit parsingFinished(0, 0, true);
} }
int completed = 0, total = 0;
StandardServiceRoot *root_item = new StandardServiceRoot(); StandardServiceRoot *root_item = new StandardServiceRoot();
QStack<RootItem*> model_items; model_items.push(root_item); QStack<RootItem*> model_items; model_items.push(root_item);
QStack<QDomElement> elements_to_process; elements_to_process.push(opml_document.documentElement().elementsByTagName(QSL("body")).at(0).toElement()); QStack<QDomElement> elements_to_process; elements_to_process.push(opml_document.documentElement().elementsByTagName(QSL("body")).at(0).toElement());
@ -175,7 +178,10 @@ bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
RootItem *active_model_item = model_items.pop(); RootItem *active_model_item = model_items.pop();
QDomElement active_element = elements_to_process.pop(); QDomElement active_element = elements_to_process.pop();
for (int i = 0; i < active_element.childNodes().size(); i++) { int current_count = active_element.childNodes().size();
total += current_count;
for (int i = 0; i < current_count; i++) {
QDomNode child = active_element.childNodes().at(i); QDomNode child = active_element.childNodes().at(i);
if (child.isElement()) { if (child.isElement()) {
@ -242,6 +248,8 @@ bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
elements_to_process.push(child_element); elements_to_process.push(child_element);
model_items.push(new_category); model_items.push(new_category);
} }
emit parsingProgress(++completed, total);
} }
} }
} }
@ -250,8 +258,7 @@ bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
setRootItem(root_item); setRootItem(root_item);
emit layoutChanged(); emit layoutChanged();
emit parsingFinished(0, completed, false);
return true;
} }
bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) { bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
@ -262,32 +269,49 @@ bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
return true; return true;
} }
bool FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) { void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
emit parsingStarted();
int completed = 0, succeded = 0, failed = 0;
StandardServiceRoot *root_item = new StandardServiceRoot(); StandardServiceRoot *root_item = new StandardServiceRoot();
QList<QByteArray> urls = data.split('\n');
foreach (const QByteArray &url, data.split('\n')) { foreach (const QByteArray &url, urls) {
if (!url.isEmpty()) { if (!url.isEmpty()) {
QPair<StandardFeed*,QNetworkReply::NetworkError> guessed = StandardFeed::guessFeed(url);
StandardFeed *feed = new StandardFeed(); if (guessed.second == QNetworkReply::NoError) {
guessed.first->setUrl(url);
root_item->appendChild(guessed.first);
succeded++;
}
else {
StandardFeed *feed = new StandardFeed();
// TODO: co guessovat ten feed? feed->setUrl(url);
feed->setTitle(url);
feed->setCreationDate(QDateTime::currentDateTime());
feed->setIcon(qApp->icons()->fromTheme(QSL("folder-feed")));
feed->setEncoding(DEFAULT_FEED_ENCODING);
root_item->appendChild(feed);
failed++;
}
feed->setUrl(url); qApp->processEvents();
feed->setTitle(url);
feed->setCreationDate(QDateTime::currentDateTime());
feed->setIcon(qApp->icons()->fromTheme(QSL("folder-feed")));
feed->setEncoding(DEFAULT_FEED_ENCODING);
root_item->appendChild(feed);
} }
else {
qWarning("Detected empty URL when parsing input TXT (one URL per line) data.");
failed++;
}
emit parsingProgress(++completed, urls.size());
} }
// Now, XML is processed and we have result in form of pointer item structure. // Now, XML is processed and we have result in form of pointer item structure.
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
setRootItem(root_item); setRootItem(root_item);
emit layoutChanged(); emit layoutChanged();
emit parsingFinished(failed, succeded, false);
return true;
} }
FeedsImportExportModel::Mode FeedsImportExportModel::mode() const { FeedsImportExportModel::Mode FeedsImportExportModel::mode() const {

View File

@ -60,12 +60,12 @@ class FeedsImportExportModel : public QAbstractItemModel {
// Exports to OPML 2.0 // Exports to OPML 2.0
// NOTE: http://dev.opml.org/spec2.html // NOTE: http://dev.opml.org/spec2.html
bool exportToOMPL20(QByteArray &result); bool exportToOMPL20(QByteArray &result);
bool importAsOPML20(const QByteArray &data); void importAsOPML20(const QByteArray &data);
// Exports to plain text format // Exports to plain text format
// where there is one feed URL per line. // where there is one feed URL per line.
bool exportToTxtURLPerLine(QByteArray &result); bool exportToTxtURLPerLine(QByteArray &result);
bool importAsTxtURLPerLine(const QByteArray &data); void importAsTxtURLPerLine(const QByteArray &data);
Mode mode() const; Mode mode() const;
void setMode(const Mode &mode); void setMode(const Mode &mode);
@ -74,8 +74,15 @@ class FeedsImportExportModel : public QAbstractItemModel {
void checkAllItems(); void checkAllItems();
void uncheckAllItems(); void uncheckAllItems();
signals:
// These signals are emitted when user selects some data
// to be imported/parsed into the model.
void parsingStarted();
void parsingProgress(int completed, int total);
void parsingFinished(int count_failed, int count_succeeded, bool parsing_error);
private: private:
QHash<RootItem*, Qt::CheckState> m_checkStates; QHash<RootItem*,Qt::CheckState> m_checkStates;
RootItem *m_rootItem; RootItem *m_rootItem;
bool m_recursiveChange; bool m_recursiveChange;