1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  * ----
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #ifndef PODCAST_SERVICE_H
25 #define PODCAST_SERVICE_H
26 
27 #include "onlineservice.h"
28 #include "models/actionmodel.h"
29 #include "models/proxymodel.h"
30 #include "mpd-interface/song.h"
31 #include <QLatin1String>
32 #include <QList>
33 #include <QDateTime>
34 #include <QSet>
35 #include <QUrl>
36 
37 class QTimer;
38 class NetworkJob;
39 
40 class PodcastService : public ActionModel, public OnlineService
41 {
42     Q_OBJECT
43 
44 public:
45     struct Item
46     {
nameItem47         Item(const QString &n=QString(), const QUrl &u=QUrl()) : name(n), url(u) { }
~ItemItem48         virtual ~Item() { }
isPodcastItem49         virtual bool isPodcast() const { return false; }
50         QString name;
51         QString descr;
52         QUrl url;
53     };
54 
55     struct Podcast;
56     struct Episode : public Item
57     {
58         enum DownloadState {
59             NotDownloading = -1,
60             QueuedForDownload = -2
61         };
62 
63         Episode(const QDateTime &d=QDateTime(), const QString &n=QString(), const QUrl &u=QUrl(), Podcast *p=nullptr)
ItemEpisode64             : Item(n, u), played(false), duration(0), publishedDate(d), parent(p), downloadProg(NotDownloading) { }
~EpisodeEpisode65         ~Episode() override { }
66         Song toSong() const;
67         bool played;
68         int duration;
69         QDateTime publishedDate;
70         Podcast *parent;
71         QString localFile;
72         int downloadProg;
73     };
74 
75     struct Podcast : public Item
76     {
77         Podcast(const QString &f=QString());
~PodcastPodcast78         ~Podcast() override { qDeleteAll(episodes); }
isPodcastPodcast79         bool isPodcast() const override { return true; }
80         bool load();
81         bool save() const;
82         void add(Episode *ep);
83         void add(QList<Episode *> &eps);
84         Episode * getEpisode(const QUrl &epUrl) const;
85         void setUnplayedCount();
86         void removeFiles();
87         const Song & coverSong();
88 
89         QList<Episode *> episodes;
90         int unplayedCount;
91         QString fileName;
92         QString imageFile;
93         QUrl imageUrl;
94         Song song;
95     };
96 
97     class Proxy : public ProxyModel
98     {
99     public:
100         Proxy(QObject *parent);
101 
102         void showUnplayedOnly(bool on);
103 
104     private:
105         bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
106         bool filterAcceptsPodcast(const Podcast *pod) const;
107         bool filterAcceptsEpisode(const Episode *item) const;
108         bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
109 
110     private:
111         bool unplayedOnly;
112     };
113 
114     static const QLatin1String constName;
115 
116     static PodcastService * self();
117 
118     PodcastService();
~PodcastService()119     ~PodcastService() override { cancelAll(); }
120 
121     Song & fixPath(Song &song) const;
122     QString name() const override;
123     QString title() const override;
124     QString descr() const override;
125     bool episodeCover(const Song &s, QImage &img, QString &imgFilename) const;
126     QString episodeDescr(const Song &s) const;
127     int rowCount(const QModelIndex &index = QModelIndex()) const override;
128     int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return 1; }
129     bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
130     QModelIndex parent(const QModelIndex &index) const override;
131     QModelIndex index(int row, int col, const QModelIndex &parent) const override;
132     QVariant data(const QModelIndex &, int) const override;
133     Qt::ItemFlags flags(const QModelIndex &index) const override;
134     QMimeData * mimeData(const QModelIndexList &indexes) const override;
135     QStringList filenames(const QModelIndexList &indexes, bool allowPlaylists=false) const;
136     QList<Song> songs(const QModelIndexList &indexes, bool allowPlaylists=false) const;
137 
podcastCount()138     int podcastCount() const { return podcasts.count(); }
139     void clear();
140     void configure(QWidget *p);
subscribedToUrl(const QUrl & url)141     bool subscribedToUrl(const QUrl &url) { return nullptr!=getPodcast(url); }
142     void unSubscribe(Podcast *podcast);
143     void refresh(const QModelIndexList &list);
144     void refreshAll();
145     void refreshSubscription(Podcast *item);
146     bool processingUrl(const QUrl &url) const;
147     void addUrl(const QUrl &url, bool isNew=true);
148     static bool isPodcastFile(const QString &file);
iconPath()149     static const QString & iconPath() { return iconFile; }
150     static QUrl fixUrl(const QString &url);
151     static QUrl fixUrl(const QUrl &orig);
isUrlOk(const QUrl & u)152     static bool isUrlOk(const QUrl &u) { return QLatin1String("http")==u.scheme() || QLatin1String("https")==u.scheme(); }
153 
isDownloading()154     bool isDownloading() const { return nullptr!=downloadJob; }
155     void cancelAllDownloads();
156     void downloadPodcasts(Podcast *pod, const QList<Episode *> &episodes);
157     void deleteDownloadedPodcasts(Podcast *pod, const QList<Episode *> &episodes);
158     void setPodcastsAsListened(Podcast *pod, const QList<Episode *> &episodes, bool listened);
cancelAllJobs()159     void cancelAllJobs() { cancelAll(); cancelAllDownloads(); }
160     Podcast * getPodcast(const QUrl &url) const;
161     void cancelAll();
162     void startRssUpdateTimer();
163     void stopRssUpdateTimer();
164     bool exportSubscriptions(const QString &name);
165 
166 Q_SIGNALS:
167     void error(const QString &msg);
168     void newError(const QString &msg);
169 
170 private:
171     bool downloadingEpisode(const QUrl &url) const;
172     void downloadEpisode(const Podcast *podcast, const QUrl &episode);
173     void cancelDownloads(const QList<Episode *> episodes);
174     void cancelDownload(const QUrl &url);
175     void cancelDownload();
176     void doNextDownload();
177     void updateEpisode(const QUrl &rssUrl, const QUrl &url, int pc);
178     void clearPartialDownloads();
179 
180 private Q_SLOTS:
181     void loadAll();
182     void rssJobFinished();
183     void updateRss();
184     void currentMpdSong(const Song &s);
185     void downloadJobFinished();
186     void downloadReadyRead();
187     void downloadPercent(int pc);
188 
189 private:
190     struct DownloadEntry
191     {
urlDownloadEntry192         DownloadEntry(const QUrl &u=QUrl(), const QUrl &r=QUrl(), const QString &d=QString()) : url(u), rssUrl(r), dest(d) { }
193         bool operator==(const DownloadEntry &o) const { return o.url==url; }
194         QUrl url;
195         QUrl rssUrl;
196         QString dest;
197         Episode *ep;
198     };
199 
200     QList<Podcast *> podcasts;
201     QList<NetworkJob *> rssJobs;
202     NetworkJob * downloadJob;
203     QList<DownloadEntry> toDownload;
204     QTimer *rssUpdateTimer;
205     QDateTime lastRssUpdate;
206     QTimer *deleteTimer;
207     QDateTime lastDelete;
208     QSet<QUrl> updateUrls;
209     static QString iconFile;
210 };
211 
212 #endif
213 
214