1 /* This file is part of Clementine.
2    Copyright 2012-2014, David Sansome <me@davidsansome.com>
3    Copyright 2014, John Maguire <john.maguire@gmail.com>
4    Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
5 
6    Clementine is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Clementine is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "podcastepisode.h"
21 
22 #include <QDataStream>
23 #include <QDateTime>
24 #include <QFile>
25 #include <QFileInfo>
26 
27 #include "podcast.h"
28 #include "core/logging.h"
29 #include "core/timeconstants.h"
30 #include "core/utilities.h"
31 
32 const QStringList PodcastEpisode::kColumns = QStringList() << "podcast_id"
33                                                            << "title"
34                                                            << "description"
35                                                            << "author"
36                                                            << "publication_date"
37                                                            << "duration_secs"
38                                                            << "url"
39                                                            << "listened"
40                                                            << "listened_date"
41                                                            << "downloaded"
42                                                            << "local_url"
43                                                            << "extra";
44 
45 const QString PodcastEpisode::kColumnSpec = PodcastEpisode::kColumns.join(", ");
46 const QString PodcastEpisode::kJoinSpec =
47     Utilities::Prepend("e.", PodcastEpisode::kColumns).join(", ");
48 const QString PodcastEpisode::kBindSpec =
49     Utilities::Prepend(":", PodcastEpisode::kColumns).join(", ");
50 const QString PodcastEpisode::kUpdateSpec =
51     Utilities::Updateify(PodcastEpisode::kColumns).join(", ");
52 
53 struct PodcastEpisode::Private : public QSharedData {
54   Private();
55 
56   int database_id_;
57   int podcast_database_id_;
58 
59   QString title_;
60   QString description_;
61   QString author_;
62   QDateTime publication_date_;
63   int duration_secs_;
64   QUrl url_;
65 
66   bool listened_;
67   QDateTime listened_date_;
68 
69   bool downloaded_;
70   QUrl local_url_;
71 
72   QVariantMap extra_;
73 };
74 
Private()75 PodcastEpisode::Private::Private()
76     : database_id_(-1),
77       podcast_database_id_(-1),
78       duration_secs_(-1),
79       listened_(false),
80       downloaded_(false) {}
81 
PodcastEpisode()82 PodcastEpisode::PodcastEpisode() : d(new Private) {}
83 
PodcastEpisode(const PodcastEpisode & other)84 PodcastEpisode::PodcastEpisode(const PodcastEpisode& other) : d(other.d) {}
85 
~PodcastEpisode()86 PodcastEpisode::~PodcastEpisode() {}
87 
operator =(const PodcastEpisode & other)88 PodcastEpisode& PodcastEpisode::operator=(const PodcastEpisode& other) {
89   d = other.d;
90   return *this;
91 }
92 
database_id() const93 int PodcastEpisode::database_id() const { return d->database_id_; }
podcast_database_id() const94 int PodcastEpisode::podcast_database_id() const {
95   return d->podcast_database_id_;
96 }
title() const97 const QString& PodcastEpisode::title() const { return d->title_; }
description() const98 const QString& PodcastEpisode::description() const { return d->description_; }
author() const99 const QString& PodcastEpisode::author() const { return d->author_; }
publication_date() const100 const QDateTime& PodcastEpisode::publication_date() const {
101   return d->publication_date_;
102 }
duration_secs() const103 int PodcastEpisode::duration_secs() const { return d->duration_secs_; }
url() const104 const QUrl& PodcastEpisode::url() const { return d->url_; }
listened() const105 bool PodcastEpisode::listened() const { return d->listened_; }
listened_date() const106 const QDateTime& PodcastEpisode::listened_date() const {
107   return d->listened_date_;
108 }
downloaded() const109 bool PodcastEpisode::downloaded() const { return d->downloaded_; }
local_url() const110 const QUrl& PodcastEpisode::local_url() const { return d->local_url_; }
extra() const111 const QVariantMap& PodcastEpisode::extra() const { return d->extra_; }
extra(const QString & key) const112 QVariant PodcastEpisode::extra(const QString& key) const {
113   return d->extra_[key];
114 }
115 
set_database_id(int v)116 void PodcastEpisode::set_database_id(int v) { d->database_id_ = v; }
set_podcast_database_id(int v)117 void PodcastEpisode::set_podcast_database_id(int v) {
118   d->podcast_database_id_ = v;
119 }
set_title(const QString & v)120 void PodcastEpisode::set_title(const QString& v) { d->title_ = v; }
set_description(const QString & v)121 void PodcastEpisode::set_description(const QString& v) { d->description_ = v; }
set_author(const QString & v)122 void PodcastEpisode::set_author(const QString& v) { d->author_ = v; }
set_publication_date(const QDateTime & v)123 void PodcastEpisode::set_publication_date(const QDateTime& v) {
124   d->publication_date_ = v;
125 }
set_duration_secs(int v)126 void PodcastEpisode::set_duration_secs(int v) { d->duration_secs_ = v; }
set_url(const QUrl & v)127 void PodcastEpisode::set_url(const QUrl& v) { d->url_ = v; }
set_listened(bool v)128 void PodcastEpisode::set_listened(bool v) { d->listened_ = v; }
set_listened_date(const QDateTime & v)129 void PodcastEpisode::set_listened_date(const QDateTime& v) {
130   d->listened_date_ = v;
131 }
set_downloaded(bool v)132 void PodcastEpisode::set_downloaded(bool v) { d->downloaded_ = v; }
set_local_url(const QUrl & v)133 void PodcastEpisode::set_local_url(const QUrl& v) { d->local_url_ = v; }
set_extra(const QVariantMap & v)134 void PodcastEpisode::set_extra(const QVariantMap& v) { d->extra_ = v; }
set_extra(const QString & key,const QVariant & value)135 void PodcastEpisode::set_extra(const QString& key, const QVariant& value) {
136   d->extra_[key] = value;
137 }
138 
InitFromQuery(const QSqlQuery & query)139 void PodcastEpisode::InitFromQuery(const QSqlQuery& query) {
140   d->database_id_ = query.value(0).toInt();
141   d->podcast_database_id_ = query.value(1).toInt();
142   d->title_ = query.value(2).toString();
143   d->description_ = query.value(3).toString();
144   d->author_ = query.value(4).toString();
145   d->publication_date_ = QDateTime::fromTime_t(query.value(5).toUInt());
146   d->duration_secs_ = query.value(6).toInt();
147   d->url_ = QUrl::fromEncoded(query.value(7).toByteArray());
148   d->listened_ = query.value(8).toBool();
149 
150   // After setting QDateTime to invalid state, it's saved into database as time_t,
151   // when this number std::numeric_limits<unsigned int>::max() (4294967295) is read back
152   // from database, it creates a valid QDateTime.
153   // So to make it behave consistently, this change is needed.
154   if (query.value(9).toUInt() == std::numeric_limits<unsigned int>::max()) {
155     d->listened_date_ = QDateTime();
156   } else {
157     d->listened_date_ = QDateTime::fromTime_t(query.value(9).toUInt());
158   }
159 
160   d->downloaded_ = query.value(10).toBool();
161   d->local_url_ = QUrl::fromEncoded(query.value(11).toByteArray());
162 
163   QDataStream extra_stream(query.value(12).toByteArray());
164   extra_stream >> d->extra_;
165 }
166 
BindToQuery(QSqlQuery * query) const167 void PodcastEpisode::BindToQuery(QSqlQuery* query) const {
168   query->bindValue(":podcast_id", d->podcast_database_id_);
169   query->bindValue(":title", d->title_);
170   query->bindValue(":description", d->description_);
171   query->bindValue(":author", d->author_);
172   query->bindValue(":publication_date", d->publication_date_.toTime_t());
173   query->bindValue(":duration_secs", d->duration_secs_);
174   query->bindValue(":url", d->url_.toEncoded());
175   query->bindValue(":listened", d->listened_);
176   query->bindValue(":listened_date", d->listened_date_.toTime_t());
177   query->bindValue(":downloaded", d->downloaded_);
178   query->bindValue(":local_url", d->local_url_.toEncoded());
179 
180   QByteArray extra;
181   QDataStream extra_stream(&extra, QIODevice::WriteOnly);
182   extra_stream << d->extra_;
183 
184   query->bindValue(":extra", extra);
185 }
186 
ToSong(const Podcast & podcast) const187 Song PodcastEpisode::ToSong(const Podcast& podcast) const {
188   Song ret;
189   ret.set_valid(true);
190   ret.set_title(title().simplified());
191   ret.set_artist(author().simplified());
192   ret.set_length_nanosec(kNsecPerSec * duration_secs());
193   ret.set_year(publication_date().date().year());
194   ret.set_comment(description());
195   ret.set_id(database_id());
196   ret.set_ctime(publication_date().toTime_t());
197   ret.set_genre(QString("Podcast"));
198   ret.set_genre_id3(186);
199 
200   if (listened() && listened_date().isValid()) {
201     ret.set_mtime(listened_date().toTime_t());
202   } else {
203     ret.set_mtime(publication_date().toTime_t());
204   }
205 
206   if (ret.length_nanosec() < 0) {
207     ret.set_length_nanosec(-1);
208   }
209 
210   if (downloaded() && QFile::exists(local_url().toLocalFile())) {
211     ret.set_url(local_url());
212   } else {
213     ret.set_url(url());
214   }
215 
216   ret.set_basefilename(QFileInfo(ret.url().path()).fileName());
217 
218   // Use information from the podcast if it's set
219   if (podcast.is_valid()) {
220     ret.set_album(podcast.title().simplified());
221     ret.set_art_automatic(podcast.ImageUrlLarge().toString());
222 
223     if (author().isEmpty()) ret.set_artist(podcast.title().simplified());
224   }
225   return ret;
226 }
227