1 /****************************************************************************************
2  * Copyright (c) 2007-2009 Bart Cerneels <bart.cerneels@kde.org>                        *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #ifndef PODCASTMETA_H
18 #define PODCASTMETA_H
19 
20 #include "core/amarokcore_export.h"
21 #include "core/meta/Meta.h"
22 #include "core/playlists/Playlist.h"
23 #include "core/support/Amarok.h"
24 
25 #include <QDateTime>
26 #include <QSharedData>
27 #include <QString>
28 #include <QStringList>
29 #include <QTextStream>
30 #include <QUrl>
31 
32 #include <KLocalizedString>
33 namespace Collections
34 {
35 class QueryMaker;
36 }
37 namespace Podcasts
38 {
39 class PodcastEpisode;
40 class PodcastChannel;
41 
42 class PodcastArtist;
43 class PodcastAlbum;
44 class PodcastComposer;
45 class PodcastGenre;
46 class PodcastYear;
47 
48 typedef AmarokSharedPointer<PodcastEpisode> PodcastEpisodePtr;
49 typedef AmarokSharedPointer<PodcastChannel> PodcastChannelPtr;
50 
51 typedef QList<PodcastEpisodePtr> PodcastEpisodeList;
52 typedef QList<PodcastChannelPtr> PodcastChannelList;
53 
54 class AMAROKCORE_EXPORT PodcastMetaCommon
55 {
56     public:
PodcastMetaCommon()57         PodcastMetaCommon() {}
~PodcastMetaCommon()58         virtual ~PodcastMetaCommon() {}
59 
title()60         virtual QString title() const { return m_title;}
description()61         virtual QString description() const { return m_description; }
keywords()62         virtual QStringList keywords() const { return m_keywords; }
subtitle()63         virtual QString subtitle() const { return m_subtitle; }
summary()64         virtual QString summary() const { return m_summary; }
author()65         virtual QString author() const { return m_author; }
66 
setTitle(const QString & title)67         virtual void setTitle( const QString &title ) { m_title = title; }
setDescription(const QString & description)68         virtual void setDescription( const QString &description ) { m_description = description; }
setKeywords(const QStringList & keywords)69         virtual void setKeywords( const QStringList &keywords ) { m_keywords = keywords; }
addKeyword(const QString & keyword)70         virtual void addKeyword( const QString &keyword ) { m_keywords << keyword; }
setSubtitle(const QString & subtitle)71         virtual void setSubtitle( const QString &subtitle ) { m_subtitle = subtitle; }
setSummary(const QString & summary)72         virtual void setSummary( const QString &summary ) { m_summary = summary; }
setAuthor(const QString & author)73         virtual void setAuthor( const QString &author ) { m_author = author; }
74 
75     protected:
76         QString m_title; //the title
77         QString m_description; //a longer description, with HTML markup
78         QStringList m_keywords; // TODO: save to DB
79         QString m_subtitle; //a short description
80         QString m_summary;
81         QString m_author; // TODO: save to DB
82 };
83 
84 class AMAROKCORE_EXPORT PodcastEpisode : public PodcastMetaCommon, public Meta::Track
85 {
86     public:
87         PodcastEpisode();
88         explicit PodcastEpisode( const PodcastChannelPtr &channel );
89         PodcastEpisode( const PodcastEpisodePtr &episode, const PodcastChannelPtr &channel );
90 
~PodcastEpisode()91         ~PodcastEpisode() override {}
92 
93         // Meta::Base methods
name()94         QString name() const override { return m_title; }
95 
96         // Meta::Track Methods
playableUrl()97         QUrl playableUrl() const override { return m_localUrl.isEmpty() ? m_url : m_localUrl; }
prettyUrl()98         QString prettyUrl() const override { return playableUrl().toDisplayString(); }
uidUrl()99         QString uidUrl() const override { return m_url.url(); }
100         QString notPlayableReason() const override;
101 
album()102         Meta::AlbumPtr album() const override { return m_albumPtr; }
artist()103         Meta::ArtistPtr artist() const override { return m_artistPtr; }
composer()104         Meta::ComposerPtr composer() const override { return m_composerPtr; }
genre()105         Meta::GenrePtr genre() const override { return m_genrePtr; }
year()106         Meta::YearPtr year() const override { return m_yearPtr; }
107 
bpm()108         qreal bpm() const override { return -1.0; }
109 
comment()110         QString comment() const override { return QString(); }
setComment(const QString & newComment)111         virtual void setComment( const QString &newComment ) { Q_UNUSED( newComment ); }
length()112         qint64 length() const override { return m_duration * 1000; }
filesize()113         int filesize() const override { return m_fileSize; }
sampleRate()114         int sampleRate() const override { return 0; }
bitrate()115         int bitrate() const override { return 0; }
trackNumber()116         int trackNumber() const override { return m_sequenceNumber; }
setTrackNumber(int newTrackNumber)117         virtual void setTrackNumber( int newTrackNumber ) { Q_UNUSED( newTrackNumber ); }
discNumber()118         int discNumber() const override { return 0; }
setDiscNumber(int newDiscNumber)119         virtual void setDiscNumber( int newDiscNumber ) { Q_UNUSED( newDiscNumber ); }
mimeType()120         virtual QString mimeType() const { return m_mimeType; }
121 
type()122         QString type() const override
123         {
124             const QString fileName = playableUrl().fileName();
125             return Amarok::extension( fileName );
126         }
127 
addMatchTo(Collections::QueryMaker * qm)128         virtual void addMatchTo( Collections::QueryMaker* qm ) { Q_UNUSED( qm ); }
inCollection()129         bool inCollection() const override { return false; }
cachedLyrics()130         QString cachedLyrics() const override { return QString(); }
setCachedLyrics(const QString & lyrics)131         void setCachedLyrics( const QString &lyrics ) override { Q_UNUSED( lyrics ); }
132 
133         bool operator==( const Meta::Track &track ) const override;
134 
135         //PodcastMetaCommon methods
setTitle(const QString & title)136         void setTitle( const QString &title ) override { m_title = title; }
137 
138         //PodcastEpisode methods
localUrl()139         virtual QUrl localUrl() const { return m_localUrl; }
pubDate()140         virtual QDateTime pubDate() const { return m_pubDate; }
duration()141         virtual int duration() const { return m_duration; }
guid()142         virtual QString guid() const { return m_guid; }
isNew()143         virtual bool isNew() const { return m_isNew; }
sequenceNumber()144         virtual int sequenceNumber() const { return m_sequenceNumber; }
channel()145         virtual PodcastChannelPtr channel() const { return m_channel; }
146 
setLocalUrl(const QUrl & url)147         virtual void setLocalUrl( const QUrl &url ) { m_localUrl = url; }
setFilesize(int fileSize)148         virtual void setFilesize( int fileSize ) { m_fileSize = fileSize; }
setMimeType(const QString & mimeType)149         virtual void setMimeType( const QString &mimeType ) { m_mimeType = mimeType; }
setUidUrl(const QUrl & url)150         virtual void setUidUrl( const QUrl &url ) { m_url = url; }
setPubDate(const QDateTime & pubDate)151         virtual void setPubDate( const QDateTime &pubDate ) { m_pubDate = pubDate; }
setDuration(int duration)152         virtual void setDuration( int duration ) { m_duration = duration; }
setGuid(const QString & guid)153         virtual void setGuid( const QString &guid ) { m_guid = guid; }
setNew(bool isNew)154         virtual void setNew( bool isNew ) { m_isNew = isNew; }
setSequenceNumber(int sequenceNumber)155         virtual void setSequenceNumber( int sequenceNumber ) { m_sequenceNumber = sequenceNumber; }
setChannel(const PodcastChannelPtr & channel)156         virtual void setChannel( const PodcastChannelPtr &channel ) { m_channel = channel; }
157 
158     protected:
159         PodcastChannelPtr m_channel;
160 
161         QString m_guid; //the GUID from the podcast feed
162         QUrl m_url; //remote url of the file
163         QUrl m_localUrl; //the localUrl, only valid if downloaded
164         QString m_mimeType; //the mimetype of the enclosure
165         QDateTime m_pubDate; //the pubDate from the feed
166         int m_duration; //the playlength in seconds
167         int m_fileSize; //the size tag from the enclosure
168         int m_sequenceNumber; //number of the episode
169         bool m_isNew; //listened to or not?
170 
171         //data members
172         Meta::AlbumPtr m_albumPtr;
173         Meta::ArtistPtr m_artistPtr;
174         Meta::ComposerPtr m_composerPtr;
175         Meta::GenrePtr m_genrePtr;
176         Meta::YearPtr m_yearPtr;
177 };
178 
179 class AMAROKCORE_EXPORT PodcastChannel : public PodcastMetaCommon, public Playlists::Playlist
180 {
181     public:
182 
183         enum FetchType
184         {
185             DownloadWhenAvailable = 0,
186             StreamOrDownloadOnDemand
187         };
188 
PodcastChannel()189         PodcastChannel()
190             : PodcastMetaCommon()
191             , Playlist()
192             , m_image()
193             , m_subscribeDate()
194             , m_copyright()
195             , m_autoScan( false )
196             , m_fetchType( DownloadWhenAvailable )
197             , m_purge( false )
198             , m_purgeCount( 0 )
199         { }
200 
201         explicit PodcastChannel(const PodcastChannelPtr &channel );
~PodcastChannel()202         ~PodcastChannel() override {}
203 
204         //Playlist virtual methods
uidUrl()205         QUrl uidUrl() const override { return m_url; }
name()206         QString name() const override { return title(); }
207 
trackCount()208         int trackCount() const override { return m_episodes.count(); }
209         Meta::TrackList tracks() override;
210         void addTrack( const Meta::TrackPtr &track, int position = -1 ) override;
211 
212         //PodcastMetaCommon methods
213         // override this since it's ambiguous in PodcastMetaCommon and Playlist
description()214         QString description() const override { return m_description; }
215 
216         //PodcastChannel methods
url()217         virtual QUrl url() const { return m_url; }
webLink()218         virtual QUrl webLink() const { return m_webLink; }
hasImage()219         virtual bool hasImage() const { return !m_image.isNull(); }
imageUrl()220         virtual QUrl imageUrl() const { return m_imageUrl; }
image()221         virtual QImage image() const { return m_image; }
copyright()222         virtual QString copyright() const { return m_copyright; }
labels()223         virtual QStringList labels() const { return m_labels; }
subscribeDate()224         virtual QDate subscribeDate() const { return m_subscribeDate; }
225 
setUrl(const QUrl & url)226         virtual void setUrl( const QUrl &url ) { m_url = url; }
setWebLink(const QUrl & link)227         virtual void setWebLink( const QUrl &link ) { m_webLink = link; }
228         // TODO: inform all albums with this channel of the changed image
setImage(const QImage & image)229         virtual void setImage( const QImage &image ) { m_image = image; }
setImageUrl(const QUrl & imageUrl)230         virtual void setImageUrl( const QUrl &imageUrl ) { m_imageUrl = imageUrl; }
setCopyright(const QString & copyright)231         virtual void setCopyright( const QString &copyright ) { m_copyright = copyright; }
setLabels(const QStringList & labels)232         virtual void setLabels( const QStringList &labels ) { m_labels = labels; }
addLabel(const QString & label)233         virtual void addLabel( const QString &label ) { m_labels << label; }
setSubscribeDate(const QDate & date)234         virtual void setSubscribeDate( const QDate &date ) { m_subscribeDate = date; }
235 
236         virtual Podcasts::PodcastEpisodePtr addEpisode( const PodcastEpisodePtr &episode );
episodes()237         virtual PodcastEpisodeList episodes() const { return m_episodes; }
238 
load(QTextStream & stream)239         bool load( QTextStream &stream ) { Q_UNUSED( stream ); return false; }
240 
241         //PodcastChannel Settings
saveLocation()242         QUrl saveLocation() const { return m_directory; }
autoScan()243         bool autoScan() const { return m_autoScan; }
fetchType()244         FetchType fetchType() const { return m_fetchType; }
hasPurge()245         bool hasPurge() const { return m_purge; }
purgeCount()246         int purgeCount() const { return m_purgeCount; }
247 
setSaveLocation(const QUrl & url)248         void setSaveLocation( const QUrl &url ) { m_directory = url; }
setAutoScan(bool autoScan)249         void setAutoScan( bool autoScan ) { m_autoScan = autoScan; }
setFetchType(FetchType fetchType)250         void setFetchType( FetchType fetchType ) { m_fetchType = fetchType; }
setPurge(bool purge)251         void setPurge( bool purge ) { m_purge = purge; }
setPurgeCount(int purgeCount)252         void setPurgeCount( int purgeCount ) { m_purgeCount = purgeCount; }
253 
254     protected:
255         QUrl m_url;
256         QUrl m_webLink;
257         QImage m_image;
258         QUrl m_imageUrl;
259         QStringList m_labels;
260         QDate m_subscribeDate;
261         QString m_copyright;
262         QUrl m_directory; //the local directory to save the files in.
263         bool m_autoScan; //should this channel be checked automatically?
264         PodcastChannel::FetchType m_fetchType; //'download when available' or 'stream or download on demand'
265         bool m_purge; //remove old episodes?
266         int m_purgeCount; //how many episodes do we keep on disk?
267         PodcastEpisodeList m_episodes;
268 };
269 
270 // internal helper classes
271 
272 class AMAROKCORE_EXPORT PodcastArtist : public Meta::Artist
273 {
274 public:
PodcastArtist(PodcastEpisode * episode)275     explicit PodcastArtist( PodcastEpisode *episode )
276         : Meta::Artist()
277         , episode( episode )
278     {}
279 
tracks()280     Meta::TrackList tracks() override
281     {
282         return Meta::TrackList();
283     }
284 
albums()285     Meta::AlbumList albums()
286     {
287         return Meta::AlbumList();
288     }
289 
name()290     QString name() const override
291     {
292         QString author;
293         if( episode && episode->channel() )
294             author = episode->channel()->author();
295 
296         return author;
297     }
298 
299     bool operator==( const Meta::Artist &other ) const override
300     {
301         return name() == other.name();
302     }
303 
304     PodcastEpisode const *episode;
305 };
306 
307 class AMAROKCORE_EXPORT PodcastAlbum : public Meta::Album
308 {
309 public:
PodcastAlbum(PodcastEpisode * episode)310     explicit PodcastAlbum( PodcastEpisode *episode )
311         : Meta::Album()
312         , episode( episode )
313     {}
314 
315     /* Its all a little bit stupid.
316        When the channel image (and also the album image) changes the album get's no indication.
317        Also the CoverCache is not in amarokcorelib but in amaroklib.
318        Why the PodcastAlbum is the only one with a concrete implementation in amarokcorelib is another question.
319 
320        virtual ~PodcastAlbum()
321        { CoverCache::invalidateAlbum( Meta::AlbumPtr(this) ); }
322     */
323 
isCompilation()324     bool isCompilation() const override
325     {
326         return false;
327     }
328 
hasAlbumArtist()329     bool hasAlbumArtist() const override
330     {
331         return false;
332     }
333 
albumArtist()334     Meta::ArtistPtr albumArtist() const override
335     {
336         return Meta::ArtistPtr();
337     }
338 
tracks()339     Meta::TrackList tracks() override
340     {
341         return Meta::TrackList();
342     }
343 
name()344     QString name() const override
345     {
346         if( episode != 0 )
347         {
348             const QString albumName = episode->channel()->title();
349             return albumName;
350         }
351         else
352             return QString();
353     }
354 
image(int size)355     QImage image( int size ) const override
356     {
357         // This is a little stupid. If Channel::setImage is called we don't Q_EMIT a MetaDataChanged or invalidate the cache
358         QImage image = episode->channel()->image();
359         return image.scaledToHeight( size );
360     }
361 
362     bool operator==( const Meta::Album &other ) const override
363     {
364         return name() == other.name();
365     }
366 
367     PodcastEpisode const *episode;
368 };
369 
370 class AMAROKCORE_EXPORT PodcastGenre : public Meta::Genre
371 {
372 public:
PodcastGenre(PodcastEpisode * episode)373     explicit PodcastGenre( PodcastEpisode *episode )
374         : Meta::Genre()
375         , episode( episode )
376     {}
377 
tracks()378     Meta::TrackList tracks() override
379     {
380         return Meta::TrackList();
381     }
382 
name()383     QString name() const override
384     {
385         const QString genreName = i18n( "Podcast" );
386         return genreName;
387     }
388 
389     bool operator==( const Meta::Genre &other ) const override
390     {
391         return name() == other.name();
392     }
393 
394     PodcastEpisode const *episode;
395 };
396 
397 class AMAROKCORE_EXPORT PodcastComposer : public Meta::Composer
398 {
399 public:
PodcastComposer(PodcastEpisode * episode)400     explicit PodcastComposer( PodcastEpisode *episode )
401         : Meta::Composer()
402         , episode( episode )
403     {}
404 
tracks()405     Meta::TrackList tracks() override
406     {
407         return Meta::TrackList();
408     }
409 
name()410     QString name() const override
411     {
412         if( episode != 0 )
413         {
414             const QString composer = episode->channel()->author();
415             return composer;
416         }
417         else
418             return QString();
419 
420      }
421 
422     bool operator==( const Meta::Composer &other ) const override
423     {
424         return name() == other.name();
425     }
426 
427     PodcastEpisode const *episode;
428 };
429 
430 class AMAROKCORE_EXPORT PodcastYear : public Meta::Year
431 {
432 public:
PodcastYear(PodcastEpisode * episode)433     explicit PodcastYear( PodcastEpisode *episode )
434         : Meta::Year()
435         , episode( episode )
436     {}
437 
tracks()438     Meta::TrackList tracks() override
439     {
440         return Meta::TrackList();
441     }
442 
name()443     QString name() const override
444     {
445         if( episode != 0 )
446         {
447             const QString year = episode->pubDate().toString( QStringLiteral("yyyy") );
448             return year;
449         }
450         else
451             return QString();
452     }
453 
454     bool operator==( const Meta::Year &other ) const override
455     {
456         return name() == other.name();
457     }
458 
459     PodcastEpisode const *episode;
460 };
461 
462 } //namespace Podcasts
463 
464 Q_DECLARE_METATYPE( Podcasts::PodcastMetaCommon* )
465 Q_DECLARE_METATYPE( Podcasts::PodcastEpisodePtr )
466 Q_DECLARE_METATYPE( Podcasts::PodcastEpisodeList )
467 Q_DECLARE_METATYPE( Podcasts::PodcastChannelPtr )
468 Q_DECLARE_METATYPE( Podcasts::PodcastChannelList )
469 
470 #endif
471