Files
cantata/models/musiclibraryitempodcast.cpp

368 lines
12 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2014 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "musiclibraryitemroot.h"
#include "musiclibraryitemartist.h"
#include "musiclibraryitemalbum.h"
#include "musiclibraryitempodcast.h"
#include "musiclibraryitemsong.h"
#include "onlineservicesmodel.h"
#include "online/onlineservice.h"
#include "widgets/icons.h"
#include "qtiocompressor/qtiocompressor.h"
#include "support/utils.h"
#include "gui/covers.h"
#include "online/rssparser.h"
#include <QPixmap>
#include <QFile>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QCryptographicHash>
#include <QNetworkReply>
static QPixmap *theDefaultIcon=0;
static QLatin1String constTopTag("podcast");
static QLatin1String constImageAttribute("img");
static QLatin1String constRssAttribute("rss");
static QLatin1String constEpisodeTag("episode");
static QLatin1String constNameAttribute("name");
static QLatin1String constDateAttribute("date");
static QLatin1String constUrlAttribute("url");
static QLatin1String constTimeAttribute("time");
static QLatin1String constPlayedAttribute("played");
static QLatin1String constLocalAttribute("local");
static QLatin1String constTrue("true");
const QString MusicLibraryItemPodcast::constExt=QLatin1String(".xml.gz");
const QString MusicLibraryItemPodcast::constDir=QLatin1String("podcasts");
static QString generateFileName(const QUrl &url, bool creatingNew)
{
QString hash=QCryptographicHash::hash(url.toString().toUtf8(), QCryptographicHash::Md5).toHex();
QString dir=Utils::dataDir(MusicLibraryItemPodcast::constDir, true);
QString fileName=dir+hash+MusicLibraryItemPodcast::constExt;
if (creatingNew) {
int i=0;
while (QFile::exists(fileName) && i<100) {
fileName=dir+hash+QChar('_')+QString::number(i)+MusicLibraryItemPodcast::constExt;
i++;
}
}
return fileName;
}
MusicLibraryItemPodcast::MusicLibraryItemPodcast(const QString &fileName, MusicLibraryItemContainer *parent)
: MusicLibraryItemContainer(QString(), parent)
, m_coverRequested(false)
, m_cover(0)
, m_fileName(fileName)
, m_unplayedEpisodeCount(0)
{
if (!m_fileName.isEmpty()) {
m_imageFile=m_fileName;
m_imageFile=m_imageFile.replace(constExt, ".jpg");
}
}
bool MusicLibraryItemPodcast::load()
{
if (m_fileName.isEmpty()) {
return false;
}
QFile file(m_fileName);
QtIOCompressor compressor(&file);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (!compressor.open(QIODevice::ReadOnly)) {
return false;
}
QXmlStreamReader reader(&compressor);
m_unplayedEpisodeCount=0;
while (!reader.atEnd()) {
reader.readNext();
if (!reader.error() && reader.isStartElement()) {
QString element = reader.name().toString();
QXmlStreamAttributes attributes=reader.attributes();
if (constTopTag == element) {
m_imageUrl=attributes.value(constImageAttribute).toString();
m_rssUrl=attributes.value(constRssAttribute).toString();
QString name=attributes.value(constNameAttribute).toString();
if (m_rssUrl.isEmpty() || name.isEmpty()) {
return false;
}
m_itemData=name;
} else if (constEpisodeTag == element) {
QString name=attributes.value(constNameAttribute).toString();
QString url=attributes.value(constUrlAttribute).toString();
if (!name.isEmpty() && !url.isEmpty()) {
Song s;
s.title=name;
s.file=url;
s.artist=m_itemData;
s.setPlayed(constTrue==attributes.value(constPlayedAttribute).toString());
s.setPodcastImage(m_imageFile);
s.setPodcastPublishedDate(attributes.value(constDateAttribute).toString());
QString time=attributes.value(constTimeAttribute).toString();
s.time=time.isEmpty() ? 0 : time.toUInt();
QString localFile=attributes.value(constLocalAttribute).toString();
if (QFile::exists(localFile)) {
s.setPodcastLocalPath(localFile);
}
MusicLibraryItemPodcastEpisode *song=new MusicLibraryItemPodcastEpisode(s, this);
m_childItems.append(song);
if (!s.hasBeenPlayed()) {
m_unplayedEpisodeCount++;
}
}
}
}
}
return true;
}
static const QString constRssTag=QLatin1String("rss");
MusicLibraryItemPodcast::RssStatus MusicLibraryItemPodcast::loadRss(QNetworkReply *dev)
{
m_rssUrl=dev->url();
if (m_fileName.isEmpty()) {
m_fileName=m_imageFile=generateFileName(m_rssUrl, true);
m_imageFile=m_imageFile.replace(constExt, ".jpg");
}
RssParser::Channel ch=RssParser::parse(dev);
if (!ch.isValid()) {
return FailedToParse;
}
if (ch.video) {
return VideoPodcast;
}
m_imageUrl=ch.image;
m_itemData=ch.name;
m_unplayedEpisodeCount=ch.episodes.count();
foreach (const RssParser::Episode &ep, ch.episodes) {
Song s;
s.title=ep.name;
s.file=ep.url.toString(); // ????
s.artist=m_itemData;
s.time=ep.duration;
s.setPlayed(false);
s.setPodcastImage(m_imageFile);
s.setPodcastPublishedDate(ep.publicationDate.toString(Qt::ISODate));
MusicLibraryItemSong *song=new MusicLibraryItemPodcastEpisode(s, this);
m_childItems.append(song);
}
return Loaded;
}
bool MusicLibraryItemPodcast::save()
{
if (m_fileName.isEmpty()) {
return false;
}
QFile file(m_fileName);
QtIOCompressor compressor(&file);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (!compressor.open(QIODevice::WriteOnly)) {
return false;
}
QXmlStreamWriter writer(&compressor);
writer.writeStartElement(constTopTag);
writer.writeAttribute(constImageAttribute, m_imageUrl.toString()); // ??
writer.writeAttribute(constRssAttribute, m_rssUrl.toString()); // ??
writer.writeAttribute(constNameAttribute, m_itemData);
foreach (MusicLibraryItem *i, m_childItems) {
MusicLibraryItemPodcastEpisode *episode=static_cast<MusicLibraryItemPodcastEpisode *>(i);
const Song &s=episode->song();
writer.writeStartElement(constEpisodeTag);
writer.writeAttribute(constNameAttribute, s.title);
writer.writeAttribute(constUrlAttribute, s.file);
if (s.time) {
writer.writeAttribute(constTimeAttribute, QString::number(s.time));
}
if (s.hasBeenPlayed()) {
writer.writeAttribute(constPlayedAttribute, constTrue);
}
if (!s.podcastPublishedDate().isEmpty()) {
writer.writeAttribute(constDateAttribute, s.podcastPublishedDate());
}
if (!s.podcastLocalPath().isEmpty()) {
writer.writeAttribute(constLocalAttribute, s.podcastLocalPath());
}
writer.writeEndElement();
}
writer.writeEndElement();
compressor.close();
return true;
}
void MusicLibraryItemPodcast::setCoverImage(const QImage &img) const
{
if (m_cover) {
delete m_cover;
}
int size=MusicLibraryItemAlbum::iconSize(largeImages());
QImage scaled=img.scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
m_cover = new QPixmap(QPixmap::fromImage(scaled));
m_coverRequested=false;
}
bool MusicLibraryItemPodcast::setCover(const QImage &img, bool update) const
{
if ((update || m_coverRequested) && !img.isNull()) {
setCoverImage(img);
return true;
}
return false;
}
const QPixmap & MusicLibraryItemPodcast::cover() const
{
if (m_cover) {
return *m_cover;
}
int iSize=MusicLibraryItemAlbum::iconSize(largeImages());
int cSize=iSize;
if (0==cSize) {
cSize=22;
}
if (!m_cover && !m_coverRequested) {
m_coverRequested = true;
QImage img=OnlineServicesModel::self()->requestImage(static_cast<OnlineService *>(parentItem())->id(), data(), QString(), m_imageUrl.toString(), // ??
m_imageFile, 300);
if (!img.isNull()) {
setCoverImage(img);
return *m_cover;
}
}
if (!theDefaultIcon || theDefaultIcon->width()!=cSize) {
if (theDefaultIcon) {
delete theDefaultIcon;
}
theDefaultIcon = new QPixmap(Icons::self()->podcastIcon.pixmap(cSize, cSize)
.scaled(QSize(cSize, cSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
return *theDefaultIcon;
}
void MusicLibraryItemPodcast::remove(int row)
{
delete m_childItems.takeAt(row);
resetRows();
}
void MusicLibraryItemPodcast::remove(MusicLibraryItemSong *i)
{
int idx=m_childItems.indexOf(i);
if (-1!=idx) {
remove(idx);
}
}
void MusicLibraryItemPodcast::clearImage() const
{
if (m_cover) {
delete m_cover;
m_cover=0;
}
}
void MusicLibraryItemPodcast::removeFiles()
{
if (!m_fileName.isEmpty() && QFile::exists(m_fileName)) {
QFile::remove(m_fileName);
}
if (!m_imageFile.isEmpty() && QFile::exists(m_imageFile)) {
QFile::remove(m_imageFile);
}
}
void MusicLibraryItemPodcast::setUnplayedCount()
{
m_unplayedEpisodeCount=childCount();
foreach (MusicLibraryItem *i, m_childItems) {
if (static_cast<MusicLibraryItemSong *>(i)->song().hasBeenPlayed()) {
m_unplayedEpisodeCount--;
}
}
}
void MusicLibraryItemPodcast::setPlayed(MusicLibraryItemSong *song)
{
if (!song->song().hasBeenPlayed()) {
song->setPlayed(true);
m_unplayedEpisodeCount--;
}
}
void MusicLibraryItemPodcast::addAll(const QList<MusicLibraryItemPodcastEpisode *> &others)
{
foreach (MusicLibraryItemPodcastEpisode *i, others) {
static_cast<MusicLibraryItemSong *>(i)->setPodcastImage(m_imageFile);
i->setParent(this);
}
}
MusicLibraryItemPodcastEpisode * MusicLibraryItemPodcast::getEpisode(const QString &file) const
{
foreach (MusicLibraryItem *i, m_childItems) {
if (static_cast<MusicLibraryItemSong *>(i)->file()==file) {
return static_cast<MusicLibraryItemPodcastEpisode *>(i);
}
}
return 0;
}
bool MusicLibraryItemPodcast::largeImages() const
{
return m_parentItem && Type_Root==m_parentItem->itemType() &&
static_cast<MusicLibraryItemRoot *>(m_parentItem)->useLargeImages();
}
const QString & MusicLibraryItemPodcastEpisode::published()
{
if (publishedDate.isEmpty()) {
QDateTime dt=QDateTime::fromString(song().podcastPublishedDate(), Qt::ISODate);
publishedDate=dt.toString(Qt::LocalDate);
}
return publishedDate;
}