2021-04-02 17:06:27 +02:00
|
|
|
/**
|
2021-04-03 19:29:40 +02:00
|
|
|
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
2021-04-02 17:06:27 +02:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
|
|
*/
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
#include "datamanager.h"
|
2021-05-28 23:16:35 +02:00
|
|
|
#include "audiomanager.h"
|
2021-05-01 21:35:37 +02:00
|
|
|
#include "database.h"
|
2021-06-19 13:29:47 +02:00
|
|
|
#include "datamanagerlogging.h"
|
2021-05-01 21:35:37 +02:00
|
|
|
#include "fetcher.h"
|
|
|
|
#include "settingsmanager.h"
|
2021-04-02 22:31:34 +02:00
|
|
|
#include <QDateTime>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QSqlDatabase>
|
|
|
|
#include <QSqlError>
|
|
|
|
#include <QStandardPaths>
|
|
|
|
#include <QUrl>
|
|
|
|
#include <QXmlStreamReader>
|
|
|
|
#include <QXmlStreamWriter>
|
2021-04-02 17:06:27 +02:00
|
|
|
|
|
|
|
DataManager::DataManager()
|
|
|
|
{
|
2021-05-01 21:35:37 +02:00
|
|
|
connect(
|
|
|
|
&Fetcher::instance(),
|
|
|
|
&Fetcher::feedDetailsUpdated,
|
|
|
|
this,
|
|
|
|
[this](const QString &url, const QString &name, const QString &image, const QString &link, const QString &description, const QDateTime &lastUpdated) {
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Start updating feed details" << m_feeds;
|
2021-05-01 21:35:37 +02:00
|
|
|
Feed *feed = getFeed(url);
|
|
|
|
if (feed != nullptr) {
|
|
|
|
feed->setName(name);
|
|
|
|
feed->setImage(image);
|
|
|
|
feed->setLink(link);
|
|
|
|
feed->setDescription(description);
|
|
|
|
feed->setLastUpdated(lastUpdated);
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Retrieving authors";
|
2021-05-01 21:35:37 +02:00
|
|
|
feed->updateAuthors();
|
2021-05-08 16:13:28 +02:00
|
|
|
// For feeds that have just been added, this is probably the point
|
|
|
|
// where the Feed object gets created; let's set refreshing to
|
|
|
|
// true in order to show user feedback that the feed is still
|
|
|
|
// being fetched
|
|
|
|
feed->setRefreshing(true);
|
2021-05-01 21:35:37 +02:00
|
|
|
}
|
|
|
|
});
|
2021-04-03 11:44:08 +02:00
|
|
|
connect(&Fetcher::instance(), &Fetcher::entryAdded, this, [this](const QString &feedurl, const QString &id) {
|
|
|
|
// Only add the new entry to m_entries
|
|
|
|
// we will repopulate m_entrymap once all new entries have been added,
|
|
|
|
// such that m_entrymap will show all new entries in the correct order
|
|
|
|
m_entries[id] = nullptr;
|
|
|
|
Q_EMIT entryAdded(feedurl, id);
|
|
|
|
});
|
|
|
|
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](const QString &feedurl) {
|
|
|
|
// Update m_entrymap for feedurl, such that the new and old entries show
|
|
|
|
// up in the correct order
|
|
|
|
// TODO: put this code into a separate method and re-use this in the constructor
|
|
|
|
QSqlQuery query;
|
|
|
|
m_entrymap[feedurl].clear();
|
|
|
|
query.prepare(QStringLiteral("SELECT id FROM Entries WHERE feed=:feed ORDER BY updated DESC;"));
|
|
|
|
query.bindValue(QStringLiteral(":feed"), feedurl);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
while (query.next()) {
|
|
|
|
m_entrymap[feedurl] += query.value(QStringLiteral("id")).toString();
|
|
|
|
}
|
2021-04-09 21:43:29 +02:00
|
|
|
|
|
|
|
// Check for "new" entries
|
2021-04-10 08:46:14 +02:00
|
|
|
if (SettingsManager::self()->autoQueue()) {
|
2021-04-09 21:43:29 +02:00
|
|
|
query.prepare(QStringLiteral("SELECT id FROM Entries WHERE feed=:feed AND new=:new;"));
|
|
|
|
query.bindValue(QStringLiteral(":feed"), feedurl);
|
|
|
|
query.bindValue(QStringLiteral(":new"), true);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
while (query.next()) {
|
2021-05-01 21:00:12 +02:00
|
|
|
QString id = query.value(QStringLiteral("id")).toString();
|
2021-04-15 23:10:40 +02:00
|
|
|
addToQueue(feedurl, id);
|
2021-04-10 08:46:14 +02:00
|
|
|
if (SettingsManager::self()->autoDownload()) {
|
2021-05-07 22:09:15 +02:00
|
|
|
if (getEntry(id) && getEntry(id)->hasEnclosure() && getEntry(id)->enclosure()) {
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Start downloading" << getEntry(id)->title();
|
2021-04-09 23:39:41 +02:00
|
|
|
getEntry(id)->enclosure()->download();
|
2021-04-10 08:46:14 +02:00
|
|
|
}
|
2021-04-09 23:39:41 +02:00
|
|
|
}
|
2021-04-09 21:43:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 11:44:08 +02:00
|
|
|
Q_EMIT feedEntriesUpdated(feedurl);
|
2021-04-02 22:31:34 +02:00
|
|
|
});
|
|
|
|
|
2021-04-02 17:06:27 +02:00
|
|
|
// Only read unique feedurls and entry ids from the database.
|
2021-04-02 22:31:34 +02:00
|
|
|
// The feed and entry datastructures will be loaded lazily.
|
2021-04-02 17:06:27 +02:00
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("SELECT url FROM Feeds;"));
|
|
|
|
Database::instance().execute(query);
|
|
|
|
while (query.next()) {
|
|
|
|
m_feedmap += query.value(QStringLiteral("url")).toString();
|
2021-04-22 10:53:02 +02:00
|
|
|
m_feeds[query.value(QStringLiteral("url")).toString()] = nullptr;
|
2021-04-02 17:06:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (auto &feedurl : m_feedmap) {
|
2021-04-03 11:44:08 +02:00
|
|
|
query.prepare(QStringLiteral("SELECT id FROM Entries WHERE feed=:feed ORDER BY updated DESC;"));
|
2021-04-02 17:06:27 +02:00
|
|
|
query.bindValue(QStringLiteral(":feed"), feedurl);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
while (query.next()) {
|
|
|
|
m_entrymap[feedurl] += query.value(QStringLiteral("id")).toString();
|
2021-04-03 11:44:08 +02:00
|
|
|
m_entries[query.value(QStringLiteral("id")).toString()] = nullptr;
|
2021-04-02 17:06:27 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-05 20:12:42 +02:00
|
|
|
// qCDebug(kastsDataManager) << "entrymap contains:" << m_entrymap;
|
2021-04-03 19:29:40 +02:00
|
|
|
|
|
|
|
query.prepare(QStringLiteral("SELECT id FROM Queue ORDER BY listnr;"));
|
|
|
|
Database::instance().execute(query);
|
|
|
|
while (query.next()) {
|
|
|
|
m_queuemap += query.value(QStringLiteral("id")).toString();
|
|
|
|
}
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Queuemap contains:" << m_queuemap;
|
2021-04-02 17:06:27 +02:00
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Feed *DataManager::getFeed(const int index) const
|
2021-04-02 17:06:27 +02:00
|
|
|
{
|
|
|
|
return getFeed(m_feedmap[index]);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Feed *DataManager::getFeed(const QString &feedurl) const
|
2021-04-02 17:06:27 +02:00
|
|
|
{
|
|
|
|
if (m_feeds[feedurl] == nullptr)
|
|
|
|
loadFeed(feedurl);
|
|
|
|
return m_feeds[feedurl];
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *DataManager::getEntry(const int feed_index, const int entry_index) const
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
|
|
|
return getEntry(m_entrymap[m_feedmap[feed_index]][entry_index]);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *DataManager::getEntry(const Feed *feed, const int entry_index) const
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
|
|
|
return getEntry(m_entrymap[feed->url()][entry_index]);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *DataManager::getEntry(const QString &id) const
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
|
|
|
if (m_entries[id] == nullptr)
|
|
|
|
loadEntry(id);
|
|
|
|
return m_entries[id];
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *DataManager::getEntry(const EpisodeModel::Type type, const int entry_index) const
|
2021-04-17 22:54:35 +02:00
|
|
|
{
|
|
|
|
QSqlQuery entryQuery;
|
2021-04-18 21:56:33 +02:00
|
|
|
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread || type == EpisodeModel::Downloaded) {
|
2021-04-17 22:54:35 +02:00
|
|
|
if (type == EpisodeModel::New) {
|
2021-04-18 21:56:33 +02:00
|
|
|
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries WHERE new=:new ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
2021-04-17 22:54:35 +02:00
|
|
|
entryQuery.bindValue(QStringLiteral(":new"), true);
|
2021-04-18 10:44:36 +02:00
|
|
|
} else if (type == EpisodeModel::Unread) {
|
2021-04-18 21:56:33 +02:00
|
|
|
entryQuery.prepare(QStringLiteral("SELECT id FROM Entries WHERE read=:read ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
|
2021-04-18 10:44:36 +02:00
|
|
|
entryQuery.bindValue(QStringLiteral(":read"), false);
|
2021-04-18 21:56:33 +02:00
|
|
|
} 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
|
2021-05-01 21:35:37 +02:00
|
|
|
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;"));
|
2021-04-18 21:56:33 +02:00
|
|
|
entryQuery.bindValue(QStringLiteral(":downloaded"), true);
|
2021-04-17 22:54:35 +02:00
|
|
|
}
|
|
|
|
entryQuery.bindValue(QStringLiteral(":index"), entry_index);
|
|
|
|
Database::instance().execute(entryQuery);
|
|
|
|
if (!entryQuery.next()) {
|
|
|
|
qWarning() << "No element with index" << entry_index << "found";
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
QString id = entryQuery.value(QStringLiteral("id")).toString();
|
|
|
|
return getEntry(id);
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2021-04-02 22:31:34 +02:00
|
|
|
int DataManager::feedCount() const
|
|
|
|
{
|
|
|
|
return m_feedmap.count();
|
|
|
|
}
|
|
|
|
|
|
|
|
int DataManager::entryCount(const int feed_index) const
|
|
|
|
{
|
|
|
|
return m_entrymap[m_feedmap[feed_index]].count();
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
int DataManager::entryCount(const Feed *feed) const
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
|
|
|
return m_entrymap[feed->url()].count();
|
|
|
|
}
|
|
|
|
|
2021-04-17 22:54:35 +02:00
|
|
|
int DataManager::entryCount(const EpisodeModel::Type type) const
|
|
|
|
{
|
|
|
|
QSqlQuery query;
|
2021-04-18 21:56:33 +02:00
|
|
|
if (type == EpisodeModel::All || type == EpisodeModel::New || type == EpisodeModel::Unread || type == EpisodeModel::Downloaded) {
|
2021-04-17 22:54:35 +02:00
|
|
|
if (type == EpisodeModel::New) {
|
|
|
|
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE new=:new;"));
|
|
|
|
query.bindValue(QStringLiteral(":new"), true);
|
2021-04-18 10:44:36 +02:00
|
|
|
} else if (type == EpisodeModel::Unread) {
|
|
|
|
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE read=:read;"));
|
|
|
|
query.bindValue(QStringLiteral(":read"), false);
|
2021-04-18 21:56:33 +02:00
|
|
|
} else if (type == EpisodeModel::All) {
|
2021-04-17 22:54:35 +02:00
|
|
|
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries;"));
|
2021-04-18 21:56:33 +02:00
|
|
|
} else { // i.e. EpisodeModel::Downloaded
|
|
|
|
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Enclosures WHERE downloaded=:downloaded;"));
|
|
|
|
query.bindValue(QStringLiteral(":downloaded"), true);
|
2021-04-17 22:54:35 +02:00
|
|
|
}
|
|
|
|
Database::instance().execute(query);
|
|
|
|
if (!query.next())
|
|
|
|
return -1;
|
|
|
|
return query.value(0).toInt();
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
int DataManager::unreadEntryCount(const Feed *feed) const
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND read=0;"));
|
|
|
|
query.bindValue(QStringLiteral(":feed"), feed->url());
|
|
|
|
Database::instance().execute(query);
|
|
|
|
if (!query.next())
|
|
|
|
return -1;
|
|
|
|
return query.value(0).toInt();
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
int DataManager::newEntryCount(const Feed *feed) const
|
2021-04-07 10:39:12 +02:00
|
|
|
{
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND new=1;"));
|
|
|
|
query.bindValue(QStringLiteral(":feed"), feed->url());
|
|
|
|
Database::instance().execute(query);
|
|
|
|
if (!query.next())
|
|
|
|
return -1;
|
|
|
|
return query.value(0).toInt();
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void DataManager::removeFeed(Feed *feed)
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "deleting feed" << feed->url() << "with index" << m_feedmap.indexOf(feed->url());
|
2021-04-02 22:31:34 +02:00
|
|
|
removeFeed(m_feedmap.indexOf(feed->url()));
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:00:12 +02:00
|
|
|
void DataManager::removeFeed(const int index)
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
|
|
|
// Get feed pointer
|
2021-05-01 21:35:37 +02:00
|
|
|
Feed *feed = getFeed(m_feedmap[index]);
|
2021-04-03 21:39:20 +02:00
|
|
|
const QString feedurl = feed->url();
|
|
|
|
|
|
|
|
// Delete the object instances and mappings
|
|
|
|
// First delete entries in Queue
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "delete queueentries of" << feedurl;
|
2021-05-01 21:35:37 +02:00
|
|
|
for (auto &id : m_queuemap) {
|
2021-04-03 21:39:20 +02:00
|
|
|
if (getEntry(id)->feed()->url() == feedurl) {
|
2021-05-28 23:16:35 +02:00
|
|
|
if (AudioManager::instance().entry() == getEntry(id)) {
|
2021-05-22 11:05:33 +02:00
|
|
|
AudioManager::instance().next();
|
|
|
|
}
|
2021-04-03 21:39:20 +02:00
|
|
|
removeQueueItem(id);
|
|
|
|
}
|
|
|
|
}
|
2021-04-02 22:31:34 +02:00
|
|
|
|
2021-04-03 21:39:20 +02:00
|
|
|
// Delete entries themselves
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "delete entries of" << feedurl;
|
2021-05-01 21:35:37 +02:00
|
|
|
for (auto &id : m_entrymap[feedurl]) {
|
|
|
|
if (getEntry(id)->hasEnclosure())
|
|
|
|
getEntry(id)->enclosure()->deleteFile(); // delete enclosure (if it exists)
|
|
|
|
if (!getEntry(id)->image().isEmpty())
|
|
|
|
Fetcher::instance().removeImage(getEntry(id)->image()); // delete entry images
|
2021-04-03 21:39:20 +02:00
|
|
|
delete m_entries[id]; // delete pointer
|
|
|
|
m_entries.remove(id); // delete the hash key
|
|
|
|
}
|
|
|
|
m_entrymap.remove(feedurl); // remove all the entry mappings belonging to the feed
|
|
|
|
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Remove feed image" << feed->image() << "for feed" << feedurl;
|
2021-05-01 21:35:37 +02:00
|
|
|
if (!feed->image().isEmpty())
|
|
|
|
Fetcher::instance().removeImage(feed->image());
|
2021-04-03 21:39:20 +02:00
|
|
|
m_feeds.remove(m_feedmap[index]); // remove from m_feeds
|
|
|
|
m_feedmap.removeAt(index); // remove from m_feedmap
|
2021-04-09 22:52:20 +02:00
|
|
|
delete feed; // remove the pointer
|
2021-04-03 21:39:20 +02:00
|
|
|
|
|
|
|
// Then delete everything from the database
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "delete database part of" << feedurl;
|
2021-04-02 22:31:34 +02:00
|
|
|
|
|
|
|
// Delete Authors
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("DELETE FROM Authors WHERE feed=:feed;"));
|
2021-04-03 21:39:20 +02:00
|
|
|
query.bindValue(QStringLiteral(":feed"), feedurl);
|
2021-04-02 22:31:34 +02:00
|
|
|
Database::instance().execute(query);
|
|
|
|
|
|
|
|
// Delete Entries
|
|
|
|
query.prepare(QStringLiteral("DELETE FROM Entries WHERE feed=:feed;"));
|
2021-04-03 21:39:20 +02:00
|
|
|
query.bindValue(QStringLiteral(":feed"), feedurl);
|
2021-04-02 22:31:34 +02:00
|
|
|
Database::instance().execute(query);
|
|
|
|
|
|
|
|
// Delete Enclosures
|
|
|
|
query.prepare(QStringLiteral("DELETE FROM Enclosures WHERE feed=:feed;"));
|
2021-04-03 21:39:20 +02:00
|
|
|
query.bindValue(QStringLiteral(":feed"), feedurl);
|
2021-04-02 22:31:34 +02:00
|
|
|
Database::instance().execute(query);
|
|
|
|
|
|
|
|
// Delete Feed
|
|
|
|
query.prepare(QStringLiteral("DELETE FROM Feeds WHERE url=:url;"));
|
2021-04-03 21:39:20 +02:00
|
|
|
query.bindValue(QStringLiteral(":url"), feedurl);
|
2021-04-02 22:31:34 +02:00
|
|
|
Database::instance().execute(query);
|
|
|
|
|
2021-04-03 22:52:46 +02:00
|
|
|
Q_EMIT feedRemoved(index);
|
2021-04-02 22:31:34 +02:00
|
|
|
}
|
|
|
|
|
2021-04-21 10:43:21 +02:00
|
|
|
void DataManager::addFeed(const QString &url)
|
|
|
|
{
|
|
|
|
addFeed(url, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataManager::addFeed(const QString &url, const bool fetch)
|
2021-04-02 22:31:34 +02:00
|
|
|
{
|
2021-04-08 11:12:16 +02:00
|
|
|
// This method will add the relevant internal data structures, and then add
|
|
|
|
// a preliminary entry into the database. Those details (as well as entries,
|
|
|
|
// authors and enclosures) will be updated by calling Fetcher::fetch() which
|
|
|
|
// will trigger a full update of the feed and all related items.
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Adding feed";
|
2021-04-02 22:31:34 +02:00
|
|
|
if (feedExists(url)) {
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Feed already exists";
|
2021-04-02 22:31:34 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Feed does not yet exist";
|
2021-04-02 22:31:34 +02:00
|
|
|
|
|
|
|
QUrl urlFromInput = QUrl::fromUserInput(url);
|
|
|
|
QSqlQuery query;
|
2021-05-01 21:35:37 +02:00
|
|
|
query.prepare(QStringLiteral(
|
|
|
|
"INSERT INTO Feeds VALUES (:name, :url, :image, :link, :description, :deleteAfterCount, :deleteAfterType, :subscribed, :lastUpdated, :new, :notify);"));
|
2021-04-02 22:31:34 +02:00
|
|
|
query.bindValue(QStringLiteral(":name"), urlFromInput.toString());
|
|
|
|
query.bindValue(QStringLiteral(":url"), urlFromInput.toString());
|
|
|
|
query.bindValue(QStringLiteral(":image"), QLatin1String(""));
|
|
|
|
query.bindValue(QStringLiteral(":link"), QLatin1String(""));
|
|
|
|
query.bindValue(QStringLiteral(":description"), QLatin1String(""));
|
|
|
|
query.bindValue(QStringLiteral(":deleteAfterCount"), 0);
|
|
|
|
query.bindValue(QStringLiteral(":deleteAfterType"), 0);
|
|
|
|
query.bindValue(QStringLiteral(":subscribed"), QDateTime::currentDateTime().toSecsSinceEpoch());
|
|
|
|
query.bindValue(QStringLiteral(":lastUpdated"), 0);
|
2021-04-09 17:03:02 +02:00
|
|
|
query.bindValue(QStringLiteral(":new"), true);
|
2021-04-02 22:31:34 +02:00
|
|
|
query.bindValue(QStringLiteral(":notify"), false);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
|
2021-04-09 20:27:58 +02:00
|
|
|
m_feeds[urlFromInput.toString()] = nullptr;
|
2021-04-02 22:31:34 +02:00
|
|
|
m_feedmap.append(urlFromInput.toString());
|
|
|
|
|
|
|
|
Q_EMIT feedAdded(urlFromInput.toString());
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
if (fetch)
|
|
|
|
Fetcher::instance().fetch(urlFromInput.toString());
|
2021-04-20 21:14:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void DataManager::addFeeds(const QStringList &urls)
|
|
|
|
{
|
2021-05-01 21:35:37 +02:00
|
|
|
if (urls.count() == 0)
|
|
|
|
return;
|
2021-04-20 21:14:19 +02:00
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
for (int i = 0; i < urls.count(); i++) {
|
|
|
|
addFeed(urls[i], false); // add preliminary feed entries, but do not fetch yet
|
2021-04-20 21:14:19 +02:00
|
|
|
}
|
|
|
|
Fetcher::instance().fetch(urls);
|
2021-04-02 22:31:34 +02:00
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
Entry *DataManager::getQueueEntry(int index) const
|
2021-04-03 13:49:33 +02:00
|
|
|
{
|
|
|
|
return getEntry(m_queuemap[index]);
|
|
|
|
}
|
|
|
|
|
|
|
|
int DataManager::queueCount() const
|
|
|
|
{
|
|
|
|
return m_queuemap.count();
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:00:12 +02:00
|
|
|
QStringList DataManager::queue() const
|
2021-04-11 23:07:21 +02:00
|
|
|
{
|
|
|
|
return m_queuemap;
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
bool DataManager::entryInQueue(const Entry *entry)
|
2021-04-16 20:38:13 +02:00
|
|
|
{
|
|
|
|
return entryInQueue(entry->feed()->url(), entry->id());
|
|
|
|
}
|
|
|
|
|
2021-04-07 22:10:39 +02:00
|
|
|
bool DataManager::entryInQueue(const QString &feedurl, const QString &id) const
|
|
|
|
{
|
|
|
|
Q_UNUSED(feedurl);
|
|
|
|
return m_queuemap.contains(id);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void DataManager::addToQueue(const Entry *entry)
|
2021-04-15 23:10:40 +02:00
|
|
|
{
|
|
|
|
if (entry != nullptr) {
|
|
|
|
return addToQueue(entry->feed()->url(), entry->id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataManager::addToQueue(const QString &feedurl, const QString &id)
|
2021-04-03 13:49:33 +02:00
|
|
|
{
|
2021-04-03 19:29:40 +02:00
|
|
|
// If item is already in queue, then stop here
|
2021-05-01 21:35:37 +02:00
|
|
|
if (m_queuemap.contains(id))
|
|
|
|
return;
|
2021-04-03 19:29:40 +02:00
|
|
|
|
|
|
|
// Add to internal queuemap data structure
|
2021-04-03 13:49:33 +02:00
|
|
|
m_queuemap += id;
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Queue mapping is now:" << m_queuemap;
|
2021-04-03 19:29:40 +02:00
|
|
|
|
|
|
|
// Get index of this entry
|
|
|
|
const int index = m_queuemap.indexOf(id); // add new entry to end of queue
|
|
|
|
|
|
|
|
// Add to Queue database
|
|
|
|
QSqlQuery query;
|
2021-04-17 20:55:01 +02:00
|
|
|
query.prepare(QStringLiteral("INSERT INTO Queue VALUES (:index, :feedurl, :id, :playing);"));
|
2021-04-03 19:29:40 +02:00
|
|
|
query.bindValue(QStringLiteral(":index"), index);
|
|
|
|
query.bindValue(QStringLiteral(":feedurl"), feedurl);
|
|
|
|
query.bindValue(QStringLiteral(":id"), id);
|
2021-04-17 20:55:01 +02:00
|
|
|
query.bindValue(QStringLiteral(":playing"), false);
|
2021-04-03 19:29:40 +02:00
|
|
|
Database::instance().execute(query);
|
|
|
|
|
2021-06-08 14:19:50 +02:00
|
|
|
// Set status to unplayed/unread when adding item to the queue
|
|
|
|
if (getEntry(id)) {
|
|
|
|
getEntry(id)->setRead(false);
|
|
|
|
}
|
|
|
|
|
2021-04-03 19:29:40 +02:00
|
|
|
// Make sure that the QueueModel is aware of the changes
|
|
|
|
Q_EMIT queueEntryAdded(index, id);
|
2021-04-03 13:49:33 +02:00
|
|
|
}
|
|
|
|
|
2021-05-01 21:00:12 +02:00
|
|
|
void DataManager::moveQueueItem(const int from, const int to)
|
2021-04-03 13:49:33 +02:00
|
|
|
{
|
2021-04-03 19:29:40 +02:00
|
|
|
// First move the items in the internal data structure
|
2021-04-03 13:49:33 +02:00
|
|
|
m_queuemap.move(from, to);
|
2021-04-03 19:29:40 +02:00
|
|
|
|
|
|
|
// Then make sure that the database Queue table reflects these changes
|
|
|
|
updateQueueListnrs();
|
|
|
|
|
|
|
|
// Make sure that the QueueModel is aware of the changes so it can update
|
2021-04-03 13:49:33 +02:00
|
|
|
Q_EMIT queueEntryMoved(from, to);
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:00:12 +02:00
|
|
|
void DataManager::removeQueueItem(const int index)
|
2021-04-03 19:29:40 +02:00
|
|
|
{
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Queuemap is now:" << m_queuemap;
|
2021-04-09 23:39:41 +02:00
|
|
|
// Unset "new" state
|
|
|
|
getEntry(m_queuemap[index])->setNew(false);
|
|
|
|
// TODO: Make sure to unset the pointer in the Audio class once it's been
|
|
|
|
// ported to c++
|
|
|
|
|
|
|
|
// Remove the item from the internal data structure
|
2021-04-03 19:29:40 +02:00
|
|
|
const QString id = m_queuemap[index];
|
|
|
|
m_queuemap.removeAt(index);
|
|
|
|
|
|
|
|
// Then make sure that the database Queue table reflects these changes
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("DELETE FROM Queue WHERE listnr=:listnr;"));
|
|
|
|
query.bindValue(QStringLiteral(":listnr"), index);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
// ... and update all other listnrs in Queue table
|
|
|
|
updateQueueListnrs();
|
|
|
|
|
|
|
|
// Make sure that the QueueModel is aware of the change so it can update
|
|
|
|
Q_EMIT queueEntryRemoved(index, id);
|
|
|
|
}
|
|
|
|
|
2021-04-03 21:39:20 +02:00
|
|
|
void DataManager::removeQueueItem(const QString id)
|
2021-04-03 19:29:40 +02:00
|
|
|
{
|
2021-04-03 21:39:20 +02:00
|
|
|
removeQueueItem(m_queuemap.indexOf(id));
|
2021-04-03 19:29:40 +02:00
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void DataManager::removeQueueItem(Entry *entry)
|
2021-04-07 22:10:39 +02:00
|
|
|
{
|
|
|
|
removeQueueItem(m_queuemap.indexOf(entry->id()));
|
|
|
|
}
|
2021-04-09 23:39:41 +02:00
|
|
|
|
2021-04-17 20:55:01 +02:00
|
|
|
QString DataManager::lastPlayingEntry()
|
|
|
|
{
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("SELECT id FROM Queue WHERE playing=:playing;"));
|
|
|
|
query.bindValue(QStringLiteral(":playing"), true);
|
|
|
|
Database::instance().execute(query);
|
2021-05-01 21:35:37 +02:00
|
|
|
if (!query.next())
|
|
|
|
return QStringLiteral("none");
|
2021-04-17 20:55:01 +02:00
|
|
|
return query.value(QStringLiteral("id")).toString();
|
|
|
|
}
|
|
|
|
|
2021-05-01 21:35:37 +02:00
|
|
|
void DataManager::setLastPlayingEntry(const QString &id)
|
2021-04-17 20:55:01 +02:00
|
|
|
{
|
|
|
|
QSqlQuery query;
|
|
|
|
// First set playing to false for all Queue items
|
|
|
|
query.prepare(QStringLiteral("UPDATE Queue SET playing=:playing;"));
|
|
|
|
query.bindValue(QStringLiteral(":playing"), false);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
// Now set the correct track to playing=true
|
|
|
|
query.prepare(QStringLiteral("UPDATE Queue SET playing=:playing WHERE id=:id;"));
|
|
|
|
query.bindValue(QStringLiteral(":playing"), true);
|
|
|
|
query.bindValue(QStringLiteral(":id"), id);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
}
|
|
|
|
|
2021-04-02 22:31:34 +02:00
|
|
|
void DataManager::importFeeds(const QString &path)
|
|
|
|
{
|
|
|
|
QUrl url(path);
|
|
|
|
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
|
|
|
|
|
|
|
file.open(QIODevice::ReadOnly);
|
|
|
|
|
2021-04-20 21:14:19 +02:00
|
|
|
QStringList urls;
|
2021-04-02 22:31:34 +02:00
|
|
|
QXmlStreamReader xmlReader(&file);
|
2021-05-01 21:35:37 +02:00
|
|
|
while (!xmlReader.atEnd()) {
|
2021-04-02 22:31:34 +02:00
|
|
|
xmlReader.readNext();
|
2021-05-01 21:35:37 +02:00
|
|
|
if (xmlReader.tokenType() == 4 && xmlReader.attributes().hasAttribute(QStringLiteral("xmlUrl"))) {
|
2021-04-20 21:14:19 +02:00
|
|
|
urls += xmlReader.attributes().value(QStringLiteral("xmlUrl")).toString();
|
2021-04-02 22:31:34 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Start importing urls:" << urls;
|
2021-04-20 21:14:19 +02:00
|
|
|
addFeeds(urls);
|
2021-04-02 22:31:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void DataManager::exportFeeds(const QString &path)
|
|
|
|
{
|
|
|
|
QUrl url(path);
|
|
|
|
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
|
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
|
|
|
|
QXmlStreamWriter xmlWriter(&file);
|
|
|
|
xmlWriter.setAutoFormatting(true);
|
|
|
|
xmlWriter.writeStartDocument(QStringLiteral("1.0"));
|
|
|
|
xmlWriter.writeStartElement(QStringLiteral("opml"));
|
|
|
|
xmlWriter.writeEmptyElement(QStringLiteral("head"));
|
|
|
|
xmlWriter.writeStartElement(QStringLiteral("body"));
|
|
|
|
xmlWriter.writeAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
|
|
|
|
QSqlQuery query;
|
|
|
|
query.prepare(QStringLiteral("SELECT url, name FROM Feeds;"));
|
|
|
|
Database::instance().execute(query);
|
2021-05-01 21:35:37 +02:00
|
|
|
while (query.next()) {
|
2021-04-02 22:31:34 +02:00
|
|
|
xmlWriter.writeEmptyElement(QStringLiteral("outline"));
|
|
|
|
xmlWriter.writeAttribute(QStringLiteral("xmlUrl"), query.value(0).toString());
|
|
|
|
xmlWriter.writeAttribute(QStringLiteral("title"), query.value(1).toString());
|
|
|
|
}
|
|
|
|
xmlWriter.writeEndElement();
|
|
|
|
xmlWriter.writeEndElement();
|
|
|
|
xmlWriter.writeEndDocument();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataManager::loadFeed(const QString feedurl) const
|
2021-04-02 17:06:27 +02:00
|
|
|
{
|
|
|
|
m_feeds[feedurl] = new Feed(feedurl);
|
|
|
|
}
|
2021-04-02 22:31:34 +02:00
|
|
|
|
|
|
|
void DataManager::loadEntry(const QString id) const
|
|
|
|
{
|
|
|
|
// First find the feed that this entry belongs to
|
2021-05-01 21:35:37 +02:00
|
|
|
Feed *feed = nullptr;
|
2021-04-02 22:31:34 +02:00
|
|
|
QHashIterator<QString, QStringList> i(m_entrymap);
|
|
|
|
while (i.hasNext()) {
|
|
|
|
i.next();
|
|
|
|
if (i.value().contains(id))
|
|
|
|
feed = getFeed(i.key());
|
|
|
|
}
|
2021-05-01 20:59:08 +02:00
|
|
|
if (!feed) {
|
2021-06-05 20:12:42 +02:00
|
|
|
qCDebug(kastsDataManager) << "Failed to find feed belonging to entry" << id;
|
2021-04-02 22:31:34 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_entries[id] = new Entry(feed, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DataManager::feedExists(const QString &url)
|
|
|
|
{
|
|
|
|
return m_feeds.contains(url);
|
|
|
|
}
|
2021-04-03 19:29:40 +02:00
|
|
|
|
|
|
|
void DataManager::updateQueueListnrs() const
|
|
|
|
{
|
|
|
|
QSqlQuery query;
|
2021-05-01 21:35:37 +02:00
|
|
|
for (int i = 0; i < m_queuemap.count(); i++) {
|
2021-04-03 19:29:40 +02:00
|
|
|
query.prepare(QStringLiteral("UPDATE Queue SET listnr=:i WHERE id=:id;"));
|
|
|
|
query.bindValue(QStringLiteral(":i"), i);
|
|
|
|
query.bindValue(QStringLiteral(":id"), m_queuemap[i]);
|
|
|
|
Database::instance().execute(query);
|
|
|
|
}
|
|
|
|
}
|