Files
cantata/models/streamsmodel.cpp
2013-05-08 18:26:12 +00:00

1006 lines
29 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2013 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 <QModelIndex>
#include <QDataStream>
#include <QTextStream>
#include <QMimeData>
#include <QStringList>
#include <QTimer>
#include <QDir>
#include <QFile>
#include <qglobal.h>
#include <QCoreApplication>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QNetworkReply>
#include "config.h"
#include "settings.h"
#if defined Q_OS_WIN
#include <QDesktopServices>
#endif
#include "localize.h"
#include "itemview.h"
#include "streamsmodel.h"
#include "playqueuemodel.h"
#include "mpdconnection.h"
#include "config.h"
#include "icons.h"
#include "utils.h"
#include "qtiocompressor/qtiocompressor.h"
#include "networkaccessmanager.h"
#include "stdactions.h"
#include "mpdparseutils.h"
#ifdef ENABLE_KDE_SUPPORT
K_GLOBAL_STATIC(StreamsModel, instance)
#endif
StreamsModel * StreamsModel::self()
{
#ifdef ENABLE_KDE_SUPPORT
return instance;
#else
static StreamsModel *instance=0;
if(!instance) {
instance=new StreamsModel;
}
return instance;
#endif
}
const QString StreamsModel::constPrefix("cantata-");
static const QString constStreamCategoryMimeType("cantata/streams-category");
static const QString constStreamMimeType("cantata/stream");
static const QLatin1String constSeparator("##Cantata##");
const QLatin1String StreamsModel::constGenreSeparator("|");
static QString encodeStreamItem(StreamsModel::StreamItem *i)
{
return i->name.replace(constSeparator, " ")+constSeparator+
i->url.toString()+constSeparator+
i->genreString()+constSeparator+
i->icon+constSeparator+
i->parent->name;
}
struct DndStream
{
QString name;
QString url;
QString genre;
QString icon;
QString category;
};
static DndStream decodeStreamItem(const QString &s)
{
DndStream i;
QStringList parts=s.split(constSeparator);
if (parts.size()>=5) {
i.name=parts.at(0);
i.url=parts.at(1);
i.genre=parts.at(2);
i.icon=parts.at(3);
i.category=parts.at(4);
}
return i;
}
static const QLatin1String constStreamsCompressedFileName("streams.xml.gz");
static const QLatin1String constStreamsOldFileName("streams.xml");
static void convertOldFile(const QString &compressedName)
{
if (compressedName.startsWith("http:/")) {
return;
}
QString prev=compressedName;
prev.replace(constStreamsCompressedFileName, constStreamsOldFileName);
if (QFile::exists(prev) && !QFile::exists(compressedName)) {
QFile old(prev);
if (old.open(QIODevice::ReadOnly)) {
QByteArray a=old.readAll();
old.close();
QFile newFile(compressedName);
QtIOCompressor compressor(&newFile);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::WriteOnly)) {
compressor.write(a);
compressor.close();
QFile::remove(prev);
}
}
}
}
QString StreamsModel::dir()
{
return Settings::self()->storeStreamsInMpdDir() ? MPDConnection::self()->getDetails().dir : Utils::configDir(QString(), false);
}
static QString getInternalFile(bool createDir=false)
{
if (Settings::self()->storeStreamsInMpdDir()) {
return MPDConnection::self()->getDetails().dir+constStreamsCompressedFileName;
}
return Utils::configDir(QString(), createDir)+constStreamsCompressedFileName;
}
StreamsModel::StreamsModel()
: ActionModel(0)
, modified(false)
, timer(0)
, job(0)
{
}
StreamsModel::~StreamsModel()
{
}
QVariant StreamsModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const
{
return QVariant();
}
int StreamsModel::rowCount(const QModelIndex &index) const
{
if (!index.isValid()) {
return items.size();
}
Item *item=static_cast<Item *>(index.internalPointer());
if (item->isCategory()) {
return static_cast<CategoryItem *>(index.internalPointer())->streams.count();
}
return 0;
}
bool StreamsModel::hasChildren(const QModelIndex &parent) const
{
return !parent.isValid() || static_cast<Item *>(parent.internalPointer())->isCategory();
}
QModelIndex StreamsModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) {
return QModelIndex();
}
Item *item=static_cast<Item *>(index.internalPointer());
if(item->isCategory())
return QModelIndex();
else
{
StreamItem *stream=static_cast<StreamItem *>(item);
if (stream->parent) {
return createIndex(items.indexOf(stream->parent), 0, stream->parent);
}
}
return QModelIndex();
}
QModelIndex StreamsModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
if (parent.isValid()) {
Item *p=static_cast<Item *>(parent.internalPointer());
if (p->isCategory()) {
CategoryItem *cat=static_cast<CategoryItem *>(p);
return row<cat->streams.count() ? createIndex(row, column, cat->streams.at(row)) : QModelIndex();
}
}
return row<items.count() ? createIndex(row, column, items.at(row)) : QModelIndex();
}
QVariant StreamsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
Item *item=static_cast<Item *>(index.internalPointer());
if (item->isCategory()) {
CategoryItem *cat=static_cast<CategoryItem *>(item);
switch(role) {
case Qt::DisplayRole: return cat->name;
case Qt::ToolTipRole:
return 0==cat->streams.count()
? cat->name
: cat->name+"\n"+
#ifdef ENABLE_KDE_SUPPORT
i18np("1 Stream", "%1 Streams", cat->streams.count());
#else
QTP_STREAMS_STR(cat->streams.count());
#endif
case Qt::DecorationRole: {
if (!cat->icon.isEmpty()) {
QIcon i=icon(cat->icon);
if (!i.isNull()) {
return i;
}
}
return Icons::streamCategoryIcon;
}
case ItemView::Role_SubText:
#ifdef ENABLE_KDE_SUPPORT
return i18np("1 Stream", "%1 Streams", cat->streams.count());
#else
return QTP_STREAMS_STR(cat->streams.count());
#endif
case ItemView::Role_Actions: {
// QVariant v;
// v.setValue<QList<Action *> >(QList<Action *>() << StdActions::self()->replacePlayQueueAction);
// return v;
break;
}
}
} else {
StreamItem *stream=static_cast<StreamItem *>(item);
switch(role) {
case Qt::DisplayRole: return stream->name;
case ItemView::Role_SubText:
case Qt::ToolTipRole: return stream->url;
case Qt::DecorationRole: {
if (!stream->icon.isEmpty()) {
QIcon i=icon(stream->icon);
if (!i.isNull()) {
return i;
}
}
return Icons::radioStreamIcon;
}
case ItemView::Role_Actions: {
QVariant v;
v.setValue<QList<Action *> >(QList<Action *>() << StdActions::self()->replacePlayQueueAction);
return v;
}
}
}
return QVariant();
}
void StreamsModel::reload()
{
beginResetModel();
clearCategories();
// clearCategories sets modified, so we need to reset here - otherwise we save file on exit, eventhough nothing has changed!
modified=false;
load(getInternalFile(), true);
endResetModel();
}
void StreamsModel::save(bool force)
{
if (force) {
if (timer) {
timer->stop();
}
persist();
} else if (!QFile::exists(getInternalFile(false)) || !QFileInfo(getInternalFile(false)).isWritable()) {
if (timer) {
timer->stop();
}
persist(); // Call persist now so as to log errors immediately
} else {
if (!timer) {
timer=new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(persist()));
}
timer->start(30*1000);
}
}
bool StreamsModel::load(const QString &filename, bool isInternal)
{
if (isInternal) {
if (filename.startsWith("http:/")) {
if (job) {
return false;
}
emit downloading(true);
job=NetworkAccessManager::self()->get(QUrl(filename));
connect(job, SIGNAL(finished()), SLOT(downloadFinished()));
return true;
} else {
convertOldFile(filename);
}
}
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
// Check for gzip header...
QByteArray header=file.read(2);
bool isCompressed=((unsigned char)header[0])==0x1f && ((unsigned char)header[1])==0x8b;
file.seek(0);
QtIOCompressor compressor(&file);
if (isCompressed) {
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (!compressor.open(QIODevice::ReadOnly)) {
return false;
}
}
return load(isCompressed ? (QIODevice *)&compressor : (QIODevice *)&file, isInternal);
}
void StreamsModel::downloadFinished()
{
QNetworkReply *reply=qobject_cast<QNetworkReply *>(sender());
if (reply==job) {
job=0;
if(QNetworkReply::NoError==reply->error()) {
QtIOCompressor comp(reply);
comp.setStreamFormat(QtIOCompressor::GzipFormat);
if (comp.open(QIODevice::ReadOnly)) {
beginResetModel();
if (!load(&comp, true)) {
emit error(i18n("Failed to parse downloaded stream list."));
}
endResetModel();
} else {
emit error(i18n("Failed to read downloaded stream list."));
}
} else {
emit error(i18n("Failed to download stream list."));
}
emit downloading(false);
}
reply->deleteLater();
}
bool StreamsModel::load(QIODevice *dev, bool isInternal)
{
QXmlStreamReader doc(dev);
bool haveInserted=false;
int level=0;
CategoryItem *cat=0;
QString unknown=i18n("Unknown");
while (!doc.atEnd()) {
doc.readNext();
if (doc.isStartElement()) {
++level;
if (2==level && QLatin1String("category")==doc.name()) {
QString catName=doc.attributes().value("name").toString();
QString catIcon=doc.attributes().value("icon").toString();
cat=getCategory(catName, true, !isInternal);
if (cat && cat->icon.isEmpty() && !catIcon.isEmpty()) {
cat->icon=catIcon;
}
} else if (cat && 3==level && QLatin1String("stream")==doc.name()) {
QString name=doc.attributes().value("name").toString();
QString icon=doc.attributes().value("icon").toString();
QString genre=doc.attributes().value("genre").toString();
QString origName=name;
QUrl url=QUrl(doc.attributes().value("url").toString());
if (!name.isEmpty() && url.isValid() && (isInternal || !entryExists(cat, QString(), url))) {
int i=1;
for (; i<100 && entryExists(cat, name); ++i) {
name=origName+QLatin1String("_")+QString::number(i);
}
if (i<100) {
if (!haveInserted) {
haveInserted=true;
}
if (!isInternal) {
beginInsertRows(createIndex(items.indexOf(cat), 0, cat), cat->streams.count(), cat->streams.count());
}
StreamItem *stream=new StreamItem(name, genre.isEmpty() ? unknown : genre, icon, url, cat);
cat->itemMap.insert(url.toString(), stream);
cat->streams.append(stream);
if (!isInternal) {
endInsertRows();
}
}
}
}
} else if (doc.isEndElement()) {
--level;
if (2==level && QLatin1String("category")==doc.name()) {
cat=0;
}
}
}
if (haveInserted) {
updateGenres();
}
if (haveInserted && !isInternal) {
modified=true;
save();
}
return haveInserted;
}
bool StreamsModel::save(const QString &filename, const QSet<StreamsModel::Item *> &selection)
{
QFile file(filename);
QtIOCompressor compressor(&file);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (!compressor.open(QIODevice::WriteOnly)) {
return false;
}
QXmlStreamWriter doc(&compressor);
doc.writeStartDocument();
doc.writeStartElement("streams");
doc.writeAttribute("version", "1.0");
if (filename==getInternalFile(false)) {
doc.setAutoFormatting(false);
} else {
doc.setAutoFormatting(true);
doc.setAutoFormattingIndent(1);
}
QString unknown=i18n("Unknown");
foreach (CategoryItem *c, items) {
if (selection.isEmpty() || selection.contains(c)) {
doc.writeStartElement("category");
doc.writeAttribute("name", c->name);
if (!c->icon.isEmpty()) {
doc.writeAttribute("icon", c->icon);
}
foreach (StreamItem *s, c->streams) {
if (selection.isEmpty() || selection.contains(s)) {
doc.writeStartElement("stream");
doc.writeAttribute("name", s->name);
doc.writeAttribute("url", s->url.toString());
if (!s->icon.isEmpty()) {
doc.writeAttribute("icon", s->icon);
}
QSet<QString> genres=s->genres;
genres.remove(unknown);
if (!genres.isEmpty()) {
doc.writeAttribute("genre", QStringList(genres.toList()).join(constGenreSeparator));
}
doc.writeEndElement();
}
}
doc.writeEndElement();
}
}
doc.writeEndElement();
doc.writeEndDocument();
return true;
}
bool StreamsModel::add(const QString &cat, const QString &name, const QString &genre, const QString &icon, const QString &url)
{
CategoryItem *c=getCategory(cat, true, true);
if (entryExists(c, name, url)) {
return false;
}
beginInsertRows(createIndex(items.indexOf(c), 0, c), c->streams.count(), c->streams.count());
StreamItem *stream=new StreamItem(name, genreSet(genre), icon, QUrl(url), c);
c->itemMap.insert(url, stream);
c->streams.append(stream);
endInsertRows();
updateGenres();
modified=true;
save();
return true;
}
void StreamsModel::add(const QString &cat, const QString &icon, const QList<StreamsModel::StreamItem *> &streams)
{
if (streams.isEmpty()) {
return;
}
StreamsModel::CategoryItem *ci=getCategory(cat, false, true);
if (ci) {
removeCategory(ci);
}
ci=getCategory(cat, true, true);
ci->icon=icon;
beginInsertRows(createIndex(items.indexOf(ci), 0, ci), 0, streams.count()-1);
foreach (StreamsModel::StreamItem *s, streams) {
s->parent=ci;
ci->itemMap.insert(s->url.toString(), s);
ci->streams.append(s);
}
endInsertRows();
updateGenres();
modified=true;
save();
}
void StreamsModel::editCategory(const QModelIndex &index, const QString &name, const QString &icon)
{
if (!index.isValid()) {
return;
}
Item *item=static_cast<Item *>(index.internalPointer());
if (item->isCategory() && (item->name!=name || item->icon!=icon)) {
item->name=name;
item->icon=icon;
emit dataChanged(index, index);
modified=true;
save();
}
}
void StreamsModel::editStream(const QModelIndex &index, const QString &oldCat, const QString &newCat, const QString &name, const QString &genre, const QString &icon, const QString &url)
{
if (!index.isValid()) {
return;
}
CategoryItem *cat=getCategory(oldCat);
if (!cat) {
return;
}
if (!newCat.isEmpty() && oldCat!=newCat) {
if(add(newCat, name, genre, icon, url)) {
updateGenres();
remove(index);
}
return;
}
int row=index.row();
if (row<cat->streams.count()) {
StreamItem *stream=cat->streams.at(row);
QString oldUrl(stream->url.toString());
stream->name=name;
stream->url=url;
stream->icon=icon;
if (oldUrl!=url) {
cat->itemMap.remove(oldUrl);
cat->itemMap.insert(url, stream);
}
QSet<QString> genres=genreSet(genre);
if (stream->genres!=genres) {
stream->genres=genres;
updateGenres();
}
emit dataChanged(index, index);
modified=true;
save();
}
}
void StreamsModel::remove(const QModelIndex &index)
{
if (!index.isValid()) {
return;
}
int row=index.row();
Item *item=static_cast<Item *>(index.internalPointer());
if (item->isCategory()) {
if (row<items.count()) {
CategoryItem *old=items.at(row);
beginRemoveRows(QModelIndex(), row, row);
items.removeAt(row);
delete old;
endRemoveRows();
modified=true;
updateGenres();
}
} else {
StreamItem *stream=static_cast<StreamItem *>(item);
if (row<stream->parent->streams.count()) {
CategoryItem *cat=stream->parent;
StreamItem *old=cat->streams.at(row);
/*if (1==cat->streams.count()) {
int catRow=items.indexOf(cat);
beginRemoveRows(QModelIndex(), catRow, catRow);
items.removeAt(catRow);
delete cat;
endRemoveRows();
} else*/ {
beginRemoveRows(createIndex(items.indexOf(cat), 0, cat), row, row);
cat->streams.removeAt(row);
cat->itemMap.remove(old->url.toString());
endRemoveRows();
delete old;
updateGenres();
}
modified=true;
}
}
save();
}
void StreamsModel::removeCategory(CategoryItem *cat)
{
int row=items.indexOf(cat);
if (-1==row) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
items.removeAt(row);
delete cat;
endRemoveRows();
modified=true;
}
void StreamsModel::removeStream(StreamItem *stream)
{
int parentRow=items.indexOf(stream->parent);
if (-1==parentRow) {
return;
}
CategoryItem *cat=stream->parent;
int row=cat->streams.indexOf(stream);
if (-1==row) {
return;
}
beginRemoveRows(createIndex(parentRow, 0, cat), row, row);
cat->streams.removeAt(row);
cat->itemMap.remove(stream->url.toString());
endRemoveRows();
delete stream;
modified=true;
}
void StreamsModel::removeStream(const QString &category, const QString &name, const QString &url)
{
CategoryItem *cat=getCategory(category);
if (!cat) {
return;
}
int parentRow=items.indexOf(cat);
if (-1==parentRow) {
return;
}
StreamItem *stream=getStream(cat, name, QUrl(url));
if (0==stream) {
return;
}
int row=cat->streams.indexOf(stream);
if (-1==row) {
return;
}
beginRemoveRows(createIndex(parentRow, 0, cat), row, row);
cat->streams.removeAt(row);
cat->itemMap.remove(stream->url.toString());
endRemoveRows();
delete stream;
if (cat->streams.isEmpty()) {
removeCategory(cat);
}
modified=true;
}
StreamsModel::CategoryItem * StreamsModel::getCategory(const QString &name, bool create, bool signal)
{
foreach (CategoryItem *c, items) {
if (c->name==name) {
return c;
}
}
if (create) {
CategoryItem *cat=new CategoryItem(name);
if (signal) {
beginInsertRows(QModelIndex(), items.count(), items.count());
}
items.append(cat);
if (signal) {
endInsertRows();
}
return cat;
}
return 0;
}
QString StreamsModel::name(CategoryItem *cat, const QString &url)
{
if(cat) {
QHash<QString, StreamItem *>::ConstIterator it=cat->itemMap.find(url);
return it==cat->itemMap.end() ? QString() : it.value()->name;
}
return QString();
}
StreamsModel::StreamItem * StreamsModel::getStream(CategoryItem *cat, const QString &name, const QUrl &url)
{
if(cat) {
foreach (StreamItem *s, cat->streams) {
if ( (!name.isEmpty() && s->name==name) || (!url.isEmpty() && s->url==url)) {
return s;
}
}
}
return 0;
}
Qt::ItemFlags StreamsModel::flags(const QModelIndex &index) const
{
if (index.isValid()) {
return index.internalPointer() && static_cast<Item*>(index.internalPointer())->isCategory()
? (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled)
: (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled);
} else {
return Qt::NoItemFlags;
}
}
Qt::DropActions StreamsModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
bool StreamsModel::validProtocol(const QString &file) const
{
QString scheme=QUrl(file).scheme();
return scheme.isEmpty() || MPDConnection::self()->urlHandlers().contains(scheme);
}
QString StreamsModel::modifyUrl(const QString &u, bool addPrefix, const QString &name)
{
return MPDParseUtils::addStreamName(!addPrefix || !u.startsWith("http:") ? u : (constPrefix+u), name);
}
QStringList StreamsModel::filenames(const QModelIndexList &indexes, bool addPrefix) const
{
QStringList fnames;
QSet<Item *> selectedCategories;
foreach(QModelIndex index, indexes) {
Item *item=static_cast<Item *>(index.internalPointer());
if (item->isCategory()) {
selectedCategories.insert(item);
foreach (const StreamItem *s, static_cast<CategoryItem*>(item)->streams) {
QString f=s->url.toString();
if (!fnames.contains(f) && validProtocol(f)) {
fnames << modifyUrl(f, addPrefix, s->name);
}
}
} else if (!selectedCategories.contains(static_cast<StreamItem*>(item)->parent)) {
QString f=static_cast<StreamItem*>(item)->url.toString();
if (!fnames.contains(f) && validProtocol(f)) {
fnames << modifyUrl(f, addPrefix, static_cast<StreamItem*>(item)->name);
}
}
}
return fnames;
}
QMimeData * StreamsModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, filenames(indexes, true));
QStringList categories;
QStringList streams;
foreach(QModelIndex index, indexes) {
Item *item=static_cast<Item *>(index.internalPointer());
if (item->isCategory()) {
categories.append(item->name);
} else {
streams.append(encodeStreamItem(static_cast<StreamItem *>(item)));
}
}
if (!categories.isEmpty()) {
PlayQueueModel::encode(*mimeData, constStreamCategoryMimeType, categories);
}
if (!streams.isEmpty()) {
PlayQueueModel::encode(*mimeData, constStreamMimeType, streams);
}
return mimeData;
}
bool StreamsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int col, const QModelIndex &parent)
{
Q_UNUSED(col)
Q_UNUSED(row)
if (!writable) {
return false;
}
if (Qt::IgnoreAction==action) {
return true;
}
if (!parent.isValid()) {
return false;
}
if (data->hasFormat(constStreamCategoryMimeType)) {
// Cant drag categories onto categories...
return false;
}
if (data->hasFormat(constStreamMimeType)) {
Item *item=static_cast<Item *>(parent.internalPointer());
if (!item->isCategory()) {
// Should not happen, due to flags() - but make sure!!!
return false;
}
CategoryItem *dest=static_cast<CategoryItem *>(item);
QStringList streams=PlayQueueModel::decode(*data, constStreamMimeType);
bool ok=false;
foreach (const QString &s, streams) {
DndStream stream=decodeStreamItem(s);
if (!stream.category.isEmpty() && stream.category!=dest->name) {
if (add(dest->name, stream.name, stream.genre, stream.icon, stream.url)) {
removeStream(stream.category, stream.name, stream.url);
}
ok=true;
}
}
return ok;
}
return false;
}
QStringList StreamsModel::mimeTypes() const
{
QStringList types;
types << PlayQueueModel::constFileNameMimeType << constStreamCategoryMimeType;
return types;
}
void StreamsModel::persist()
{
if (modified) {
QString fileName=getInternalFile(true);
modified=false;
if (items.isEmpty()) {
// No entries, so remove file...
if (QFile::exists(fileName) && !QFile::remove(fileName)) {
emit error(i18n("Failed to save stream list. Please check %1 is writable.").arg(fileName));
reload();
}
}
else if (save(fileName)) {
Utils::setFilePerms(fileName);
} else {
emit error(i18n("Failed to save stream list. Please check %1 is writable.").arg(fileName));
reload();
}
}
}
void StreamsModel::updateGenres()
{
QSet<QString> genres;
foreach (CategoryItem *c, items) {
c->genres.clear();
foreach (const StreamItem *s, c->streams) {
c->genres+=s->genres;
genres+=s->genres;
}
}
emit updateGenres(genres);
}
bool StreamsModel::checkWritable()
{
QString dirName=dir();
bool isHttp=dirName.startsWith("http:/");
writable=!isHttp && QFileInfo(dirName).isWritable();
if (writable) {
QString fileName=getInternalFile(false);
if (QFile::exists(fileName) && !QFile(fileName).isWritable()) {
writable=false;
}
}
return writable;
}
Action * StreamsModel::getAction(const QModelIndex &idx, int num)
{
Q_UNUSED(idx)
return 0==num ? StdActions::self()->replacePlayQueueAction : 0;
}
void StreamsModel::clearCategories()
{
qDeleteAll(items);
items.clear();
modified=true;
}
void StreamsModel::CategoryItem::clearStreams()
{
qDeleteAll(streams);
streams.clear();
}
const QMap<QString, QIcon> & StreamsModel::icons()
{
static bool loaded=false;
if (!loaded) {
loaded=true;
#ifdef Q_OS_WIN
QString dir(QCoreApplication::applicationDirPath()+"/streamicons/");
#else
QString dir(QString(INSTALL_PREFIX"/share/")+QCoreApplication::applicationName()+"/streamicons/");
#endif
QStringList names=QDir(dir).entryList(QStringList() << "*.svg" << "*.png");
foreach (const QString &name, names) {
QString n=QString(name).remove(".svg").remove(".png");
if (!iconMap.contains(n)) {
iconMap.insert(n, QIcon(dir+name));
}
}
}
return iconMap;
}
QIcon StreamsModel::icon(const QString &name) const
{
if (name.isEmpty()) {
return QIcon();
}
if (!iconMap.contains(name)) {
#ifdef Q_OS_WIN
QString dir(QCoreApplication::applicationDirPath()+"/streamicons/");
#else
QString dir(QString(INSTALL_PREFIX"/share/")+QCoreApplication::applicationName()+"/streamicons/");
#endif
iconMap.insert(name, QFile::exists(dir+name+".svg") ? QIcon(dir+name+".svg") : (QFile::exists(dir+name+".png") ? QIcon(dir+name+".png") : QIcon()));
}
return iconMap[name];
}