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
powermanagementinterface.cpp
errorlogmodel.cpp
error.h
error.cpp
mpris2/mpris2.cpp
resources.qrc
)

View File

@ -39,8 +39,11 @@ Database::Database()
bool Database::migrate()
{
if (version() < 1)
int dbversion = version();
if (dbversion < 1)
TRUE_OR_RETURN(migrateTo1());
if (dbversion < 2)
TRUE_OR_RETURN(migrateTo2());
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 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 Errors (url TEXT, id TEXT, code INTEGER, message TEXT, date INTEGER);")));
TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 1;")));
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)
{
QSqlQuery q;

View File

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

View File

@ -5,9 +5,12 @@
*/
#include "datamanager.h"
#include "audiomanager.h"
#include "database.h"
#include "datamanagerlogging.h"
#include "entry.h"
#include "feed.h"
#include "fetcher.h"
#include "settingsmanager.h"
#include <QDateTime>
@ -574,4 +577,4 @@ void DataManager::updateQueueListnrs() const
query.bindValue(QStringLiteral(":id"), m_queuemap[i]);
Database::instance().execute(query);
}
}
}

View File

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

View File

@ -17,6 +17,7 @@
#include "downloadprogressmodel.h"
#include "enclosuredownloadjob.h"
#include "entry.h"
#include "error.h"
#include "errorlogmodel.h"
#include "fetcher.h"
@ -110,7 +111,7 @@ void Enclosure::download()
if (downloadJob->error() != QNetworkReply::OperationCanceledError) {
m_entry->feed()->setErrorId(downloadJob->error());
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);

View File

@ -13,6 +13,8 @@
#include <KFormat>
#include "error.h"
class Entry;
class Enclosure : public QObject
@ -66,7 +68,7 @@ Q_SIGNALS:
void playPositionChanged();
void durationChanged();
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:
void processDownloadedFile();
@ -82,4 +84,4 @@ private:
double m_downloadProgress = 0;
Status m_status;
KFormat m_kformat;
};
};

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
#include "episodemodel.h"
#include "datamanager.h"
#include "entry.h"
EpisodeModel::EpisodeModel()
: 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
#include "datamanager.h"
#include <QDateTime>
#include <QObject>
#include <QString>
@ -15,40 +14,35 @@ class Error : public QObject
{
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 id MEMBER id CONSTANT)
Q_PROPERTY(int code MEMBER code CONSTANT)
Q_PROPERTY(QString message MEMBER message CONSTANT)
Q_PROPERTY(QDateTime date MEMBER date CONSTANT)
Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(QString description READ description CONSTANT)
public:
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;
};
Error(Type type, const QString url, const QString id, const int code, const QString message, const QDateTime date);
QString title() const;
QString description() const;
Type type;
QString url;
QString id;
int code;
QString message;
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;"));
Database::instance().execute(query);
while (query.next()) {
QString id = query.value(QStringLiteral("id")).toString();
QString url = query.value(QStringLiteral("url")).toString();
Error *error = new Error(Error::dbToType(query.value(QStringLiteral("type")).toInt()),
query.value(QStringLiteral("url")).toString(),
query.value(QStringLiteral("id")).toString(),
Error *error = new Error(url,
id,
query.value(QStringLiteral("code")).toInt(),
query.value(QStringLiteral("message")).toString(),
QDateTime::fromSecsSinceEpoch(query.value(QStringLiteral("date")).toInt()));
@ -54,19 +53,20 @@ int ErrorLogModel::rowCount(const QModelIndex &parent) const
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;
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);
m_errors.prepend(error);
endInsertRows();
// Also add error to database
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(":id"), error->id);
query.bindValue(QStringLiteral(":code"), error->code);
@ -91,4 +91,4 @@ void ErrorLogModel::clearAll()
QSqlQuery query;
query.prepare(QStringLiteral("DELETE FROM Errors;"));
Database::instance().execute(query);
}
}

View File

@ -32,7 +32,7 @@ public:
Q_INVOKABLE void clearAll();
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:
void newErrorLogged(Error *error);

View File

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

View File

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

View File

@ -90,7 +90,7 @@ void Fetcher::retrieveFeed(const QString &url)
if (reply->error()) {
qWarning() << "Error fetching feed";
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 {
QByteArray data = reply->readAll();
Syndication::DocumentSource *document = new Syndication::DocumentSource(data, url);
@ -452,4 +452,4 @@ QNetworkReply *Fetcher::head(QNetworkRequest &request) const
void Fetcher::setHeader(QNetworkRequest &request) const
{
request.setRawHeader("User-Agent", "Kasts/0.1; Syndication");
}
}

View File

@ -14,6 +14,8 @@
#include <QUrl>
#include <Syndication/Syndication>
#include "error.h"
class Fetcher : public QObject
{
Q_OBJECT
@ -35,6 +37,7 @@ public:
Q_INVOKABLE QString image(const QString &url) const;
void removeImage(const QString &url);
Q_INVOKABLE QNetworkReply *download(const QString &url, const QString &fileName) const;
QString imagePath(const QString &url) const;
QString enclosurePath(const QString &url) const;
@ -48,7 +51,7 @@ Q_SIGNALS:
const QString &description,
const QDateTime &lastUpdated);
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 downloadFinished(QString url) const;
void downloadFileSizeUpdated(QString url, int fileSize) const;
@ -77,4 +80,4 @@ private:
int m_updateProgress;
int m_updateTotal;
bool m_updating;
};
};

View File

@ -11,6 +11,7 @@
#include <QLoggingCategory>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>
#include <QQuickView>
#include <QString>
@ -35,8 +36,10 @@
#include "datamanager.h"
#include "downloadprogressmodel.h"
#include "entriesmodel.h"
#include "entry.h"
#include "episodemodel.h"
#include "errorlogmodel.h"
#include "feed.h"
#include "feedsmodel.h"
#include "fetcher.h"
#include "kasts-version.h"
@ -129,6 +132,7 @@ int main(int argc, char *argv[])
qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "AudioManager", &AudioManager::instance());
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
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.Layouts 1.14
import QtGraphicalEffects 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kasts 1.0
@ -40,7 +41,7 @@ Kirigami.ScrollablePage {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
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
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
@ -54,7 +55,7 @@ Kirigami.ScrollablePage {
opacity: 1
}
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
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
@ -82,4 +83,4 @@ Kirigami.ScrollablePage {
visible: errorList.count > 0
onTriggered: ErrorLogModel.clearAll()
}
}
}

View File

@ -203,7 +203,7 @@ Kirigami.ApplicationWindow {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: bottomMessageSpacing
bottomMargin: bottomMessageSpacing + ( inlineMessage.visible ? inlineMessage.height + Kirigami.Units.largeSpacing : 0 )
}
}
@ -214,7 +214,7 @@ Kirigami.ApplicationWindow {
right: parent.right
left: parent.left
margins: Kirigami.Units.gridUnit
bottomMargin: bottomMessageSpacing + ( updateNotification.visible ? updateNotification.height + Kirigami.Units.largeSpacing : 0 )
bottomMargin: bottomMessageSpacing
}
type: Kirigami.MessageType.Error
showCloseButton: true
@ -222,7 +222,7 @@ Kirigami.ApplicationWindow {
Connections {
target: ErrorLogModel
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;
}
}