Refactor Error implementation and add Error::Type

- This refactoring also includes a cleanup of a lot of header includes to
  avoid circular dependencies.
- The error message will now be shown below the info message.
- Add database migration (for Errors)
This commit is contained in:
Bart De Vries 2021-05-14 16:46:54 +02:00
parent 719879072e
commit 49977adc38
23 changed files with 178 additions and 63 deletions

View File

@ -19,7 +19,7 @@ set(SRCS_base
audiomanager.cpp audiomanager.cpp
powermanagementinterface.cpp powermanagementinterface.cpp
errorlogmodel.cpp errorlogmodel.cpp
error.h error.cpp
mpris2/mpris2.cpp mpris2/mpris2.cpp
resources.qrc resources.qrc
) )

View File

@ -39,8 +39,11 @@ Database::Database()
bool Database::migrate() bool Database::migrate()
{ {
if (version() < 1) int dbversion = version();
if (dbversion < 1)
TRUE_OR_RETURN(migrateTo1()); TRUE_OR_RETURN(migrateTo1());
if (dbversion < 2)
TRUE_OR_RETURN(migrateTo2());
return true; return true;
} }
@ -56,13 +59,22 @@ bool Database::migrateTo1()
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Authors (feed TEXT, id TEXT, name TEXT, uri TEXT, email TEXT);"))); TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Authors (feed TEXT, id TEXT, name TEXT, uri TEXT, email TEXT);")));
TRUE_OR_RETURN( TRUE_OR_RETURN(
execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Enclosures (feed TEXT, id TEXT, duration INTEGER, size INTEGER, title TEXT, type TEXT, url TEXT, " execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Enclosures (feed TEXT, id TEXT, duration INTEGER, size INTEGER, title TEXT, type TEXT, url TEXT, "
"playposition INTEGER, downloaded BOOL);"))); //, filename TEXT);"))); "playposition INTEGER, downloaded BOOL);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Queue (listnr INTEGER, feed TEXT, id TEXT, playing BOOL);"))); TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Queue (listnr INTEGER, feed TEXT, id TEXT, playing BOOL);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Errors (url TEXT, id TEXT, code INTEGER, message TEXT, date INTEGER);"))); TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Errors (url TEXT, id TEXT, code INTEGER, message TEXT, date INTEGER);")));
TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 1;"))); TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 1;")));
return true; return true;
} }
bool Database::migrateTo2()
{
qDebug() << "Migrating database to version 2";
TRUE_OR_RETURN(execute(QStringLiteral("DROP TABLE Errors;")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Errors (type INTEGER, url TEXT, id TEXT, code INTEGER, message TEXT, date INTEGER);")));
TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 2;")));
return true;
}
bool Database::execute(const QString &query) bool Database::execute(const QString &query)
{ {
QSqlQuery q; QSqlQuery q;

View File

@ -28,5 +28,6 @@ private:
bool migrate(); bool migrate();
bool migrateTo1(); bool migrateTo1();
bool migrateTo2();
void cleanup(); void cleanup();
}; };

View File

@ -5,9 +5,12 @@
*/ */
#include "datamanager.h" #include "datamanager.h"
#include "audiomanager.h" #include "audiomanager.h"
#include "database.h" #include "database.h"
#include "datamanagerlogging.h" #include "datamanagerlogging.h"
#include "entry.h"
#include "feed.h"
#include "fetcher.h" #include "fetcher.h"
#include "settingsmanager.h" #include "settingsmanager.h"
#include <QDateTime> #include <QDateTime>

View File

@ -6,9 +6,10 @@
#pragma once #pragma once
#include "entry.h"
#include "episodemodel.h" #include "episodemodel.h"
#include "feed.h"
class Entry;
class Feed;
class DataManager : public QObject class DataManager : public QObject
{ {

View File

@ -17,6 +17,7 @@
#include "downloadprogressmodel.h" #include "downloadprogressmodel.h"
#include "enclosuredownloadjob.h" #include "enclosuredownloadjob.h"
#include "entry.h" #include "entry.h"
#include "error.h"
#include "errorlogmodel.h" #include "errorlogmodel.h"
#include "fetcher.h" #include "fetcher.h"
@ -110,7 +111,7 @@ void Enclosure::download()
if (downloadJob->error() != QNetworkReply::OperationCanceledError) { if (downloadJob->error() != QNetworkReply::OperationCanceledError) {
m_entry->feed()->setErrorId(downloadJob->error()); m_entry->feed()->setErrorId(downloadJob->error());
m_entry->feed()->setErrorString(downloadJob->errorString()); m_entry->feed()->setErrorString(downloadJob->errorString());
Q_EMIT downloadError(m_entry->feed()->url(), m_entry->id(), downloadJob->error(), downloadJob->errorString()); Q_EMIT downloadError(Error::Type::MediaDownload, m_entry->feed()->url(), m_entry->id(), downloadJob->error(), downloadJob->errorString());
} }
} }
disconnect(this, &Enclosure::cancelDownload, this, nullptr); disconnect(this, &Enclosure::cancelDownload, this, nullptr);

View File

@ -13,6 +13,8 @@
#include <KFormat> #include <KFormat>
#include "error.h"
class Entry; class Entry;
class Enclosure : public QObject class Enclosure : public QObject
@ -66,7 +68,7 @@ Q_SIGNALS:
void playPositionChanged(); void playPositionChanged();
void durationChanged(); void durationChanged();
void sizeChanged(); void sizeChanged();
void downloadError(const QString &url, const QString &id, const int errorId, const QString &errorString); void downloadError(const Error::Type type, const QString &url, const QString &id, const int errorId, const QString &errorString);
private: private:
void processDownloadedFile(); void processDownloadedFile();

View File

@ -10,6 +10,7 @@
#include "datamanager.h" #include "datamanager.h"
#include "entriesmodel.h" #include "entriesmodel.h"
#include "entry.h" #include "entry.h"
#include "feed.h"
EntriesModel::EntriesModel(Feed *feed) EntriesModel::EntriesModel(Feed *feed)
: QAbstractListModel(feed) : QAbstractListModel(feed)

View File

@ -12,7 +12,7 @@
#include <QObject> #include <QObject>
#include <QVariant> #include <QVariant>
#include "feed.h" class Feed;
class EntriesModel : public QAbstractListModel class EntriesModel : public QAbstractListModel
{ {

View File

@ -13,6 +13,7 @@
#include "database.h" #include "database.h"
#include "datamanager.h" #include "datamanager.h"
#include "feed.h"
#include "fetcher.h" #include "fetcher.h"
Entry::Entry(Feed *feed, const QString &id) Entry::Entry(Feed *feed, const QString &id)

View File

@ -16,7 +16,8 @@
#include "author.h" #include "author.h"
#include "enclosure.h" #include "enclosure.h"
#include "feed.h"
class Feed;
class Entry : public QObject class Entry : public QObject
{ {

View File

@ -6,6 +6,7 @@
#include "episodemodel.h" #include "episodemodel.h"
#include "datamanager.h" #include "datamanager.h"
#include "entry.h"
EpisodeModel::EpisodeModel() EpisodeModel::EpisodeModel()
: QAbstractListModel(nullptr) : QAbstractListModel(nullptr)

81
src/error.cpp Normal file
View File

@ -0,0 +1,81 @@
/**
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <KLocalizedString>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "datamanager.h"
#include "entry.h"
#include "error.h"
#include "feed.h"
Error::Error(const Type type, const QString url, const QString id, const int code, const QString message, const QDateTime date)
: QObject(nullptr)
{
this->type = type;
this->url = url;
this->id = id;
this->code = code;
this->message = message;
this->date = date;
};
QString Error::title() const
{
QString title;
if (!id.isEmpty()) {
if (DataManager::instance().getEntry(id))
title = DataManager::instance().getEntry(id)->title();
} else if (!url.isEmpty()) {
if (DataManager::instance().getFeed(url))
title = DataManager::instance().getFeed(url)->name();
}
return title;
}
QString Error::description() const
{
switch (type) {
case Error::Type::FeedUpdate:
return i18n("Podcast Update Error");
case Error::Type::MediaDownload:
return i18n("Media Download Error");
case Error::Type::MeteredNotAllowed:
return i18n("Update Not Allowed on Metered Connection");
default:
return QString();
}
}
int Error::typeToDb(Error::Type type)
{
switch (type) {
case Error::Type::FeedUpdate:
return 0;
case Error::Type::MediaDownload:
return 1;
case Error::Type::MeteredNotAllowed:
return 2;
default:
return -1;
}
}
Error::Type Error::dbToType(int value)
{
switch (value) {
case 0:
return Error::Type::FeedUpdate;
case 1:
return Error::Type::MediaDownload;
case 2:
return Error::Type::MeteredNotAllowed;
default:
return Error::Type::Unknown;
}
}

View File

@ -6,7 +6,6 @@
#pragma once #pragma once
#include "datamanager.h"
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
@ -15,40 +14,35 @@ class Error : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
enum Type {
Unknown,
FeedUpdate,
MediaDownload,
MeteredNotAllowed,
};
Q_ENUM(Type)
static int typeToDb(Type type); // needed to translate Error::Type values to int for sqlite
static Type dbToType(int value); // needed to translate from int to Error::Type values for sqlite
Q_PROPERTY(QString url MEMBER url CONSTANT) Q_PROPERTY(QString url MEMBER url CONSTANT)
Q_PROPERTY(QString id MEMBER id CONSTANT) Q_PROPERTY(QString id MEMBER id CONSTANT)
Q_PROPERTY(int code MEMBER code CONSTANT) Q_PROPERTY(int code MEMBER code CONSTANT)
Q_PROPERTY(QString message MEMBER message CONSTANT) Q_PROPERTY(QString message MEMBER message CONSTANT)
Q_PROPERTY(QDateTime date MEMBER date CONSTANT) Q_PROPERTY(QDateTime date MEMBER date CONSTANT)
Q_PROPERTY(QString title READ title CONSTANT) Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(QString description READ description CONSTANT)
public: Error(Type type, const QString url, const QString id, const int code, const QString message, const QDateTime date);
Error(const QString url, const QString id, const int code, const QString message, const QDateTime date)
: QObject(nullptr)
{
this->url = url;
this->id = id;
this->code = code;
this->message = message;
this->date = date;
};
QString title() const;
QString description() const;
Type type;
QString url; QString url;
QString id; QString id;
int code; int code;
QString message; QString message;
QDateTime date; QDateTime date;
QString title() const
{
QString title;
if (!id.isEmpty()) {
if (DataManager::instance().getEntry(id))
title = DataManager::instance().getEntry(id)->title();
} else if (!url.isEmpty()) {
if (DataManager::instance().getFeed(url))
title = DataManager::instance().getFeed(url)->name();
}
return title;
}
}; };

View File

@ -21,11 +21,10 @@ ErrorLogModel::ErrorLogModel()
query.prepare(QStringLiteral("SELECT * FROM Errors ORDER BY date DESC;")); query.prepare(QStringLiteral("SELECT * FROM Errors ORDER BY date DESC;"));
Database::instance().execute(query); Database::instance().execute(query);
while (query.next()) { while (query.next()) {
QString id = query.value(QStringLiteral("id")).toString(); Error *error = new Error(Error::dbToType(query.value(QStringLiteral("type")).toInt()),
QString url = query.value(QStringLiteral("url")).toString(); query.value(QStringLiteral("url")).toString(),
query.value(QStringLiteral("id")).toString(),
Error *error = new Error(url,
id,
query.value(QStringLiteral("code")).toInt(), query.value(QStringLiteral("code")).toInt(),
query.value(QStringLiteral("message")).toString(), query.value(QStringLiteral("message")).toString(),
QDateTime::fromSecsSinceEpoch(query.value(QStringLiteral("date")).toInt())); QDateTime::fromSecsSinceEpoch(query.value(QStringLiteral("date")).toInt()));
@ -54,19 +53,20 @@ int ErrorLogModel::rowCount(const QModelIndex &parent) const
return m_errors.count(); return m_errors.count();
} }
void ErrorLogModel::monitorErrorMessages(const QString &url, const QString &id, const int errorCode, const QString &errorString) void ErrorLogModel::monitorErrorMessages(const Error::Type type, const QString &url, const QString &id, const int errorCode, const QString &errorString)
{ {
qDebug() << "Error happened:" << url << id << errorCode << errorString; qDebug() << "Error happened:" << type << url << id << errorCode << errorString;
QString title; QString title;
Error *error = new Error(url, id, errorCode, errorString, QDateTime::currentDateTime()); Error *error = new Error(type, url, id, errorCode, errorString, QDateTime::currentDateTime());
beginInsertRows(QModelIndex(), 0, 0); beginInsertRows(QModelIndex(), 0, 0);
m_errors.prepend(error); m_errors.prepend(error);
endInsertRows(); endInsertRows();
// Also add error to database // Also add error to database
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("INSERT INTO Errors VALUES (:url, :id, :code, :message, :date);")); query.prepare(QStringLiteral("INSERT INTO Errors VALUES (:type, :url, :id, :code, :message, :date);"));
query.bindValue(QStringLiteral(":type"), Error::typeToDb(error->type));
query.bindValue(QStringLiteral(":url"), error->url); query.bindValue(QStringLiteral(":url"), error->url);
query.bindValue(QStringLiteral(":id"), error->id); query.bindValue(QStringLiteral(":id"), error->id);
query.bindValue(QStringLiteral(":code"), error->code); query.bindValue(QStringLiteral(":code"), error->code);

View File

@ -32,7 +32,7 @@ public:
Q_INVOKABLE void clearAll(); Q_INVOKABLE void clearAll();
public: public:
void monitorErrorMessages(const QString &url, const QString &id, const int errorCode, const QString &errorString); void monitorErrorMessages(const Error::Type type, const QString &url, const QString &id, const int errorCode, const QString &errorString);
Q_SIGNALS: Q_SIGNALS:
void newErrorLogged(Error *error); void newErrorLogged(Error *error);

View File

@ -7,9 +7,11 @@
#include <QVariant> #include <QVariant>
#include "author.h"
#include "database.h" #include "database.h"
#include "datamanager.h" #include "datamanager.h"
#include "entriesmodel.h" #include "entriesmodel.h"
#include "error.h"
#include "feed.h" #include "feed.h"
#include "feedlogging.h" #include "feedlogging.h"
#include "fetcher.h" #include "fetcher.h"
@ -57,14 +59,18 @@ Feed::Feed(const QString &feedurl)
setErrorString(QLatin1String("")); setErrorString(QLatin1String(""));
} }
}); });
connect(&Fetcher::instance(), &Fetcher::error, this, [this](const QString &url, const QString &id, int errorId, const QString &errorString) { connect(&Fetcher::instance(),
Q_UNUSED(id) &Fetcher::error,
if (url == m_url) { this,
setErrorId(errorId); [this](const Error::Type type, const QString &url, const QString &id, int errorId, const QString &errorString) {
setErrorString(errorString); Q_UNUSED(type)
setRefreshing(false); Q_UNUSED(id)
} if (url == m_url) {
}); setErrorId(errorId);
setErrorString(errorString);
setRefreshing(false);
}
});
connect(&Fetcher::instance(), &Fetcher::feedUpdateFinished, this, [this](const QString &url) { connect(&Fetcher::instance(), &Fetcher::feedUpdateFinished, this, [this](const QString &url) {
if (url == m_url) { if (url == m_url) {
setRefreshing(false); setRefreshing(false);

View File

@ -9,6 +9,8 @@
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QString>
#include <QVector>
#include "author.h" #include "author.h"

View File

@ -90,7 +90,7 @@ void Fetcher::retrieveFeed(const QString &url)
if (reply->error()) { if (reply->error()) {
qWarning() << "Error fetching feed"; qWarning() << "Error fetching feed";
qWarning() << reply->errorString(); qWarning() << reply->errorString();
Q_EMIT error(url, QString(), reply->error(), reply->errorString()); Q_EMIT error(Error::Type::FeedUpdate, url, QString(), reply->error(), reply->errorString());
} else { } else {
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
Syndication::DocumentSource *document = new Syndication::DocumentSource(data, url); Syndication::DocumentSource *document = new Syndication::DocumentSource(data, url);

View File

@ -14,6 +14,8 @@
#include <QUrl> #include <QUrl>
#include <Syndication/Syndication> #include <Syndication/Syndication>
#include "error.h"
class Fetcher : public QObject class Fetcher : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -35,6 +37,7 @@ public:
Q_INVOKABLE QString image(const QString &url) const; Q_INVOKABLE QString image(const QString &url) const;
void removeImage(const QString &url); void removeImage(const QString &url);
Q_INVOKABLE QNetworkReply *download(const QString &url, const QString &fileName) const; Q_INVOKABLE QNetworkReply *download(const QString &url, const QString &fileName) const;
QString imagePath(const QString &url) const; QString imagePath(const QString &url) const;
QString enclosurePath(const QString &url) const; QString enclosurePath(const QString &url) const;
@ -48,7 +51,7 @@ Q_SIGNALS:
const QString &description, const QString &description,
const QDateTime &lastUpdated); const QDateTime &lastUpdated);
void feedUpdateFinished(const QString &url); void feedUpdateFinished(const QString &url);
void error(const QString &url, const QString &id, const int errorId, const QString &errorString); void error(Error::Type type, const QString &url, const QString &id, const int errorId, const QString &errorString);
void entryAdded(const QString &feedurl, const QString &id); void entryAdded(const QString &feedurl, const QString &id);
void downloadFinished(QString url) const; void downloadFinished(QString url) const;
void downloadFileSizeUpdated(QString url, int fileSize) const; void downloadFileSizeUpdated(QString url, int fileSize) const;

View File

@ -11,6 +11,7 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <QQuickStyle> #include <QQuickStyle>
#include <QQuickView> #include <QQuickView>
#include <QString> #include <QString>
@ -35,8 +36,10 @@
#include "datamanager.h" #include "datamanager.h"
#include "downloadprogressmodel.h" #include "downloadprogressmodel.h"
#include "entriesmodel.h" #include "entriesmodel.h"
#include "entry.h"
#include "episodemodel.h" #include "episodemodel.h"
#include "errorlogmodel.h" #include "errorlogmodel.h"
#include "feed.h"
#include "feedsmodel.h" #include "feedsmodel.h"
#include "fetcher.h" #include "fetcher.h"
#include "kasts-version.h" #include "kasts-version.h"
@ -129,6 +132,7 @@ int main(int argc, char *argv[])
qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "AudioManager", &AudioManager::instance()); qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "AudioManager", &AudioManager::instance());
qRegisterMetaType<Entry *>("const Entry*"); // "hack" to make qml understand Entry* qRegisterMetaType<Entry *>("const Entry*"); // "hack" to make qml understand Entry*
qRegisterMetaType<Feed *>("const Feed*"); // "hack" to make qml understand Feed*
// Make sure that settings are saved before the application exits // Make sure that settings are saved before the application exits
QObject::connect(&app, &QCoreApplication::aboutToQuit, SettingsManager::self(), &SettingsManager::save); QObject::connect(&app, &QCoreApplication::aboutToQuit, SettingsManager::self(), &SettingsManager::save);

View File

@ -8,6 +8,7 @@ import QtQuick 2.14
import QtQuick.Controls 2.14 as Controls import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.15 import QtGraphicalEffects 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.kasts 1.0 import org.kde.kasts 1.0
@ -40,7 +41,7 @@ Kirigami.ScrollablePage {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Controls.Label { Controls.Label {
text: ( (error.id) ? i18n("Media Download Error") : i18n("Podcast Update Error") ) + " · " + error.date.toLocaleDateString(Qt.locale(), Locale.NarrowFormat) + " · " + error.date.toLocaleTimeString(Qt.locale(), Locale.NarrowFormat) text: error.description + " · " + error.date.toLocaleDateString(Qt.locale(), Locale.NarrowFormat) + " · " + error.date.toLocaleTimeString(Qt.locale(), Locale.NarrowFormat)
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
font: Kirigami.Theme.smallFont font: Kirigami.Theme.smallFont
@ -54,7 +55,7 @@ Kirigami.ScrollablePage {
opacity: 1 opacity: 1
} }
Controls.Label { Controls.Label {
text: i18n("Error Code: ") + error.code + (error.message ? " · " + error.message : "") text: i18n("Error Code: ") + error.code + (error.message ? " · " + error.message : "")
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
font: Kirigami.Theme.smallFont font: Kirigami.Theme.smallFont

View File

@ -203,7 +203,7 @@ Kirigami.ApplicationWindow {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
bottom: parent.bottom bottom: parent.bottom
bottomMargin: bottomMessageSpacing bottomMargin: bottomMessageSpacing + ( inlineMessage.visible ? inlineMessage.height + Kirigami.Units.largeSpacing : 0 )
} }
} }
@ -214,7 +214,7 @@ Kirigami.ApplicationWindow {
right: parent.right right: parent.right
left: parent.left left: parent.left
margins: Kirigami.Units.gridUnit margins: Kirigami.Units.gridUnit
bottomMargin: bottomMessageSpacing + ( updateNotification.visible ? updateNotification.height + Kirigami.Units.largeSpacing : 0 ) bottomMargin: bottomMessageSpacing
} }
type: Kirigami.MessageType.Error type: Kirigami.MessageType.Error
showCloseButton: true showCloseButton: true
@ -222,7 +222,7 @@ Kirigami.ApplicationWindow {
Connections { Connections {
target: ErrorLogModel target: ErrorLogModel
function onNewErrorLogged(error) { function onNewErrorLogged(error) {
inlineMessage.text = error.id ? i18n("Media Download Error") : i18n("Podcast Update Error") + "\n" + i18n("Check Error Log Tab (under Downloads) for more details."); inlineMessage.text = error.description + "\n" + i18n("Check Error Log Tab (under Downloads) for more details");
inlineMessage.visible = true; inlineMessage.visible = true;
} }
} }