/* This file is part of Clementine. Copyright 2012, David Sansome Copyright 2014, Krzysztof A. Sobiecki Copyright 2014, John Maguire Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Clementine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Clementine. If not, see . */ #include "podcastbackend.h" #include #include "core/application.h" #include "core/database.h" #include "core/logging.h" #include "core/scopedtransaction.h" PodcastBackend::PodcastBackend(Application* app, QObject* parent) : QObject(parent), app_(app), db_(app->database()) {} void PodcastBackend::Subscribe(Podcast* podcast) { // If this podcast is already in the database, do nothing if (podcast->is_valid()) { return; } // If there's an entry in the database with the same URL, take its data. Podcast existing_podcast = GetSubscriptionByUrl(podcast->url()); if (existing_podcast.is_valid()) { *podcast = existing_podcast; return; } QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); ScopedTransaction t(&db); // Insert the podcast. QSqlQuery q(db); q.prepare("INSERT INTO podcasts (" + Podcast::kColumnSpec + ")" " VALUES (" + Podcast::kBindSpec + ")"); podcast->BindToQuery(&q); q.exec(); if (db_->CheckErrors(q)) return; // Update the database ID. const int database_id = q.lastInsertId().toInt(); podcast->set_database_id(database_id); // Update the IDs of any episodes. PodcastEpisodeList* episodes = podcast->mutable_episodes(); for (auto it = episodes->begin(); it != episodes->end(); ++it) { it->set_podcast_database_id(database_id); } // Add those episodes to the database. AddEpisodes(episodes, &db); t.Commit(); emit SubscriptionAdded(*podcast); } void PodcastBackend::Unsubscribe(const Podcast& podcast) { // If this podcast is not already in the database, do nothing if (!podcast.is_valid()) { return; } QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); ScopedTransaction t(&db); // Remove the podcast. QSqlQuery q(db); q.prepare("DELETE FROM podcasts WHERE ROWID = :id"); q.bindValue(":id", podcast.database_id()); q.exec(); if (db_->CheckErrors(q)) return; // Remove all episodes in the podcast q.prepare("DELETE FROM podcast_episodes WHERE podcast_id = :id"); q.bindValue(":id", podcast.database_id()); q.exec(); if (db_->CheckErrors(q)) return; t.Commit(); emit SubscriptionRemoved(podcast); } void PodcastBackend::AddEpisodes(PodcastEpisodeList* episodes, QSqlDatabase* db) { QSqlQuery q(*db); q.prepare("INSERT INTO podcast_episodes (" + PodcastEpisode::kColumnSpec + ")" " VALUES (" + PodcastEpisode::kBindSpec + ")"); for (auto it = episodes->begin(); it != episodes->end(); ++it) { it->BindToQuery(&q); q.exec(); if (db_->CheckErrors(q)) continue; const int database_id = q.lastInsertId().toInt(); it->set_database_id(database_id); } } void PodcastBackend::AddEpisodes(PodcastEpisodeList* episodes) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); ScopedTransaction t(&db); AddEpisodes(episodes, &db); t.Commit(); emit EpisodesAdded(*episodes); } void PodcastBackend::UpdateEpisodes(const PodcastEpisodeList& episodes) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); ScopedTransaction t(&db); QSqlQuery q(db); q.prepare( "UPDATE podcast_episodes" " SET listened = :listened," " listened_date = :listened_date," " downloaded = :downloaded," " local_url = :local_url" " WHERE ROWID = :id"); for (const PodcastEpisode& episode : episodes) { q.bindValue(":listened", episode.listened()); q.bindValue(":listened_date", episode.listened_date().toTime_t()); q.bindValue(":downloaded", episode.downloaded()); q.bindValue(":local_url", episode.local_url().toEncoded()); q.bindValue(":id", episode.database_id()); q.exec(); db_->CheckErrors(q); } t.Commit(); emit EpisodesUpdated(episodes); } PodcastList PodcastBackend::GetAllSubscriptions() { PodcastList ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + Podcast::kColumnSpec + " FROM podcasts"); q.exec(); if (db_->CheckErrors(q)) return ret; while (q.next()) { Podcast podcast; podcast.InitFromQuery(q); ret << podcast; } return ret; } Podcast PodcastBackend::GetSubscriptionById(int id) { Podcast ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + Podcast::kColumnSpec + " FROM podcasts" " WHERE ROWID = :id"); q.bindValue(":id", id); q.exec(); if (!db_->CheckErrors(q) && q.next()) { ret.InitFromQuery(q); } return ret; } Podcast PodcastBackend::GetSubscriptionByUrl(const QUrl& url) { Podcast ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + Podcast::kColumnSpec + " FROM podcasts" " WHERE url = :url"); q.bindValue(":url", url.toEncoded()); q.exec(); if (!db_->CheckErrors(q) && q.next()) { ret.InitFromQuery(q); } return ret; } PodcastEpisodeList PodcastBackend::GetEpisodes(int podcast_id) { PodcastEpisodeList ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE podcast_id = :id" " ORDER BY publication_date DESC"); q.bindValue(":id", podcast_id); q.exec(); if (db_->CheckErrors(q)) return ret; while (q.next()) { PodcastEpisode episode; episode.InitFromQuery(q); ret << episode; } return ret; } PodcastEpisode PodcastBackend::GetEpisodeById(int id) { PodcastEpisode ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE ROWID = :id"); q.bindValue(":id", id); q.exec(); if (!db_->CheckErrors(q) && q.next()) { ret.InitFromQuery(q); } return ret; } PodcastEpisode PodcastBackend::GetEpisodeByUrl(const QUrl& url) { PodcastEpisode ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE url = :url"); q.bindValue(":url", url.toEncoded()); q.exec(); if (!db_->CheckErrors(q) && q.next()) { ret.InitFromQuery(q); } return ret; } PodcastEpisode PodcastBackend::GetEpisodeByUrlOrLocalUrl(const QUrl& url) { PodcastEpisode ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE url = :url" " OR local_url = :url"); q.bindValue(":url", url.toEncoded()); q.exec(); if (!db_->CheckErrors(q) && q.next()) { ret.InitFromQuery(q); } return ret; } PodcastEpisodeList PodcastBackend::GetOldDownloadedEpisodes( const QDateTime& max_listened_date) { PodcastEpisodeList ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE downloaded = 'true'" " AND listened_date <= :max_listened_date"); q.bindValue(":max_listened_date", max_listened_date.toTime_t()); q.exec(); if (db_->CheckErrors(q)) return ret; while (q.next()) { PodcastEpisode episode; episode.InitFromQuery(q); ret << episode; } return ret; } PodcastEpisode PodcastBackend::GetOldestDownloadedListenedEpisode() { PodcastEpisode ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE downloaded = 'true'" " AND listened = 'true'" " ORDER BY listened_date ASC"); q.exec(); if (db_->CheckErrors(q)) return ret; q.next(); ret.InitFromQuery(q); return ret; } PodcastEpisodeList PodcastBackend::GetNewDownloadedEpisodes() { PodcastEpisodeList ret; QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); QSqlQuery q(db); q.prepare("SELECT ROWID, " + PodcastEpisode::kColumnSpec + " FROM podcast_episodes" " WHERE downloaded = 'true'" " AND listened = 'false'"); q.exec(); if (db_->CheckErrors(q)) return ret; while (q.next()) { PodcastEpisode episode; episode.InitFromQuery(q); ret << episode; } return ret; }