Implement Downloads page
This change includes an update to the database, adding a downloaded column in Enclosures.
This commit is contained in:
parent
98bc7ffa61
commit
e9d20ec569
@ -50,7 +50,7 @@ bool Database::migrateTo1()
|
||||
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT, image TEXT, link TEXT, description TEXT, deleteAfterCount INTEGER, deleteAfterType INTEGER, subscribed INTEGER, lastUpdated INTEGER, new BOOL, notify BOOL);")));
|
||||
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT UNIQUE, title TEXT, content TEXT, created INTEGER, updated INTEGER, link TEXT, read bool, new bool, hasEnclosure BOOL, image 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(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Enclosures (feed TEXT, id TEXT, duration INTEGER, size INTEGER, title TEXT, type TEXT, url TEXT, playposition INTEGER);"))); //, filename 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);")));
|
||||
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Queue (listnr INTEGER, feed TEXT, id TEXT, playing BOOL);")));
|
||||
TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 1;")));
|
||||
return true;
|
||||
|
@ -141,15 +141,19 @@ Entry* DataManager::getEntry(QString id) const
|
||||
Entry* DataManager::getEntry(const EpisodeModel::Type type, const int entry_index) const
|
||||
{
|
||||
QSqlQuery entryQuery;
|
||||
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread) {
|
||||
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread || type == EpisodeModel::Downloaded) {
|
||||
|
||||
if (type == EpisodeModel::New) {
|
||||
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries WHERE new=:new ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries WHERE new=:new ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
entryQuery.bindValue(QStringLiteral(":new"), true);
|
||||
} else if (type == EpisodeModel::Unread) {
|
||||
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries WHERE read=:read ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries WHERE read=:read ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
entryQuery.bindValue(QStringLiteral(":read"), false);
|
||||
} else { // i.e. EpisodeModel::All
|
||||
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
} else if (type == EpisodeModel::All) {
|
||||
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
} else { // i.e. EpisodeModel::Downloaded
|
||||
entryQuery.prepare(QStringLiteral("SELECT * FROM Enclosures INNER JOIN Entries ON Enclosures.id = Entries.id WHERE downloaded=:downloaded ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
||||
entryQuery.bindValue(QStringLiteral(":downloaded"), true);
|
||||
}
|
||||
entryQuery.bindValue(QStringLiteral(":index"), entry_index);
|
||||
Database::instance().execute(entryQuery);
|
||||
@ -159,6 +163,7 @@ Entry* DataManager::getEntry(const EpisodeModel::Type type, const int entry_inde
|
||||
}
|
||||
QString id = entryQuery.value(QStringLiteral("id")).toString();
|
||||
return getEntry(id);
|
||||
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@ -181,15 +186,18 @@ int DataManager::entryCount(const Feed* feed) const
|
||||
int DataManager::entryCount(const EpisodeModel::Type type) const
|
||||
{
|
||||
QSqlQuery query;
|
||||
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread) {
|
||||
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread || type == EpisodeModel::Downloaded) {
|
||||
if (type == EpisodeModel::New) {
|
||||
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE new=:new;"));
|
||||
query.bindValue(QStringLiteral(":new"), true);
|
||||
} else if (type == EpisodeModel::Unread) {
|
||||
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE read=:read;"));
|
||||
query.bindValue(QStringLiteral(":read"), false);
|
||||
} else { // i.e. EpisodeModel::All
|
||||
} else if (type == EpisodeModel::All) {
|
||||
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries;"));
|
||||
} else { // i.e. EpisodeModel::Downloaded
|
||||
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Enclosures WHERE downloaded=:downloaded;"));
|
||||
query.bindValue(QStringLiteral(":downloaded"), true);
|
||||
}
|
||||
Database::instance().execute(query);
|
||||
if (!query.next())
|
||||
|
@ -71,6 +71,7 @@ Q_SIGNALS:
|
||||
|
||||
void unreadEntryCountChanged(const QString &url);
|
||||
void newEntryCountChanged(const QString &url);
|
||||
void downloadCountChanged(const QString &url);
|
||||
|
||||
private:
|
||||
DataManager();
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QSqlQuery>
|
||||
|
||||
#include "database.h"
|
||||
#include "datamanager.h"
|
||||
#include "enclosuredownloadjob.h"
|
||||
#include "entry.h"
|
||||
#include "fetcher.h"
|
||||
@ -35,18 +36,46 @@ Enclosure::Enclosure(Entry *entry)
|
||||
m_type = query.value(QStringLiteral("type")).toString();
|
||||
m_url = query.value(QStringLiteral("url")).toString();
|
||||
m_playposition = query.value(QStringLiteral("playposition")).toLongLong();
|
||||
m_status = query.value(QStringLiteral("downloaded")).toBool() ? Downloaded : Downloadable;
|
||||
m_playposition_dbsave = m_playposition;
|
||||
|
||||
// In principle the database contains this status, we check anyway in case
|
||||
// something changed on disk
|
||||
QFile file(path());
|
||||
if (file.exists()) {
|
||||
if(file.size() == m_size) {
|
||||
m_status = Downloaded;
|
||||
if (m_status == Downloadable) {
|
||||
// file is on disk, but was not expected, write to database
|
||||
// this should never happen
|
||||
m_status = Downloaded;
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET downloaded=:downloaded WHERE id=:id;"));
|
||||
query.bindValue(QStringLiteral(":id"), entry->id());
|
||||
query.bindValue(QStringLiteral(":downloaded"), true);
|
||||
Database::instance().execute(query);
|
||||
}
|
||||
} else {
|
||||
file.remove();
|
||||
m_status = Downloadable;
|
||||
if (m_status == Downloaded) {
|
||||
// file was downloaded, but there is a size mismatch
|
||||
// delete file and update status in database
|
||||
file.remove();
|
||||
m_status = Downloadable;
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET downloaded=:downloaded WHERE id=:id;"));
|
||||
query.bindValue(QStringLiteral(":id"), entry->id());
|
||||
query.bindValue(QStringLiteral(":downloaded"), false);
|
||||
Database::instance().execute(query);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_status = Downloadable;
|
||||
if (m_status == Downloaded) {
|
||||
// file was supposed to be on disk, but isn't there
|
||||
// update status and write to the database
|
||||
file.remove();
|
||||
m_status = Downloadable;
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET downloaded=:downloaded WHERE id=:id;"));
|
||||
query.bindValue(QStringLiteral(":id"), entry->id());
|
||||
query.bindValue(QStringLiteral(":downloaded"), false);
|
||||
Database::instance().execute(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,9 +92,7 @@ void Enclosure::download()
|
||||
|
||||
connect(downloadJob, &KJob::result, this, [this, downloadJob]() {
|
||||
if(downloadJob->error() == 0) {
|
||||
m_status = Downloaded;
|
||||
processDownloadedFile();
|
||||
|
||||
} else {
|
||||
m_status = Downloadable;
|
||||
if(downloadJob->error() != QNetworkReply::OperationCanceledError) {
|
||||
@ -81,6 +108,7 @@ void Enclosure::download()
|
||||
downloadJob->doKill();
|
||||
m_status = Downloadable;
|
||||
Q_EMIT statusChanged();
|
||||
Q_EMIT DataManager::instance().downloadCountChanged(m_entry->feed()->url());
|
||||
disconnect(this, &Enclosure::cancelDownload, this, nullptr);
|
||||
});
|
||||
|
||||
@ -95,7 +123,12 @@ void Enclosure::download()
|
||||
|
||||
void Enclosure::processDownloadedFile() {
|
||||
// This will be run if the enclosure has been downloaded successfully
|
||||
|
||||
m_status = Downloaded;
|
||||
QSqlQuery query;
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET downloaded=:downloaded WHERE id=:id;"));
|
||||
query.bindValue(QStringLiteral(":id"), m_entry->id());
|
||||
query.bindValue(QStringLiteral(":downloaded"), true);
|
||||
Database::instance().execute(query);
|
||||
// Unset "new" status of item
|
||||
if (m_entry->getNew()) m_entry->setNew(false);
|
||||
|
||||
@ -113,6 +146,8 @@ void Enclosure::processDownloadedFile() {
|
||||
query.bindValue(QStringLiteral(":size"), m_size);
|
||||
Database::instance().execute(query);
|
||||
}
|
||||
Q_EMIT DataManager::instance().downloadCountChanged(m_entry->feed()->url());
|
||||
|
||||
}
|
||||
|
||||
void Enclosure::deleteFile()
|
||||
@ -123,8 +158,13 @@ void Enclosure::deleteFile()
|
||||
QFile(path()).remove();
|
||||
// If file disappeared unexpectedly, then still change status to downloadable
|
||||
m_status = Downloadable;
|
||||
QSqlQuery query;
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET downloaded=:downloaded WHERE id=:id;"));
|
||||
query.bindValue(QStringLiteral(":id"), m_entry->id());
|
||||
query.bindValue(QStringLiteral(":downloaded"), false);
|
||||
Database::instance().execute(query);
|
||||
Q_EMIT statusChanged();
|
||||
|
||||
Q_EMIT DataManager::instance().downloadCountChanged(m_entry->feed()->url());
|
||||
}
|
||||
|
||||
QString Enclosure::path() const
|
||||
@ -136,7 +176,6 @@ Enclosure::Status Enclosure::status() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
qint64 Enclosure::playPosition() const{
|
||||
return m_playposition;
|
||||
}
|
||||
|
@ -57,5 +57,20 @@ void EpisodeModel::setType(EpisodeModel::Type type)
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
} else if (m_type == EpisodeModel::Unread) {
|
||||
connect(&DataManager::instance(), &DataManager::unreadEntryCountChanged, this, [this](const QString &url) {
|
||||
// we have to reset the entire model in case entries are removed or added
|
||||
// because we have no way of knowing where those entries will be added/removed
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
else if (m_type == EpisodeModel::Downloaded) { // TODO: this needs to be removed !!!!!!
|
||||
connect(&DataManager::instance(), &DataManager::downloadCountChanged, this, [this](const QString &url) {
|
||||
// we have to reset the entire model in case entries are removed or added
|
||||
// because we have no way of knowing where those entries will be added/removed
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -255,9 +255,9 @@ void Fetcher::processEnclosure(Syndication::EnclosurePtr enclosure, Syndication:
|
||||
query.next();
|
||||
|
||||
if (query.value(0).toInt() != 0)
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET feed=:feed, id=:id, duration=:duration, size=:size, title=:title, type=:type, url=:url, playposition=:playposition;"));
|
||||
query.prepare(QStringLiteral("UPDATE Enclosures SET feed=:feed, id=:id, duration=:duration, size=:size, title=:title, type=:type, url=:url;"));
|
||||
else
|
||||
query.prepare(QStringLiteral("INSERT INTO Enclosures VALUES (:feed, :id, :duration, :size, :title, :type, :url, :playposition);"));
|
||||
query.prepare(QStringLiteral("INSERT INTO Enclosures VALUES (:feed, :id, :duration, :size, :title, :type, :url, :playposition, :downloaded);"));
|
||||
|
||||
query.bindValue(QStringLiteral(":feed"), feedUrl);
|
||||
query.bindValue(QStringLiteral(":id"), entry->id());
|
||||
@ -267,6 +267,7 @@ void Fetcher::processEnclosure(Syndication::EnclosurePtr enclosure, Syndication:
|
||||
query.bindValue(QStringLiteral(":type"), enclosure->type());
|
||||
query.bindValue(QStringLiteral(":url"), enclosure->url());
|
||||
query.bindValue(QStringLiteral(":playposition"), 0);
|
||||
query.bindValue(QStringLiteral(":downloaded"), false);
|
||||
Database::instance().execute(query);
|
||||
}
|
||||
|
||||
|
61
src/qml/DownloadSwipePage.qml
Normal file
61
src/qml/DownloadSwipePage.qml
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtMultimedia 5.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.alligator 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
id: page
|
||||
|
||||
title: i18n("Downloads")
|
||||
padding: 0
|
||||
|
||||
header: Loader {
|
||||
id: headerLoader
|
||||
active: !Kirigami.Settings.isMobile
|
||||
sourceComponent: tabBarComponent
|
||||
property var swipeViewItem: swipeView
|
||||
}
|
||||
|
||||
footer: Loader {
|
||||
id: footerLoader
|
||||
active: Kirigami.Settings.isMobile
|
||||
sourceComponent: tabBarComponent
|
||||
property var swipeViewItem: swipeView
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tabBarComponent
|
||||
Controls.TabBar {
|
||||
id: tabBar
|
||||
position: Controls.TabBar.Footer
|
||||
currentIndex: swipeViewItem.currentIndex
|
||||
|
||||
Controls.TabButton {
|
||||
width: parent.parent.width/parent.count
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
text: i18n("Downloaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.SwipeView {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
currentIndex: Kirigami.Settings.isMobile ? footerLoader.item.currentIndex : headerLoader.item.currentIndex
|
||||
|
||||
EpisodeListPage {
|
||||
title: i18n("Downloaded")
|
||||
episodeType: EpisodeModel.Downloaded
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ Kirigami.ApplicationWindow {
|
||||
pageStack.initialPage: mainPagePool.loadPage(SettingsManager.lastOpenedPage === "FeedListPage" ? "qrc:/FeedListPage.qml"
|
||||
: SettingsManager.lastOpenedPage === "QueuePage" ? "qrc:/QueuePage.qml"
|
||||
: SettingsManager.lastOpenedPage === "EpisodeSwipePage" ? "qrc:/EpisodeSwipePage.qml"
|
||||
: SettingsManager.lastOpenedPage === "DownloadSwipePage" ? "qrc:/DownloadSwipePage.qml"
|
||||
: "qrc:/FeedListPage.qml")
|
||||
|
||||
globalDrawer: Kirigami.GlobalDrawer {
|
||||
@ -63,6 +64,15 @@ Kirigami.ApplicationWindow {
|
||||
SettingsManager.lastOpenedPage = "FeedListPage" // for persistency
|
||||
}
|
||||
},
|
||||
Kirigami.PagePoolAction {
|
||||
text: i18n("Downloads")
|
||||
iconName: "download"
|
||||
pagePool: mainPagePool
|
||||
page: "qrc:/DownloadSwipePage.qml"
|
||||
onTriggered: {
|
||||
SettingsManager.lastOpenedPage = "DownloadSwipePage" // for persistency
|
||||
}
|
||||
},
|
||||
Kirigami.PagePoolAction {
|
||||
text: i18n("Settings")
|
||||
iconName: "settings-configure"
|
||||
|
@ -15,6 +15,7 @@
|
||||
<file alias="QueuePage.qml">qml/QueuePage.qml</file>
|
||||
<file alias="EpisodeListPage.qml">qml/EpisodeListPage.qml</file>
|
||||
<file alias="EpisodeSwipePage.qml">qml/EpisodeSwipePage.qml</file>
|
||||
<file alias="DownloadSwipePage.qml">qml/DownloadSwipePage.qml</file>
|
||||
<file alias="GenericListHeader.qml">qml/GenericListHeader.qml</file>
|
||||
<file alias="GenericEntryDelegate.qml">qml/GenericEntryDelegate.qml</file>
|
||||
<file alias="logo.png">../logo.png</file>
|
||||
|
Loading…
x
Reference in New Issue
Block a user