diff --git a/CMakeLists.txt b/CMakeLists.txt index c7d5ca9e4..687d7bc93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,7 @@ set(CANTATA_SRCS gui/application.cpp gui/main.cpp gui/initialsettingswizard.cpp gui/settings.cpp gui/covers.cpp gui/filesettings.cpp gui/interfacesettings.cpp gui/playbacksettings.cpp gui/serversettings.cpp gui/librarypage.cpp gui/albumspage.cpp gui/folderpage.cpp gui/playlistspage.cpp gui/trayitem.cpp gui/cachesettings.cpp gui/coverdialog.cpp gui/stdactions.cpp - streams/streamspage.cpp streams/streamdialog.cpp streams/streamcategorydialog.cpp streams/streamfetcher.cpp streams/webstreams.cpp + streams/streamspage.cpp streams/streamdialog.cpp streams/streamfetcher.cpp models/musiclibraryitemroot.cpp models/musiclibraryitemartist.cpp models/musiclibraryitemalbum.cpp models/musiclibrarymodel.cpp models/musiclibraryproxymodel.cpp models/playlistsmodel.cpp models/playlistsproxymodel.cpp models/playqueuemodel.cpp models/playqueueproxymodel.cpp models/dirviewmodel.cpp models/dirviewproxymodel.cpp models/dirviewitem.cpp models/dirviewitemdir.cpp @@ -114,7 +114,7 @@ set(CANTATA_MOC_HDRS gui/initialsettingswizard.h gui/mainwindow.h gui/settings.h gui/covers.h gui/folderpage.h gui/librarypage.h gui/albumspage.h gui/playlistspage.h gui/playbacksettings.h gui/serversettings.h gui/preferencesdialog.h gui/filesettings.h gui/interfacesettings.h gui/cachesettings.h gui/trayitem.h gui/coverdialog.h - streams/streamspage.h streams/streamdialog.h streams/streamcategorydialog.h streams/streamfetcher.h streams/webstreams.h + streams/streamspage.h streams/streamdialog.h streams/streamfetcher.h models/musiclibrarymodel.h models/musiclibraryproxymodel.h models/playlistsmodel.h models/playlistsproxymodel.h models/playqueuemodel.h models/playqueueproxymodel.h models/dirviewmodel.h models/dirviewproxymodel.h models/albumsmodel.h models/streamsmodel.h models/actionmodel.h @@ -402,8 +402,7 @@ endif (MSVC) add_subdirectory(po) add_subdirectory(support) -add_subdirectory(streams/icons) - + if (ENABLE_KDE_SUPPORT) include_directories(${KDE4_INCLUDES}) add_definitions(${KDE4_DEFINITIONS}) diff --git a/ChangeLog b/ChangeLog index 53a5137f2..9fd1f711c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -51,56 +51,54 @@ 33. Automatic accelator assignment for Qt builds. 34. Add cmake check to see if TagLib has id3version in MPEG save. 35. RTL fixes. -36. Add import/export of Streams XML files - these contians just streams, - without categories. The intention is to be able to import these into - MPDroid. -37. For Qt4 linux builds, use system QJson if found. -38. Remove amazon cover fetching - required API key that Cantata never really +36. For Qt4 linux builds, use system QJson if found. +37. Remove amazon cover fetching - required API key that Cantata never really had. -39. Add debug logging. Please see README for details. -40. Enable MPD HTTP stream playback using QtMultiMedia for Qt5 builds. Thanks +38. Add debug logging. Please see README for details. +39. Enable MPD HTTP stream playback using QtMultiMedia for Qt5 builds. Thanks to Marcel Bosling for the patch. Disabled by default, to enable pass -DENABLE_HTTP_STREAM_PLAYBACK=ON to cmake. -41. Fix Qt5 segfault on exit, due to static QIcons being destructed. -42. Work-around Qt5 bug where toolbuttons (usually with menus) stay in the +40. Fix Qt5 segfault on exit, due to static QIcons being destructed. +41. Work-around Qt5 bug where toolbuttons (usually with menus) stay in the raised state. -43. Add port number to library cache filename, to cater for scenarios where +42. Add port number to library cache filename, to cater for scenarios where there is more than 1 server on the same host. -44. Fix retrieval of covers in albums view for multiple-artist albums when +43. Fix retrieval of covers in albums view for multiple-artist albums when these are configured to be grouped under "Various Artists" -45. Refresh albums view when multiple-artist grouping is changed. -46. Add context menu to replygain and file organizer dialogs to remove items +44. Refresh albums view when multiple-artist grouping is changed. +45. Add context menu to replygain and file organizer dialogs to remove items from list. -47. Also use discogs for artist images in cover dialog. -48. Fix invalid covers showing for online services. -49. For Qt builds, if shortcut is set to default then remove entry from config +46. Also use discogs for artist images in cover dialog. +47. Fix invalid covers showing for online services. +48. For Qt builds, if shortcut is set to default then remove entry from config file. -50. Don't show page shortcuts in tooltips, as tooltip is not updated when +49. Don't show page shortcuts in tooltips, as tooltip is not updated when shortcut is changed. -51. Check that perl is installed before attempting to start cantata-dynamic in +50. Check that perl is installed before attempting to start cantata-dynamic in local mode. -52. If cantata-dynamic is started in server mode, then have it create any +51. If cantata-dynamic is started in server mode, then have it create any missing folders. -53. Simpler proxy settings. -54. Delay loading of local devices at atart-up, so that we have time to add +52. Simpler proxy settings. +53. Delay loading of local devices at atart-up, so that we have time to add device to view before try to expand it. -55. If cantata-dynamic is started in server mode, then communicate status via +54. If cantata-dynamic is started in server mode, then communicate status via UDP multicast messages. -56. If using server mode cantata-dynamic and this is not started, then show an +55. If using server mode cantata-dynamic and this is not started, then show an error message in dynamic page. -57. Fix keyboard shortcuts of tab pages. -58. Add support for a simple profile where MPD is started by cantata, and +56. Fix keyboard shortcuts of tab pages. +57. Add support for a simple profile where MPD is started by cantata, and the only settings are the music folder and cover names. -59. Combine Output and Playback config pages. -60. Remove proxy config from settings, and always use system proxy. +58. Combine Output and Playback config pages. +59. Remove proxy config from settings, and always use system proxy. To re-enable proxy settings pass -DENABLE_PROXY_CONFIG=ON to cmake. -61. Add option to draw curent album cover as backdrop to play queue. -62. Add 'Copy Songs To Device' action to playlists page. -63. Copy Qt5 Linux system proxy code for Qt4 builds. -64. Embed pre-rendered PNG versions of cantata icon, to help with Qt5 builds +60. Add option to draw curent album cover as backdrop to play queue. +61. Add 'Copy Songs To Device' action to playlists page. +62. Copy Qt5 Linux system proxy code for Qt4 builds. +63. Embed pre-rendered PNG versions of cantata icon, to help with Qt5 builds on systems that do not have the Qt SVG icon engine installed. -65. Add support for importing Digitally Imported, Jazz Radio, and Sky.fm - streams. Only free, ad supported, streams at the moment. +64. Simplify streams page. Remove user-categories, instead have a set of + predefined top-level items; Favourites (user streams), TuneIn, IceCast, + SomaFM, Digitially Imported, Jazz Radio, and Sky.fm. 1.0.3 ----- diff --git a/cantata.qrc b/cantata.qrc index d716832f6..14d8865a9 100644 --- a/cantata.qrc +++ b/cantata.qrc @@ -1,7 +1,6 @@ context/ultimate_providers.xml -streams/providers.xml context/weblinks.xml icons/view-media-repeat16.png icons/view-media-repeat22.png @@ -11,7 +10,6 @@ icons/view-media-shuffle22.png icons/view-media-shuffle32.png icons/view-media-shuffle48.png - icons/sidebar-albums-dark.svg icons/sidebar-artists-dark.svg icons/sidebar-devices-dark.svg @@ -32,5 +30,13 @@ icons/sidebar-playlists-light.svg icons/sidebar-playqueue-light.svg icons/sidebar-streams-light.svg +icons/streams-icecast.svg +icons/streams-shoutcast.png +icons/streams-favourites.svg +icons/streams-tunein.png +icons/streams-digitallyimported.png +icons/streams-somafm.png +icons/streams-skyfm.png +icons/streams-jazzradio.png diff --git a/gui/filesettings.ui b/gui/filesettings.ui index f7f91b523..229662d6b 100644 --- a/gui/filesettings.ui +++ b/gui/filesettings.ui @@ -49,7 +49,7 @@ - Save list of streams in music folder: + Save list of favourite streams in music folder: storeStreamsInMpdDir diff --git a/gui/initialsettingswizard.ui b/gui/initialsettingswizard.ui index 2138dd47f..7e9f7b24d 100644 --- a/gui/initialsettingswizard.ui +++ b/gui/initialsettingswizard.ui @@ -744,7 +744,7 @@ - Save list of streams in music folder: + Save list of favourite streams in music folder: storeStreamsInMpdDir diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 43fe2560c..01f74623e 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -2192,25 +2192,13 @@ void MainWindow::addToExistingStoredPlaylist(const QString &name, bool pq) void MainWindow::addStreamToPlayQueue() { - // Need to load streams, if not already loaded - so that we can get list of categories/genres... - if (!(loaded&TAB_STREAMS)) { - loaded|=TAB_STREAMS; - streamsPage->refresh(); - } - - StreamDialog dlg(streamsPage->getCategories(), streamsPage->getGenres(), this, true); + StreamDialog dlg(this, true); if (QDialog::Accepted==dlg.exec()) { QString url=dlg.url(); if (dlg.save()) { - QString name=dlg.name(); - QString cat=dlg.category(); - QString existing=StreamsModel::self()->name(cat, url); - - if (existing.isEmpty()) { - StreamsModel::self()->add(cat, name, dlg.genre(), dlg.icon(), url); - } + StreamsModel::self()->addToFavourites(url, dlg.name()); } playQueueModel.addItems(QStringList() << StreamsModel::modifyUrl(url), false, 0); } diff --git a/icons/streams-digitallyimported.png b/icons/streams-digitallyimported.png new file mode 100644 index 000000000..bfef73f5c Binary files /dev/null and b/icons/streams-digitallyimported.png differ diff --git a/streams/icons/heart.svg b/icons/streams-favourites.svg similarity index 100% rename from streams/icons/heart.svg rename to icons/streams-favourites.svg diff --git a/streams/icons/cube.svg b/icons/streams-icecast.svg similarity index 100% rename from streams/icons/cube.svg rename to icons/streams-icecast.svg diff --git a/icons/streams-jazzradio.png b/icons/streams-jazzradio.png new file mode 100644 index 000000000..d0c98c148 Binary files /dev/null and b/icons/streams-jazzradio.png differ diff --git a/icons/streams-shoutcast.png b/icons/streams-shoutcast.png new file mode 100644 index 000000000..e3ee70abd Binary files /dev/null and b/icons/streams-shoutcast.png differ diff --git a/icons/streams-skyfm.png b/icons/streams-skyfm.png new file mode 100644 index 000000000..2243f49bb Binary files /dev/null and b/icons/streams-skyfm.png differ diff --git a/icons/streams-somafm.png b/icons/streams-somafm.png new file mode 100644 index 000000000..23554a584 Binary files /dev/null and b/icons/streams-somafm.png differ diff --git a/icons/streams-tunein.png b/icons/streams-tunein.png new file mode 100644 index 000000000..913797886 Binary files /dev/null and b/icons/streams-tunein.png differ diff --git a/models/streamsmodel.cpp b/models/streamsmodel.cpp index 057bdc018..2c51eb00a 100644 --- a/models/streamsmodel.cpp +++ b/models/streamsmodel.cpp @@ -21,36 +21,33 @@ * Boston, MA 02110-1301, USA. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "config.h" -#include "settings.h" -#if defined Q_OS_WIN -#include -#endif -#include "localize.h" -#include "itemview.h" #include "streamsmodel.h" -#include "playqueuemodel.h" -#include "mpdconnection.h" -#include "config.h" +#include "icon.h" #include "icons.h" -#include "utils.h" -#include "qtiocompressor/qtiocompressor.h" #include "networkaccessmanager.h" -#include "stdactions.h" +#include "localize.h" +#include "utils.h" +#include "mpdconnection.h" #include "mpdparseutils.h" +#include "settings.h" +#include "playqueuemodel.h" +#include "itemview.h" +#include "action.h" +#include "stdactions.h" +#include "qjson/parser.h" +#include "qtiocompressor/qtiocompressor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif #ifdef ENABLE_KDE_SUPPORT K_GLOBAL_STATIC(StreamsModel, instance) @@ -70,73 +67,29 @@ StreamsModel * StreamsModel::self() } 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; -} +static const char * constOrigUrlProperty = "orig-url"; -struct DndStream -{ - QString name; - QString url; - QString genre; - QString icon; - QString category; -}; +static QString constRadioTimeHost=QLatin1String("opml.radiotime.com"); +static QString constRadioTimeUrl=QLatin1String("http://")+constRadioTimeHost+QLatin1String("/Browse.ashx"); +static QString constFavouritesUrl=QLatin1String("cantata://internal"); +static QString constIceCastUrl=QLatin1String("http://dir.xiph.org/yp.xml"); +static QString constSomaFMUrl=QLatin1String("http://somafm.com/channels.xml"); -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 QString constDigitiallyImportedUrl=QLatin1String("http://www.di.fm"); +static QString constJazzRadioUrl=QLatin1String("http://www.jazzradio.com"); +static QString constSkyFmUrl=QLatin1String("http://www.sky.fm"); +static QStringList constDiUrls=QStringList() << constDigitiallyImportedUrl << constJazzRadioUrl << constSkyFmUrl; +static const char * constDiApiUsername="ephemeron"; +static const char * constDiApiPassword="dayeiph0ne@pp"; +//static const QString constDiAuthUrl=QLatin1String("http://api.audioaddict.com/v1/%1/members/authenticate"); +static const QString constDiChannelListHost=QLatin1String("api.v2.audioaddict.com"); +static const QString constDiChannelListUrl=QLatin1String("http://")+constDiChannelListHost+("/v1/%1/mobile/batch_update?asset_group_key=mobile_icons&stream_set_key="); +static const QString constDiStdUrl=QLatin1String("http://%1/public3/%2.pls"); -static const QLatin1String constStreamsCompressedFileName("streams.xml.gz"); -static const QLatin1String constStreamsOldFileName("streams.xml"); +static const QLatin1String constFavouritesFileName("streams.xml.gz"); -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() +QString StreamsModel::favouritesDir() { return Settings::self()->storeStreamsInMpdDir() ? MPDConnection::self()->getDetails().dir : Utils::configDir(QString(), false); } @@ -144,66 +97,32 @@ QString StreamsModel::dir() static QString getInternalFile(bool createDir=false) { if (Settings::self()->storeStreamsInMpdDir()) { - return MPDConnection::self()->getDetails().dir+constStreamsCompressedFileName; + return MPDConnection::self()->getDetails().dir+constFavouritesFileName; } - return Utils::configDir(QString(), createDir)+constStreamsCompressedFileName; + return Utils::configDir(QString(), createDir)+constFavouritesFileName; } -StreamsModel::StreamsModel() - : ActionModel(0) - , modified(false) - , timer(0) - , job(0) +StreamsModel::StreamsModel(QObject *parent) + : ActionModel(parent) + , root(new CategoryItem(QString(), "root")) + , favouritesIsWriteable(true) + , favouritesModified(false) + , favouritesSaveTimer(0) { + root->children.append(new CategoryItem(constRadioTimeUrl, i18n("TuneIn"), root, QIcon(":streams-tunein"))); + root->children.append(new CategoryItem(constIceCastUrl, i18n("IceCast"), root, QIcon(":streams-icecast"))); + root->children.append(new CategoryItem(constSomaFMUrl, i18n("SomaFM"), root, QIcon(":streams-somafm"))); + root->children.append(new CategoryItem(constDigitiallyImportedUrl, i18n("Digitally Imported"), root, QIcon(":streams-digitallyimported"))); + root->children.append(new CategoryItem(constJazzRadioUrl, i18n("JazzRadio.com"), root, QIcon(":streams-jazzradio"))); + root->children.append(new CategoryItem(constSkyFmUrl, i18n("Sky.fm"), root, QIcon(":streams-skyfm"))); + favourites=new CategoryItem(constFavouritesUrl, i18n("Favourites"), root, QIcon(":streams-favourites")); + favourites->isFavourites=true; + root->children.append(favourites); } 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(index.internalPointer()); - if (item->isCategory()) { - return static_cast(index.internalPointer())->streams.count(); - } - return 0; -} - -bool StreamsModel::hasChildren(const QModelIndex &parent) const -{ - return !parent.isValid() || static_cast(parent.internalPointer())->isCategory(); -} - -QModelIndex StreamsModel::parent(const QModelIndex &index) const -{ - if (!index.isValid()) { - return QModelIndex(); - } - - Item *item=static_cast(index.internalPointer()); - - if(item->isCategory()) - return QModelIndex(); - else - { - StreamItem *stream=static_cast(item); - - if (stream->parent) { - return createIndex(items.indexOf(stream->parent), 0, stream->parent); - } - } - - return QModelIndex(); + delete root; } QModelIndex StreamsModel::index(int row, int column, const QModelIndex &parent) const @@ -212,139 +131,599 @@ QModelIndex StreamsModel::index(int row, int column, const QModelIndex &parent) return QModelIndex(); } - if (parent.isValid()) { - Item *p=static_cast(parent.internalPointer()); + const CategoryItem * p = parent.isValid() ? static_cast(parent.internalPointer()) : root; + const Item * c = rowchildren.count() ? p->children.at(row) : 0; + return c ? createIndex(row, column, (void *)c) : QModelIndex(); +} - if (p->isCategory()) { - CategoryItem *cat=static_cast(p); - return rowstreams.count() ? createIndex(row, column, cat->streams.at(row)) : QModelIndex(); - } +QModelIndex StreamsModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) { + return QModelIndex(); } - return rowparent; + + if (!parent || parent == root || !parent->parent) { + return QModelIndex(); + } + + return createIndex(static_cast(parent->parent)->children.indexOf(parent), 0, parent); +} + +QVariant StreamsModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const +{ + return QVariant(); +} + +int StreamsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + Item *item = toItem(parent); + return item->isCategory() ? static_cast(item)->children.count() : 0; + } + + return root->children.count(); +} + +int StreamsModel::columnCount(const QModelIndex &) const +{ + return 1; } QVariant StreamsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) { - return QVariant(); - } + const Item *item = toItem(index); - Item *item=static_cast(index.internalPointer()); - - if (item->isCategory()) { - CategoryItem *cat=static_cast(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; - } + switch (role) { + case Qt::DecorationRole: + if (item->isCategory()) { + const CategoryItem *cat=static_cast(item); + return cat->icon.isNull() ? Icons::self()->streamCategoryIcon : cat->icon; + } else { + return Icon("audio-x-generic"); + } + case Qt::DisplayRole: + return item->name; + case Qt::ToolTipRole: + return item->isCategory() ? item->name : item->url; + case ItemView::Role_SubText: + if (item->isCategory()) { + const CategoryItem *cat=static_cast(item); + switch (cat->state) { + case CategoryItem::Initial: + return i18n("No Loaded"); + case CategoryItem::Fetching: + return i18n("Loading..."); + default: + #ifdef ENABLE_KDE_SUPPORT + return i18np("1 Entry", "%1 Entries", cat->children.count()); + #else + return QTP_ENTRIES_STR(cat->children.count()); + #endif } - return Icons::self()->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() << StdActions::self()->replacePlayQueueAction); -// return v; - break; - } - } - } else { - StreamItem *stream=static_cast(item); - switch(role) { - case Qt::DisplayRole: return stream->name; - case ItemView::Role_SubText: return QVariant(); - 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::self()->radioStreamIcon; - } - case ItemView::Role_Actions: { + break; + case ItemView::Role_Actions: + if (!item->isCategory()){ QVariant v; v.setValue >(QList() << StdActions::self()->replacePlayQueueAction); return v; } - } + break; + default: + break; } - return QVariant(); } -void StreamsModel::reload() +Qt::ItemFlags StreamsModel::flags(const QModelIndex &index) const { - 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(); + if (index.isValid()) { + return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; + } else { + return Qt::NoItemFlags; + } } -void StreamsModel::save(bool force) +bool StreamsModel::hasChildren(const QModelIndex &index) const +{ + return index.isValid() ? toItem(index)->isCategory() : true; +} + +bool StreamsModel::canFetchMore(const QModelIndex &index) const +{ + if (index.isValid()) { + Item *item = toItem(index); + return item->isCategory() && CategoryItem::Initial==static_cast(item)->state && !item->url.isEmpty(); + } else { + return false; + } +} + +void StreamsModel::fetchMore(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + + Item *item = toItem(index); + if (item->isCategory() && !item->url.isEmpty()) { + CategoryItem *cat=static_cast(item); + if (item->url==constFavouritesUrl && !getInternalFile().startsWith(QLatin1String("http://"))) { + cat->state=CategoryItem::Fetching; + emit dataChanged(index, index); + loadFavourites(index); + cat->state=CategoryItem::Fetched; + } else { + QNetworkRequest req; + if (constDiUrls.contains(cat->url)) { + req=QNetworkRequest(constDiChannelListUrl.arg(cat->url.split(".").at(1))); + #if QT_VERSION < 0x050000 + req.setRawHeader("Authorization", "Basic "+QString("%1:%2").arg(constDiApiUsername, constDiApiPassword).toAscii().toBase64()); + #else + req.setRawHeader("Authorization", "Basic "+QString("%1:%2").arg(constDiApiUsername, constDiApiPassword).toLatin1().toBase64()); + #endif + } else { + req=QNetworkRequest(cat->url); + } + + if (cat==favourites && !favourites->children.isEmpty()) { + beginRemoveRows(index, 0, favourites->children.count()-1); + qDeleteAll(favourites->children); + favourites->children.clear(); + endRemoveRows(); + } + + QNetworkReply *job=NetworkAccessManager::self()->get(req); + job->setProperty(constOrigUrlProperty, cat->url); + if (jobs.isEmpty()) { + emit loading(); + } + jobs.insert(job, cat); + connect(job, SIGNAL(finished()), SLOT(jobFinished())); + cat->state=CategoryItem::Fetching; + } + emit dataChanged(index, index); + } +} + +void StreamsModel::saveFavourites(bool force) { if (force) { - if (timer) { - timer->stop(); + if (favouritesSaveTimer) { + favouritesSaveTimer->stop(); } - persist(); + persistFavourites(); } else if (!QFile::exists(getInternalFile(false)) || !QFileInfo(getInternalFile(false)).isWritable()) { - if (timer) { - timer->stop(); + if (favouritesSaveTimer) { + favouritesSaveTimer->stop(); } - persist(); // Call persist now so as to log errors immediately + persistFavourites(); // Call persist now so as to log errors immediately } else { - if (!timer) { - timer=new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(persist())); + if (!favouritesSaveTimer) { + favouritesSaveTimer=new QTimer(this); + connect(favouritesSaveTimer, SIGNAL(timeout()), this, SLOT(persistFavourites())); } - timer->start(30*1000); + favouritesSaveTimer->start(10*1000); } } -bool StreamsModel::load(const QString &filename, bool isInternal) +bool StreamsModel::checkFavouritesWritable() { - 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())); + QString dirName=favouritesDir(); + bool isHttp=dirName.startsWith("http:/"); + favouritesIsWriteable=!isHttp && QFileInfo(dirName).isWritable(); + if (favouritesIsWriteable) { + QString fileName=getInternalFile(false); + if (QFile::exists(fileName) && !QFileInfo(fileName).isWritable()) { + favouritesIsWriteable=false; + } + } + return favouritesIsWriteable; +} + +void StreamsModel::reloadFavourites() +{ + fetchMore(createIndex(root->children.indexOf(favourites), 0, favourites)); +} + +void StreamsModel::removeFromFavourites(const QModelIndex &index) +{ + Item *item=static_cast(index.internalPointer()); + int pos=favourites->children.indexOf(item); + + if (-1!=pos) { + QModelIndex index=createIndex(root->children.indexOf(favourites), 0, (void *)favourites); + beginRemoveRows(index, pos, pos); + delete favourites->children.takeAt(pos); + endRemoveRows(); + favouritesModified=true; + saveFavourites(); + } +} + +void StreamsModel::addToFavourites(const QString &url, const QString &name) +{ + QSet existingNames; + + foreach (Item *i, favourites->children) { + if (i->url==url) { + return; + } + existingNames.insert(i->name); + } + + QString n=name; + int i=1; + for (; i<100 && existingNames.contains(n); ++i) { + n=name+QLatin1String("_")+QString::number(i); + } + + if (i<100) { + QModelIndex index=createIndex(root->children.indexOf(favourites), 0, (void *)favourites); + beginInsertRows(index, favourites->children.count(), favourites->children.count()); + favourites->children.append(new Item(url, n, favourites)); + endInsertRows(); + favouritesModified=true; + saveFavourites(); + } +} + +QString StreamsModel::favouritesNameForUrl(const QString &u) +{ + foreach (Item *i, favourites->children) { + if (i->url==u) { + return i->name; + } + } + return QString(); +} + +bool StreamsModel::nameExistsInFavourites(const QString &n) +{ + foreach (Item *i, favourites->children) { + if (i->name==n) { return true; - } else { - convertOldFile(filename); + } + } + return false; +} + +void StreamsModel::updateFavouriteStream(Item *item) +{ + int pos=favourites->children.indexOf(item); + + if (-1==pos) { + return; + } + QModelIndex index=createIndex(favourites->children.indexOf(item), 0, (void *)item); + favouritesModified=true; + saveFavourites(); + emit dataChanged(index, index); +} + +bool StreamsModel::importXml(const QString &fileName) +{ + return loadXml(fileName, createIndex(root->children.indexOf(favourites), 0, favourites)); +} + +bool StreamsModel::saveXml(const QString &fileName, const QList &items) +{ + QFile file(fileName); + + if (fileName.endsWith(".xml")) { + return file.open(QIODevice::WriteOnly) && saveXml(&file, items.isEmpty() ? favourites->children : items, true); + } else { + QtIOCompressor compressor(&file); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + return compressor.open(QIODevice::WriteOnly) && saveXml(&compressor, items.isEmpty() ? favourites->children : items, false); + } +} + +bool StreamsModel::validProtocol(const QString &file) +{ + 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); +} + +static void filenames(QStringList &fn, bool addPrefix, const StreamsModel::CategoryItem *cat) +{ + foreach (const StreamsModel::Item *i, cat->children) { + if (i->isCategory()) { + filenames(fn, addPrefix, static_cast(i)); + } else if (!fn.contains(i->url) && StreamsModel::validProtocol(i->url)) { + fn << StreamsModel::modifyUrl(i->url, addPrefix, i->name); + } + } +} + +QStringList StreamsModel::filenames(const QModelIndexList &indexes, bool addPrefix) const +{ + QStringList fnames; + foreach(QModelIndex index, indexes) { + Item *item=static_cast(index.internalPointer()); + + if (item->isCategory()) { + ::filenames(fnames, addPrefix, static_cast(item)); + } else if (!fnames.contains(item->url) && validProtocol(item->url)) { + fnames << modifyUrl(item->url, addPrefix, item->name); } } - QFile file(filename); + return fnames; +} + +QMimeData * StreamsModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = new QMimeData(); + PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, filenames(indexes, true)); + return mimeData; +} + +QStringList StreamsModel::mimeTypes() const +{ + QStringList types; + types << PlayQueueModel::constFileNameMimeType; + return types; +} + +void StreamsModel::jobFinished() +{ + QNetworkReply *job=dynamic_cast(sender()); + + if (!job) { + return; + } + + job->deleteLater(); + + if (jobs.contains(job)) { + CategoryItem *cat=jobs[job]; + cat->state=CategoryItem::Fetched; + jobs.remove(job); + + QModelIndex index=createIndex(cat->parent->children.indexOf(cat), 0, (void *)cat); + if (QNetworkReply::NoError==job->error()) { + QList newItems; + if (cat==favourites) { + newItems=loadXml(job, true); + } else if (QLatin1String("http")==job->url().scheme()) { + QString url=job->url().toString(); + if (constRadioTimeHost==job->url().host()) { + newItems=parseRadioTimeResponse(job, cat); + } else if (constIceCastUrl==url) { + newItems=parseIceCastResponse(job, cat); + } else if (constSomaFMUrl==url) { + newItems=parseSomaFmResponse(job, cat); + } else if (constDiChannelListHost==job->url().host()) { + newItems=parseDigitallyImportedResponse(job, cat, job->property(constOrigUrlProperty).toString()); + } + } + + if (!newItems.isEmpty()) { + beginInsertRows(index, 0, newItems.count()-1); + cat->children=newItems; + endInsertRows(); + } + } + emit dataChanged(index, index); + if (jobs.isEmpty()) { + emit loaded(); + } + } +} + +void StreamsModel::persistFavourites() +{ + if (favouritesModified) { + QString fileName=getInternalFile(true); + favouritesModified=false; + if (favourites->children.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)); + reloadFavourites(); + } + } else if (saveXml(fileName, favourites->children)) { + Utils::setFilePerms(fileName); + } else { + emit error(i18n("Failed to save stream list. Please check %1 is writable.").arg(fileName)); + reloadFavourites(); + } + } +} + +QList StreamsModel::parseRadioTimeResponse(QIODevice *dev, CategoryItem *cat) +{ + QList newItems; + QXmlStreamReader doc(dev); + while (!doc.atEnd()) { + doc.readNext(); + if (doc.isStartElement() && QLatin1String("outline")==doc.name()) { + Item *item = parseRadioTimeEntry(doc, cat); + if (item) { + newItems.append(item); + } + } + } + return newItems; +} + +QList StreamsModel::parseIceCastResponse(QIODevice *dev, CategoryItem *cat) +{ + QList newItems; + QXmlStreamReader doc(dev); + while (!doc.atEnd()) { + doc.readNext(); + if (doc.isStartElement() && QLatin1String("entry")==doc.name()) { + Item *item = parseIceCastEntry(doc, cat); + if (item) { + newItems.append(item); + } + } + } + return newItems; +} + +QList StreamsModel::parseSomaFmResponse(QIODevice *dev, CategoryItem *cat) +{ + QList newItems; + QXmlStreamReader doc(dev); + while (!doc.atEnd()) { + doc.readNext(); + if (doc.isStartElement() && QLatin1String("channel")==doc.name()) { + Item *item = parseSomaFmEntry(doc, cat); + if (item) { + newItems.append(item); + } + } + } + return newItems; +} + +QList StreamsModel::parseDigitallyImportedResponse(QIODevice *dev, CategoryItem *cat, const QString &origUrl) +{ + QList newItems; + QJson::Parser parser; + QVariantMap data = parser.parse(dev).toMap(); + QString listenHost=QLatin1String("listen.")+QUrl(origUrl).host().remove("www."); + + if (data.contains("channel_filters")) { + QVariantList filters = data["channel_filters"].toList(); + + foreach (const QVariant &filter, filters) { + // Find the filter called "All" + QVariantMap filterMap = filter.toMap(); + if (filterMap.value("name", QString()).toString() != "All") { + continue; + } + + // Add all its stations to the result + QVariantList channels = filterMap.value("channels", QVariantList()).toList(); + foreach (const QVariant &channel, channels) { + QVariantMap channelMap = channel.toMap(); + QString url=constDiStdUrl.arg(listenHost).arg(channelMap.value("key").toString()); + newItems.append(new Item(url, channelMap.value("name").toString(), cat)); + } + + break; + } + } + + return newItems; +} + +StreamsModel::Item * StreamsModel::parseRadioTimeEntry(QXmlStreamReader &doc, CategoryItem *parent) +{ + Item *item=0; + CategoryItem *cat=0; + while (!doc.atEnd()) { + if (doc.isStartElement()) { + QString text=doc.attributes().value("text").toString(); + if (!text.isEmpty()) { + QString url=doc.attributes().value("URL").toString(); + bool isStation=QLatin1String("audio")==doc.attributes().value("type").toString(); + if (isStation) { + item=new Item(url, text, parent); + } else { + item=cat=new CategoryItem(url, text, parent); + } + } + } + + doc.readNext(); + if (doc.isStartElement() && QLatin1String("outline")==doc.name()) { + Item *child = parseRadioTimeEntry(doc, cat); + if (child) { + if (cat) { + cat->state=CategoryItem::Fetched; + cat->children.append(child); + } else { + delete child; + } + } + } else if (doc.isEndElement() && QLatin1String("outline")==doc.name()) { + break; + } + } + + return item; +} + +StreamsModel::Item * StreamsModel::parseIceCastEntry(QXmlStreamReader &doc, CategoryItem *parent) +{ + QString name; + QString url; + while (!doc.atEnd()) { + doc.readNext(); + + if (QXmlStreamReader::StartElement==doc.tokenType()) { + QStringRef elem = doc.name(); + + if (QLatin1String("server_name")==elem) { + name=doc.readElementText().trimmed(); + } else if (QLatin1String("listen_url")==elem) { + url=doc.readElementText().toFloat(); + } + } else if (doc.isEndElement() && QLatin1String("entry")==doc.name()) { + break; + } + } + + return name.isEmpty() || url.isEmpty() ? 0 : new Item(url, name, parent); +} + +StreamsModel::Item * StreamsModel::parseSomaFmEntry(QXmlStreamReader &doc, CategoryItem *parent) +{ + QString name; + QString url; + QString streamFormat; + + while (!doc.atEnd()) { + doc.readNext(); + + if (QXmlStreamReader::StartElement==doc.tokenType()) { + QStringRef elem = doc.name(); + + if (QLatin1String("title")==elem) { + name=doc.readElementText().trimmed(); + } else if (QLatin1String("fastpls")==elem) { + if (streamFormat.isEmpty() || QLatin1String("mp3")!=streamFormat) { + streamFormat=doc.attributes().value("format").toString(); + url=doc.readElementText(); + } + } + } else if (doc.isEndElement() && QLatin1String("channel")==doc.name()) { + break; + } + } + + return name.isEmpty() || url.isEmpty() ? 0 : new Item(url, name, parent); +} + +void StreamsModel::loadFavourites(const QModelIndex &index) +{ + if (!favourites->children.isEmpty()) { + beginRemoveRows(index, 0, favourites->children.count()-1); + qDeleteAll(favourites->children); + favourites->children.clear(); + endRemoveRows(); + } + loadXml(getInternalFile(false), index); +} + +bool StreamsModel::loadXml(const QString &fileName, const QModelIndex &index) +{ + 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; @@ -357,119 +736,60 @@ bool StreamsModel::load(const QString &filename, bool isInternal) return false; } } - return load(isCompressed ? (QIODevice *)&compressor : (QIODevice *)&file, isInternal); -} -void StreamsModel::downloadFinished() -{ - QNetworkReply *reply=qobject_cast(sender()); + QList newItems=loadXml(isCompressed ? (QIODevice *)&compressor : (QIODevice *)&file, true); - 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); + if (!newItems.isEmpty()) { + beginInsertRows(index, favourites->children.count(), (favourites->children.count()+newItems.count())-1); + favourites->children+=newItems; + endInsertRows(); + return true; } - reply->deleteLater(); + return false; } -bool StreamsModel::load(QIODevice *dev, bool isInternal) +QList StreamsModel::loadXml(QIODevice *dev, bool isInternal) { + QList newItems; QXmlStreamReader doc(dev); - bool haveInserted=false; - CategoryItem *cat=0; - QString unknown=i18n("Unknown"); - QString import=i18n("Import"); + QSet existingUrls; + QSet existingNames; + + if (!isInternal) { + foreach (Item *i, favourites->children) { + existingUrls.insert(i->url); + existingNames.insert(i->name); + } + } while (!doc.atEnd()) { doc.readNext(); - if (doc.isStartElement()) { - if (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 (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 (doc.isStartElement() && QLatin1String("stream")==doc.name()) { + QString name=doc.attributes().value("name").toString(); + QString origName=name; + QString url=doc.attributes().value("url").toString(); - if (!cat) { - cat=getCategory(isInternal ? unknown : import, true, !isInternal); - } - - if (!name.isEmpty() && url.isValid() && (isInternal || !entryExists(cat, QString(), url))) { - int i=1; - for (; i<100 && entryExists(cat, name); ++i) { + if (!name.isEmpty() && !name.isEmpty() && (isInternal || !existingUrls.contains(url))) { + int i=1; + if (!isInternal) { + for (; i<100 && existingNames.contains(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()) { - if (QLatin1String("category")==doc.name()) { - cat=0; + + if (i<100) { + existingNames.insert(name); + existingUrls.insert(url); + newItems.append(new Item(url, name, favourites)); + } } } } - - if (haveInserted) { - updateGenres(); - } - if (haveInserted && !isInternal) { - modified=true; - save(); - } - - return haveInserted; + return newItems; } -bool StreamsModel::save(const QString &filename, const QSet &selection, bool streamsOnly) -{ - QFile file(filename); - - if (streamsOnly) { - return file.open(QIODevice::WriteOnly) && save(&file, selection, streamsOnly, true); - } else { - QtIOCompressor compressor(&file); - compressor.setStreamFormat(QtIOCompressor::GzipFormat); - return compressor.open(QIODevice::WriteOnly) && save(&compressor, selection, streamsOnly, filename!=getInternalFile(false)); - } -} - -bool StreamsModel::save(QIODevice *dev, const QSet &selection, bool streamsOnly, bool format) +bool StreamsModel::saveXml(QIODevice *dev, const QList &items, bool format) const { QXmlStreamWriter doc(dev); doc.writeStartDocument(); @@ -482,536 +802,13 @@ bool StreamsModel::save(QIODevice *dev, const QSet &select doc.setAutoFormatting(false); } - QString unknown=i18n("Unknown"); - - foreach (CategoryItem *c, items) { - if (selection.isEmpty() || selection.contains(c)) { - if (!streamsOnly) { - 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 (!streamsOnly && !s->icon.isEmpty()) { - doc.writeAttribute("icon", s->icon); - } - QSet genres=s->genres; - genres.remove(unknown); - if (!genres.isEmpty()) { - doc.writeAttribute("genre", QStringList(genres.toList()).join(constGenreSeparator)); - } - doc.writeEndElement(); - } - } - if (!streamsOnly) { - doc.writeEndElement(); - } - } + foreach (const Item *i, items) { + doc.writeStartElement("stream"); + doc.writeAttribute("name", i->name); + doc.writeAttribute("url", i->url); + 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 &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(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 (rowstreams.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 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(index.internalPointer()); - - if (item->isCategory()) { - if (row(item); - if (rowparent->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::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(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 selectedCategories; - foreach(QModelIndex index, indexes) { - Item *item=static_cast(index.internalPointer()); - - if (item->isCategory()) { - selectedCategories.insert(item); - foreach (const StreamItem *s, static_cast(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(item)->parent)) { - QString f=static_cast(item)->url.toString(); - if (!fnames.contains(f) && validProtocol(f)) { - fnames << modifyUrl(f, addPrefix, static_cast(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(index.internalPointer()); - - if (item->isCategory()) { - categories.append(item->name); - } else { - streams.append(encodeStreamItem(static_cast(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(parent.internalPointer()); - if (!item->isCategory()) { - // Should not happen, due to flags() - but make sure!!! - return false; - } - CategoryItem *dest=static_cast(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 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) && !QFileInfo(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 & 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]; -} diff --git a/models/streamsmodel.h b/models/streamsmodel.h index fd993ea2c..18a5924bd 100644 --- a/models/streamsmodel.h +++ b/models/streamsmodel.h @@ -20,133 +20,123 @@ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ + #ifndef STREAMSMODEL_H #define STREAMSMODEL_H -#include -#include -#include -#include -#include -#include #include "actionmodel.h" +#include +#include +#include -class QTimer; -class QIODevice; class QNetworkReply; +class QXmlStreamReader; +class QIODevice; +class QTimer; class StreamsModel : public ActionModel { Q_OBJECT public: - static const QString constPrefix; - static QString modifyUrl(const QString &u, bool addPrefix=true, const QString &name=QString()); - static QString dir(); - static const QLatin1String constGenreSeparator; - - static QSet genreSet(const QString &str) { return str.split(constGenreSeparator, QString::SkipEmptyParts).toSet(); } - + struct CategoryItem; struct Item { - Item(const QString &n, const QString &i) : name(n), icon(i) { name.replace("#", ""); } - virtual bool isCategory() = 0; + Item(const QString &u, const QString &n=QString(), CategoryItem *p=0) : url(u), name(n), parent(p) { } virtual ~Item() { } + QString url; QString name; - QString icon; - }; - - struct CategoryItem; - struct StreamItem : public Item - { - StreamItem(const QString &n, const QString &g, const QString &i, const QUrl &u, CategoryItem *p=0) : Item(n, i), genres(genreSet(g)), url(u), parent(p) { } - StreamItem(const QString &n, const QSet &g, const QString &i, const QUrl &u, CategoryItem *p=0) : Item(n, i), genres(g), url(u), parent(p) { } - bool isCategory() { return false; } - QString genreString() const { return QStringList(genres.toList()).join(constGenreSeparator); } - QSet genres; - QUrl url; CategoryItem *parent; + virtual bool isCategory() const { return false; } }; - + struct CategoryItem : public Item { - CategoryItem(const QString &n, const QString &i=QString()) : Item(n, i) { } - virtual ~CategoryItem() { clearStreams(); } - bool isCategory() { return true; } - void clearStreams(); - QHash itemMap; - QList streams; - QSet genres; + enum State + { + Initial, + Fetching, + Fetched + }; + + CategoryItem(const QString &u, const QString &n=QString(), CategoryItem *p=0, const QIcon &i=QIcon()) + : Item(u, n, p), state(Initial), isFavourites(false), icon(i) { } + virtual ~CategoryItem() { qDeleteAll(children); } + virtual bool isCategory() const { return true; } + State state; + bool isFavourites; + QList children; + QIcon icon; }; + + static const QString constPrefix; static StreamsModel * self(); + static QString favouritesDir(); + static QString modifyUrl(const QString &u, bool addPrefix=true, const QString &name=QString()); + static bool validProtocol(const QString &file); - StreamsModel(); + StreamsModel(QObject *parent = 0); ~StreamsModel(); + QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const; + QModelIndex parent(const QModelIndex &) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex&) const { return 1; } - bool hasChildren(const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &index) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; + int columnCount(const QModelIndex &) const; QVariant data(const QModelIndex &, int) const; - void reload(); - void save(bool force=false); - bool save(const QString &filename, const QSet &selection=QSet(), bool streamsOnly=false); - bool import(const QString &filename) { return load(filename, false); } - bool add(const QString &cat, const QString &name, const QString &genre, const QString &icon, const QString &url); - void add(const QString &cat, const QString &icon, const QList &streams); - void editCategory(const QModelIndex &index, const QString &name, const QString &icon); - void editStream(const QModelIndex &index, const QString &oldCat, const QString &newCat, const QString &name, const QString &genre, const QString &icon, const QString &url); - void remove(const QModelIndex &index); - void removeCategory(CategoryItem *cat); - void removeStream(StreamItem *stream); - void removeStream(const QString &category, const QString &name, const QString &url); - QString name(const QString &cat, const QString &url) { return name(getCategory(cat), url); } - bool entryExists(const QString &cat, const QString &name, const QUrl &url=QUrl()) { return entryExists(getCategory(cat), name, url); } Qt::ItemFlags flags(const QModelIndex &index) const; - Qt::DropActions supportedDropActions() const; - bool validProtocol(const QString &file) const; + bool hasChildren(const QModelIndex &index) const; + bool canFetchMore(const QModelIndex &index) const; + void fetchMore(const QModelIndex &index); + + void saveFavourites(bool force=false); + bool haveFavourites() const { return !favourites->children.isEmpty(); } + bool isFavoritesWritable() { return favouritesIsWriteable; } + bool checkFavouritesWritable(); + void reloadFavourites(); + void removeFromFavourites(const QModelIndex &index); + void addToFavourites(const QString &url, const QString &name); + QString favouritesNameForUrl(const QString &u); + bool nameExistsInFavourites(const QString &n); + void updateFavouriteStream(Item *item); + + bool importXml(const QString &fileName); + bool saveXml(const QString &fileName, const QList &items); + QStringList filenames(const QModelIndexList &indexes, bool addPrefix) const; QMimeData * mimeData(const QModelIndexList &indexes) const; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int col, const QModelIndex &parent); QStringList mimeTypes() const; - void mark(const QList &rows, bool f); - void updateGenres(); - bool isWritable() const { return writable; } - bool checkWritable(); - Action * getAction(const QModelIndex &idx, int num); - const QMap & icons(); - QIcon icon(const QString &name) const; Q_SIGNALS: - void downloading(bool); - void updateGenres(const QSet &genres); - void error(const QString &e); + void loading(); + void loaded(); + void error(const QString &msg); private Q_SLOTS: - void downloadFinished(); + void jobFinished(); + void persistFavourites(); private: - bool save(QIODevice *dev, const QSet &selection, bool streamsOnly, bool format); - void clearCategories(); - bool load(const QString &filename, bool isInternal); - bool load(QIODevice *dev, bool isInternal); - CategoryItem * getCategory(const QString &name, bool create=false, bool signal=false); - QString name(CategoryItem *cat, const QString &url); - bool entryExists(CategoryItem *cat, const QString &name, const QUrl &url=QUrl()) { return 0!=getStream(cat, name, url); } - StreamItem * getStream(CategoryItem *cat, const QString &name, const QUrl &url); - -private Q_SLOTS: - void persist(); + Item * toItem(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : root; } + QList parseRadioTimeResponse(QIODevice *dev, CategoryItem *cat); + QList parseIceCastResponse(QIODevice *dev, CategoryItem *cat); + QList parseSomaFmResponse(QIODevice *dev, CategoryItem *cat); + QList parseDigitallyImportedResponse(QIODevice *dev, CategoryItem *cat, const QString &origUrl); + Item * parseRadioTimeEntry(QXmlStreamReader &doc, CategoryItem *parent); + Item * parseIceCastEntry(QXmlStreamReader &doc, CategoryItem *parent); + Item * parseSomaFmEntry(QXmlStreamReader &doc, CategoryItem *parent); + void loadFavourites(const QModelIndex &index); + bool loadXml(const QString &fileName, const QModelIndex &index); + QList loadXml(QIODevice *dev, bool isInternal); + bool saveXml(QIODevice *dev, const QList &items, bool format) const; private: - QList items; - mutable QMap iconMap; - bool writable; - bool modified; - QTimer *timer; - QNetworkReply *job; + QMap jobs; + CategoryItem *root; + CategoryItem *favourites; + bool favouritesIsWriteable; + bool favouritesModified; + QTimer *favouritesSaveTimer; }; #endif diff --git a/models/streamsproxymodel.cpp b/models/streamsproxymodel.cpp index ce31968f0..d72f39157 100644 --- a/models/streamsproxymodel.cpp +++ b/models/streamsproxymodel.cpp @@ -33,6 +33,26 @@ StreamsProxyModel::StreamsProxyModel(QObject *parent) sort(0); } +bool StreamsProxyModel::filterAcceptsItem(const void *i, QStringList strings) const +{ + const StreamsModel::Item *item=static_cast(i); + strings << item->name; + if (matchesFilter(strings)) { + return true; + } + + if (item->isCategory()) { + const StreamsModel::CategoryItem *cat=static_cast(item); + foreach (const StreamsModel::Item *c, cat->children) { + if (filterAcceptsItem(c, strings)) { + return true; + } + } + } + + return false; +} + bool StreamsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!filterEnabled) { @@ -41,33 +61,58 @@ bool StreamsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourc if (!isChildOfRoot(sourceParent)) { return true; } + const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); StreamsModel::Item *item = static_cast(index.internalPointer()); + QModelIndex idx=index.parent(); + QStringList strings; + + // Traverse back up tree, so we get parent strings... + while (idx.isValid()) { + StreamsModel::Item *i = static_cast(idx.internalPointer()); + if (!i->isCategory()) { + break; + } + strings << i->name; + idx=idx.parent(); + } if (item->isCategory()) { - StreamsModel::CategoryItem *cat = static_cast(item); - - if (!filterGenre.isEmpty() && !cat->genres.contains(filterGenre)) { - return false; - } - - if (cat->name.contains(filterRegExp())) { + // Check *all* children... + if (filterAcceptsItem(item, strings)) { return true; } - - foreach (StreamsModel::StreamItem *s, cat->streams) { - if (matchesFilter(QStringList() << cat->name << s->name)) { - return true; - } - } } else { - StreamsModel::StreamItem *s = static_cast(item); - - if (!filterGenre.isEmpty() && !s->genres.contains(filterGenre)) { - return false; - } - return matchesFilter(QStringList() << s->name); + strings << item->name; + return matchesFilter(strings); } return false; } + +bool StreamsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const StreamsModel::Item * leftItem = static_cast(left.internalPointer()); + const StreamsModel::Item * rightItem = static_cast(right.internalPointer()); + + if (leftItem->isCategory() && !rightItem->isCategory()) { + return true; + } + if (!leftItem->isCategory() && rightItem->isCategory()) { + return false; + } + + if (leftItem->isCategory() && rightItem->isCategory()) { + const StreamsModel::CategoryItem * leftCat = static_cast(leftItem); + const StreamsModel::CategoryItem * rightCat = static_cast(rightItem); + + if (leftCat->isFavourites && !rightCat->isFavourites) { + return true; + } + if (!leftCat->isFavourites && rightCat->isFavourites) { + return false; + } + } + + return QSortFilterProxyModel::lessThan(left, right); +} diff --git a/models/streamsproxymodel.h b/models/streamsproxymodel.h index c01a9a7fa..20f444196 100644 --- a/models/streamsproxymodel.h +++ b/models/streamsproxymodel.h @@ -26,12 +26,15 @@ #include "proxymodel.h" + class StreamsProxyModel : public ProxyModel { public: StreamsProxyModel(QObject *parent = 0); - void setFilterGenre(const QString &genre); + + bool filterAcceptsItem(const void *i, QStringList strings) const; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; #endif diff --git a/streams/icons/CMakeLists.txt b/streams/icons/CMakeLists.txt deleted file mode 100644 index 09d58db83..000000000 --- a/streams/icons/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -file(GLOB svg_files *.svg) -file(GLOB png_files *.png) -if (WIN32) - install(FILES ${svg_files} ${png_files} DESTINATION ${CMAKE_INSTALL_PREFIX}/streamicons/) -else (WIN32) - install(FILES ${svg_files} ${png_files} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/${CMAKE_PROJECT_NAME}/streamicons/) -endif (WIN32) diff --git a/streams/icons/bassclef.svg b/streams/icons/bassclef.svg deleted file mode 100644 index 6a0159772..000000000 --- a/streams/icons/bassclef.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/streams/icons/dice.svg b/streams/icons/dice.svg deleted file mode 100644 index 834a81477..000000000 --- a/streams/icons/dice.svg +++ /dev/null @@ -1,6 +0,0 @@ - -Six Sided Dice - - - - diff --git a/streams/icons/flag_ad.png b/streams/icons/flag_ad.png deleted file mode 100644 index 25c62eac2..000000000 Binary files a/streams/icons/flag_ad.png and /dev/null differ diff --git a/streams/icons/flag_ae.png b/streams/icons/flag_ae.png deleted file mode 100644 index 44b8f80d3..000000000 Binary files a/streams/icons/flag_ae.png and /dev/null differ diff --git a/streams/icons/flag_af.png b/streams/icons/flag_af.png deleted file mode 100644 index 6a7b97f5b..000000000 Binary files a/streams/icons/flag_af.png and /dev/null differ diff --git a/streams/icons/flag_ag.png b/streams/icons/flag_ag.png deleted file mode 100644 index 66bade753..000000000 Binary files a/streams/icons/flag_ag.png and /dev/null differ diff --git a/streams/icons/flag_ai.png b/streams/icons/flag_ai.png deleted file mode 100644 index da108facf..000000000 Binary files a/streams/icons/flag_ai.png and /dev/null differ diff --git a/streams/icons/flag_al.png b/streams/icons/flag_al.png deleted file mode 100644 index ab55d7d3f..000000000 Binary files a/streams/icons/flag_al.png and /dev/null differ diff --git a/streams/icons/flag_am.png b/streams/icons/flag_am.png deleted file mode 100644 index 41f1f17a8..000000000 Binary files a/streams/icons/flag_am.png and /dev/null differ diff --git a/streams/icons/flag_ao.png b/streams/icons/flag_ao.png deleted file mode 100644 index c2d5ec36c..000000000 Binary files a/streams/icons/flag_ao.png and /dev/null differ diff --git a/streams/icons/flag_aq.png b/streams/icons/flag_aq.png deleted file mode 100644 index e8ccd143a..000000000 Binary files a/streams/icons/flag_aq.png and /dev/null differ diff --git a/streams/icons/flag_ar.png b/streams/icons/flag_ar.png deleted file mode 100644 index aff9b892e..000000000 Binary files a/streams/icons/flag_ar.png and /dev/null differ diff --git a/streams/icons/flag_as.png b/streams/icons/flag_as.png deleted file mode 100644 index 16dbf81e7..000000000 Binary files a/streams/icons/flag_as.png and /dev/null differ diff --git a/streams/icons/flag_at.png b/streams/icons/flag_at.png deleted file mode 100644 index 0933fd15e..000000000 Binary files a/streams/icons/flag_at.png and /dev/null differ diff --git a/streams/icons/flag_au.png b/streams/icons/flag_au.png deleted file mode 100644 index e702fab6e..000000000 Binary files a/streams/icons/flag_au.png and /dev/null differ diff --git a/streams/icons/flag_aw.png b/streams/icons/flag_aw.png deleted file mode 100644 index 307f0d0ec..000000000 Binary files a/streams/icons/flag_aw.png and /dev/null differ diff --git a/streams/icons/flag_ax.png b/streams/icons/flag_ax.png deleted file mode 100644 index 8851c8751..000000000 Binary files a/streams/icons/flag_ax.png and /dev/null differ diff --git a/streams/icons/flag_az.png b/streams/icons/flag_az.png deleted file mode 100644 index ee3a35aeb..000000000 Binary files a/streams/icons/flag_az.png and /dev/null differ diff --git a/streams/icons/flag_ba.png b/streams/icons/flag_ba.png deleted file mode 100644 index 7e16be5e2..000000000 Binary files a/streams/icons/flag_ba.png and /dev/null differ diff --git a/streams/icons/flag_bb.png b/streams/icons/flag_bb.png deleted file mode 100644 index 6d668f3e8..000000000 Binary files a/streams/icons/flag_bb.png and /dev/null differ diff --git a/streams/icons/flag_bd.png b/streams/icons/flag_bd.png deleted file mode 100644 index 5eb4e9de4..000000000 Binary files a/streams/icons/flag_bd.png and /dev/null differ diff --git a/streams/icons/flag_be.png b/streams/icons/flag_be.png deleted file mode 100644 index d64e81d6b..000000000 Binary files a/streams/icons/flag_be.png and /dev/null differ diff --git a/streams/icons/flag_bf.png b/streams/icons/flag_bf.png deleted file mode 100644 index 3f6870456..000000000 Binary files a/streams/icons/flag_bf.png and /dev/null differ diff --git a/streams/icons/flag_bg.png b/streams/icons/flag_bg.png deleted file mode 100644 index bca9dcfe9..000000000 Binary files a/streams/icons/flag_bg.png and /dev/null differ diff --git a/streams/icons/flag_bh.png b/streams/icons/flag_bh.png deleted file mode 100644 index 0e4cad5ff..000000000 Binary files a/streams/icons/flag_bh.png and /dev/null differ diff --git a/streams/icons/flag_bi.png b/streams/icons/flag_bi.png deleted file mode 100644 index 3a5cea930..000000000 Binary files a/streams/icons/flag_bi.png and /dev/null differ diff --git a/streams/icons/flag_bj.png b/streams/icons/flag_bj.png deleted file mode 100644 index b90774590..000000000 Binary files a/streams/icons/flag_bj.png and /dev/null differ diff --git a/streams/icons/flag_bl.png b/streams/icons/flag_bl.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_bl.png and /dev/null differ diff --git a/streams/icons/flag_bm.png b/streams/icons/flag_bm.png deleted file mode 100644 index 9eebcef88..000000000 Binary files a/streams/icons/flag_bm.png and /dev/null differ diff --git a/streams/icons/flag_bn.png b/streams/icons/flag_bn.png deleted file mode 100644 index dfbf15da4..000000000 Binary files a/streams/icons/flag_bn.png and /dev/null differ diff --git a/streams/icons/flag_bo.png b/streams/icons/flag_bo.png deleted file mode 100644 index ba4b4d625..000000000 Binary files a/streams/icons/flag_bo.png and /dev/null differ diff --git a/streams/icons/flag_bq.png b/streams/icons/flag_bq.png deleted file mode 100644 index f5659f71e..000000000 Binary files a/streams/icons/flag_bq.png and /dev/null differ diff --git a/streams/icons/flag_br.png b/streams/icons/flag_br.png deleted file mode 100644 index f26d1f80d..000000000 Binary files a/streams/icons/flag_br.png and /dev/null differ diff --git a/streams/icons/flag_bs.png b/streams/icons/flag_bs.png deleted file mode 100644 index 98597c13e..000000000 Binary files a/streams/icons/flag_bs.png and /dev/null differ diff --git a/streams/icons/flag_bt.png b/streams/icons/flag_bt.png deleted file mode 100644 index b5190678d..000000000 Binary files a/streams/icons/flag_bt.png and /dev/null differ diff --git a/streams/icons/flag_bv.png b/streams/icons/flag_bv.png deleted file mode 100644 index fe3781cf1..000000000 Binary files a/streams/icons/flag_bv.png and /dev/null differ diff --git a/streams/icons/flag_bw.png b/streams/icons/flag_bw.png deleted file mode 100644 index 2da8ed283..000000000 Binary files a/streams/icons/flag_bw.png and /dev/null differ diff --git a/streams/icons/flag_by.png b/streams/icons/flag_by.png deleted file mode 100644 index 13b71e528..000000000 Binary files a/streams/icons/flag_by.png and /dev/null differ diff --git a/streams/icons/flag_bz.png b/streams/icons/flag_bz.png deleted file mode 100644 index 62d188ae6..000000000 Binary files a/streams/icons/flag_bz.png and /dev/null differ diff --git a/streams/icons/flag_ca.png b/streams/icons/flag_ca.png deleted file mode 100644 index c6ed0602e..000000000 Binary files a/streams/icons/flag_ca.png and /dev/null differ diff --git a/streams/icons/flag_cc.png b/streams/icons/flag_cc.png deleted file mode 100644 index 004241527..000000000 Binary files a/streams/icons/flag_cc.png and /dev/null differ diff --git a/streams/icons/flag_cd.png b/streams/icons/flag_cd.png deleted file mode 100644 index 53518621e..000000000 Binary files a/streams/icons/flag_cd.png and /dev/null differ diff --git a/streams/icons/flag_cf.png b/streams/icons/flag_cf.png deleted file mode 100644 index af93d9435..000000000 Binary files a/streams/icons/flag_cf.png and /dev/null differ diff --git a/streams/icons/flag_cg.png b/streams/icons/flag_cg.png deleted file mode 100644 index 5bd932ae5..000000000 Binary files a/streams/icons/flag_cg.png and /dev/null differ diff --git a/streams/icons/flag_ch.png b/streams/icons/flag_ch.png deleted file mode 100644 index 4fdcc88e5..000000000 Binary files a/streams/icons/flag_ch.png and /dev/null differ diff --git a/streams/icons/flag_ci.png b/streams/icons/flag_ci.png deleted file mode 100644 index e20a81f70..000000000 Binary files a/streams/icons/flag_ci.png and /dev/null differ diff --git a/streams/icons/flag_ck.png b/streams/icons/flag_ck.png deleted file mode 100644 index aee7b0483..000000000 Binary files a/streams/icons/flag_ck.png and /dev/null differ diff --git a/streams/icons/flag_cl.png b/streams/icons/flag_cl.png deleted file mode 100644 index 29263c812..000000000 Binary files a/streams/icons/flag_cl.png and /dev/null differ diff --git a/streams/icons/flag_cm.png b/streams/icons/flag_cm.png deleted file mode 100644 index 807e33abf..000000000 Binary files a/streams/icons/flag_cm.png and /dev/null differ diff --git a/streams/icons/flag_cn.png b/streams/icons/flag_cn.png deleted file mode 100644 index 20aeba0f0..000000000 Binary files a/streams/icons/flag_cn.png and /dev/null differ diff --git a/streams/icons/flag_co.png b/streams/icons/flag_co.png deleted file mode 100644 index b72031dbe..000000000 Binary files a/streams/icons/flag_co.png and /dev/null differ diff --git a/streams/icons/flag_cr.png b/streams/icons/flag_cr.png deleted file mode 100644 index 27a88bb9f..000000000 Binary files a/streams/icons/flag_cr.png and /dev/null differ diff --git a/streams/icons/flag_cu.png b/streams/icons/flag_cu.png deleted file mode 100644 index ecdc47c50..000000000 Binary files a/streams/icons/flag_cu.png and /dev/null differ diff --git a/streams/icons/flag_cv.png b/streams/icons/flag_cv.png deleted file mode 100644 index 76ff6429c..000000000 Binary files a/streams/icons/flag_cv.png and /dev/null differ diff --git a/streams/icons/flag_cw.png b/streams/icons/flag_cw.png deleted file mode 100644 index d1fc9f9dd..000000000 Binary files a/streams/icons/flag_cw.png and /dev/null differ diff --git a/streams/icons/flag_cx.png b/streams/icons/flag_cx.png deleted file mode 100644 index c0d86dd5e..000000000 Binary files a/streams/icons/flag_cx.png and /dev/null differ diff --git a/streams/icons/flag_cy.png b/streams/icons/flag_cy.png deleted file mode 100644 index 6c795008a..000000000 Binary files a/streams/icons/flag_cy.png and /dev/null differ diff --git a/streams/icons/flag_cz.png b/streams/icons/flag_cz.png deleted file mode 100644 index 0c8fd9389..000000000 Binary files a/streams/icons/flag_cz.png and /dev/null differ diff --git a/streams/icons/flag_de.png b/streams/icons/flag_de.png deleted file mode 100644 index 4ff8e0581..000000000 Binary files a/streams/icons/flag_de.png and /dev/null differ diff --git a/streams/icons/flag_dj.png b/streams/icons/flag_dj.png deleted file mode 100644 index 12582c0c8..000000000 Binary files a/streams/icons/flag_dj.png and /dev/null differ diff --git a/streams/icons/flag_dk.png b/streams/icons/flag_dk.png deleted file mode 100644 index cd258831a..000000000 Binary files a/streams/icons/flag_dk.png and /dev/null differ diff --git a/streams/icons/flag_dm.png b/streams/icons/flag_dm.png deleted file mode 100644 index 4d69c4239..000000000 Binary files a/streams/icons/flag_dm.png and /dev/null differ diff --git a/streams/icons/flag_do.png b/streams/icons/flag_do.png deleted file mode 100644 index 5e752f785..000000000 Binary files a/streams/icons/flag_do.png and /dev/null differ diff --git a/streams/icons/flag_dz.png b/streams/icons/flag_dz.png deleted file mode 100644 index 05931cce8..000000000 Binary files a/streams/icons/flag_dz.png and /dev/null differ diff --git a/streams/icons/flag_ec.png b/streams/icons/flag_ec.png deleted file mode 100644 index 1bd8480d0..000000000 Binary files a/streams/icons/flag_ec.png and /dev/null differ diff --git a/streams/icons/flag_ee.png b/streams/icons/flag_ee.png deleted file mode 100644 index 77cf97ce2..000000000 Binary files a/streams/icons/flag_ee.png and /dev/null differ diff --git a/streams/icons/flag_eg.png b/streams/icons/flag_eg.png deleted file mode 100644 index 3f5076096..000000000 Binary files a/streams/icons/flag_eg.png and /dev/null differ diff --git a/streams/icons/flag_eh.png b/streams/icons/flag_eh.png deleted file mode 100644 index ff25a7d50..000000000 Binary files a/streams/icons/flag_eh.png and /dev/null differ diff --git a/streams/icons/flag_eo.png b/streams/icons/flag_eo.png deleted file mode 100644 index 23f0ed909..000000000 Binary files a/streams/icons/flag_eo.png and /dev/null differ diff --git a/streams/icons/flag_er.png b/streams/icons/flag_er.png deleted file mode 100644 index 005eb6f00..000000000 Binary files a/streams/icons/flag_er.png and /dev/null differ diff --git a/streams/icons/flag_es.png b/streams/icons/flag_es.png deleted file mode 100644 index 4373c6b5c..000000000 Binary files a/streams/icons/flag_es.png and /dev/null differ diff --git a/streams/icons/flag_et.png b/streams/icons/flag_et.png deleted file mode 100644 index 839366f03..000000000 Binary files a/streams/icons/flag_et.png and /dev/null differ diff --git a/streams/icons/flag_fi.png b/streams/icons/flag_fi.png deleted file mode 100644 index e4140c40f..000000000 Binary files a/streams/icons/flag_fi.png and /dev/null differ diff --git a/streams/icons/flag_fj.png b/streams/icons/flag_fj.png deleted file mode 100644 index 4d1deef9d..000000000 Binary files a/streams/icons/flag_fj.png and /dev/null differ diff --git a/streams/icons/flag_fk.png b/streams/icons/flag_fk.png deleted file mode 100644 index 4cd225674..000000000 Binary files a/streams/icons/flag_fk.png and /dev/null differ diff --git a/streams/icons/flag_fm.png b/streams/icons/flag_fm.png deleted file mode 100644 index 458bf1503..000000000 Binary files a/streams/icons/flag_fm.png and /dev/null differ diff --git a/streams/icons/flag_fo.png b/streams/icons/flag_fo.png deleted file mode 100644 index 87a474894..000000000 Binary files a/streams/icons/flag_fo.png and /dev/null differ diff --git a/streams/icons/flag_fr.png b/streams/icons/flag_fr.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_fr.png and /dev/null differ diff --git a/streams/icons/flag_ga.png b/streams/icons/flag_ga.png deleted file mode 100644 index cefa4653f..000000000 Binary files a/streams/icons/flag_ga.png and /dev/null differ diff --git a/streams/icons/flag_gb.png b/streams/icons/flag_gb.png deleted file mode 100644 index 6fec2bc7f..000000000 Binary files a/streams/icons/flag_gb.png and /dev/null differ diff --git a/streams/icons/flag_gd.png b/streams/icons/flag_gd.png deleted file mode 100644 index 19a897b89..000000000 Binary files a/streams/icons/flag_gd.png and /dev/null differ diff --git a/streams/icons/flag_ge.png b/streams/icons/flag_ge.png deleted file mode 100644 index 00068c02f..000000000 Binary files a/streams/icons/flag_ge.png and /dev/null differ diff --git a/streams/icons/flag_gf.png b/streams/icons/flag_gf.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_gf.png and /dev/null differ diff --git a/streams/icons/flag_gg.png b/streams/icons/flag_gg.png deleted file mode 100644 index 6be6e154b..000000000 Binary files a/streams/icons/flag_gg.png and /dev/null differ diff --git a/streams/icons/flag_gh.png b/streams/icons/flag_gh.png deleted file mode 100644 index 4cccea545..000000000 Binary files a/streams/icons/flag_gh.png and /dev/null differ diff --git a/streams/icons/flag_gi.png b/streams/icons/flag_gi.png deleted file mode 100644 index 0e65e3a61..000000000 Binary files a/streams/icons/flag_gi.png and /dev/null differ diff --git a/streams/icons/flag_gl.png b/streams/icons/flag_gl.png deleted file mode 100644 index 9d716ad2f..000000000 Binary files a/streams/icons/flag_gl.png and /dev/null differ diff --git a/streams/icons/flag_gm.png b/streams/icons/flag_gm.png deleted file mode 100644 index a3126c486..000000000 Binary files a/streams/icons/flag_gm.png and /dev/null differ diff --git a/streams/icons/flag_gn.png b/streams/icons/flag_gn.png deleted file mode 100644 index c7251de4f..000000000 Binary files a/streams/icons/flag_gn.png and /dev/null differ diff --git a/streams/icons/flag_gp.png b/streams/icons/flag_gp.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_gp.png and /dev/null differ diff --git a/streams/icons/flag_gq.png b/streams/icons/flag_gq.png deleted file mode 100644 index 5bd3169ef..000000000 Binary files a/streams/icons/flag_gq.png and /dev/null differ diff --git a/streams/icons/flag_gr.png b/streams/icons/flag_gr.png deleted file mode 100644 index 4b4cdd90b..000000000 Binary files a/streams/icons/flag_gr.png and /dev/null differ diff --git a/streams/icons/flag_gs.png b/streams/icons/flag_gs.png deleted file mode 100644 index 0c4c1dca8..000000000 Binary files a/streams/icons/flag_gs.png and /dev/null differ diff --git a/streams/icons/flag_gt.png b/streams/icons/flag_gt.png deleted file mode 100644 index ec5343d97..000000000 Binary files a/streams/icons/flag_gt.png and /dev/null differ diff --git a/streams/icons/flag_gu.png b/streams/icons/flag_gu.png deleted file mode 100644 index 3e54b4ed5..000000000 Binary files a/streams/icons/flag_gu.png and /dev/null differ diff --git a/streams/icons/flag_gw.png b/streams/icons/flag_gw.png deleted file mode 100644 index 7e3285f88..000000000 Binary files a/streams/icons/flag_gw.png and /dev/null differ diff --git a/streams/icons/flag_gy.png b/streams/icons/flag_gy.png deleted file mode 100644 index d0b9e5211..000000000 Binary files a/streams/icons/flag_gy.png and /dev/null differ diff --git a/streams/icons/flag_hk.png b/streams/icons/flag_hk.png deleted file mode 100644 index c79197118..000000000 Binary files a/streams/icons/flag_hk.png and /dev/null differ diff --git a/streams/icons/flag_hm.png b/streams/icons/flag_hm.png deleted file mode 100644 index 6087307e9..000000000 Binary files a/streams/icons/flag_hm.png and /dev/null differ diff --git a/streams/icons/flag_hn.png b/streams/icons/flag_hn.png deleted file mode 100644 index 23e5dac2a..000000000 Binary files a/streams/icons/flag_hn.png and /dev/null differ diff --git a/streams/icons/flag_hr.png b/streams/icons/flag_hr.png deleted file mode 100644 index c7a6454b6..000000000 Binary files a/streams/icons/flag_hr.png and /dev/null differ diff --git a/streams/icons/flag_ht.png b/streams/icons/flag_ht.png deleted file mode 100644 index b3dd4fd46..000000000 Binary files a/streams/icons/flag_ht.png and /dev/null differ diff --git a/streams/icons/flag_hu.png b/streams/icons/flag_hu.png deleted file mode 100644 index 019b52f2f..000000000 Binary files a/streams/icons/flag_hu.png and /dev/null differ diff --git a/streams/icons/flag_id.png b/streams/icons/flag_id.png deleted file mode 100644 index 5c0f56dad..000000000 Binary files a/streams/icons/flag_id.png and /dev/null differ diff --git a/streams/icons/flag_ie.png b/streams/icons/flag_ie.png deleted file mode 100644 index 44b2eaf01..000000000 Binary files a/streams/icons/flag_ie.png and /dev/null differ diff --git a/streams/icons/flag_il.png b/streams/icons/flag_il.png deleted file mode 100644 index 7b060d22a..000000000 Binary files a/streams/icons/flag_il.png and /dev/null differ diff --git a/streams/icons/flag_im.png b/streams/icons/flag_im.png deleted file mode 100644 index e62edb8b4..000000000 Binary files a/streams/icons/flag_im.png and /dev/null differ diff --git a/streams/icons/flag_in.png b/streams/icons/flag_in.png deleted file mode 100644 index 089037af0..000000000 Binary files a/streams/icons/flag_in.png and /dev/null differ diff --git a/streams/icons/flag_io.png b/streams/icons/flag_io.png deleted file mode 100644 index 7d2cef90c..000000000 Binary files a/streams/icons/flag_io.png and /dev/null differ diff --git a/streams/icons/flag_iq.png b/streams/icons/flag_iq.png deleted file mode 100644 index 9946fa402..000000000 Binary files a/streams/icons/flag_iq.png and /dev/null differ diff --git a/streams/icons/flag_ir.png b/streams/icons/flag_ir.png deleted file mode 100644 index 3daf24186..000000000 Binary files a/streams/icons/flag_ir.png and /dev/null differ diff --git a/streams/icons/flag_is.png b/streams/icons/flag_is.png deleted file mode 100644 index 777f30bb9..000000000 Binary files a/streams/icons/flag_is.png and /dev/null differ diff --git a/streams/icons/flag_it.png b/streams/icons/flag_it.png deleted file mode 100644 index cdf4370d4..000000000 Binary files a/streams/icons/flag_it.png and /dev/null differ diff --git a/streams/icons/flag_je.png b/streams/icons/flag_je.png deleted file mode 100644 index 9eb942385..000000000 Binary files a/streams/icons/flag_je.png and /dev/null differ diff --git a/streams/icons/flag_jm.png b/streams/icons/flag_jm.png deleted file mode 100644 index 36405b25b..000000000 Binary files a/streams/icons/flag_jm.png and /dev/null differ diff --git a/streams/icons/flag_jo.png b/streams/icons/flag_jo.png deleted file mode 100644 index f25028991..000000000 Binary files a/streams/icons/flag_jo.png and /dev/null differ diff --git a/streams/icons/flag_jp.png b/streams/icons/flag_jp.png deleted file mode 100644 index 6cb0c3269..000000000 Binary files a/streams/icons/flag_jp.png and /dev/null differ diff --git a/streams/icons/flag_ke.png b/streams/icons/flag_ke.png deleted file mode 100644 index 07c187ed6..000000000 Binary files a/streams/icons/flag_ke.png and /dev/null differ diff --git a/streams/icons/flag_kg.png b/streams/icons/flag_kg.png deleted file mode 100644 index d9143d073..000000000 Binary files a/streams/icons/flag_kg.png and /dev/null differ diff --git a/streams/icons/flag_kh.png b/streams/icons/flag_kh.png deleted file mode 100644 index b338dfcc8..000000000 Binary files a/streams/icons/flag_kh.png and /dev/null differ diff --git a/streams/icons/flag_ki.png b/streams/icons/flag_ki.png deleted file mode 100644 index c9a47fe9f..000000000 Binary files a/streams/icons/flag_ki.png and /dev/null differ diff --git a/streams/icons/flag_km.png b/streams/icons/flag_km.png deleted file mode 100644 index f8318ca40..000000000 Binary files a/streams/icons/flag_km.png and /dev/null differ diff --git a/streams/icons/flag_kn.png b/streams/icons/flag_kn.png deleted file mode 100644 index 224658cbb..000000000 Binary files a/streams/icons/flag_kn.png and /dev/null differ diff --git a/streams/icons/flag_kp.png b/streams/icons/flag_kp.png deleted file mode 100644 index 080561898..000000000 Binary files a/streams/icons/flag_kp.png and /dev/null differ diff --git a/streams/icons/flag_kr.png b/streams/icons/flag_kr.png deleted file mode 100644 index 44ca8c2f8..000000000 Binary files a/streams/icons/flag_kr.png and /dev/null differ diff --git a/streams/icons/flag_kw.png b/streams/icons/flag_kw.png deleted file mode 100644 index 764fab1d2..000000000 Binary files a/streams/icons/flag_kw.png and /dev/null differ diff --git a/streams/icons/flag_ky.png b/streams/icons/flag_ky.png deleted file mode 100644 index 8fd21f3ad..000000000 Binary files a/streams/icons/flag_ky.png and /dev/null differ diff --git a/streams/icons/flag_kz.png b/streams/icons/flag_kz.png deleted file mode 100644 index da85193be..000000000 Binary files a/streams/icons/flag_kz.png and /dev/null differ diff --git a/streams/icons/flag_la.png b/streams/icons/flag_la.png deleted file mode 100644 index 0294062b0..000000000 Binary files a/streams/icons/flag_la.png and /dev/null differ diff --git a/streams/icons/flag_lb.png b/streams/icons/flag_lb.png deleted file mode 100644 index d3bc98f14..000000000 Binary files a/streams/icons/flag_lb.png and /dev/null differ diff --git a/streams/icons/flag_lc.png b/streams/icons/flag_lc.png deleted file mode 100644 index 94e0429db..000000000 Binary files a/streams/icons/flag_lc.png and /dev/null differ diff --git a/streams/icons/flag_li.png b/streams/icons/flag_li.png deleted file mode 100644 index ea60eaeef..000000000 Binary files a/streams/icons/flag_li.png and /dev/null differ diff --git a/streams/icons/flag_lk.png b/streams/icons/flag_lk.png deleted file mode 100644 index d1cfae784..000000000 Binary files a/streams/icons/flag_lk.png and /dev/null differ diff --git a/streams/icons/flag_lr.png b/streams/icons/flag_lr.png deleted file mode 100644 index d1bb09933..000000000 Binary files a/streams/icons/flag_lr.png and /dev/null differ diff --git a/streams/icons/flag_ls.png b/streams/icons/flag_ls.png deleted file mode 100644 index 957d83f54..000000000 Binary files a/streams/icons/flag_ls.png and /dev/null differ diff --git a/streams/icons/flag_lt.png b/streams/icons/flag_lt.png deleted file mode 100644 index 6200bd77e..000000000 Binary files a/streams/icons/flag_lt.png and /dev/null differ diff --git a/streams/icons/flag_lu.png b/streams/icons/flag_lu.png deleted file mode 100644 index d0652614e..000000000 Binary files a/streams/icons/flag_lu.png and /dev/null differ diff --git a/streams/icons/flag_lv.png b/streams/icons/flag_lv.png deleted file mode 100644 index c883fead9..000000000 Binary files a/streams/icons/flag_lv.png and /dev/null differ diff --git a/streams/icons/flag_ly.png b/streams/icons/flag_ly.png deleted file mode 100644 index 2363ad530..000000000 Binary files a/streams/icons/flag_ly.png and /dev/null differ diff --git a/streams/icons/flag_ma.png b/streams/icons/flag_ma.png deleted file mode 100644 index 11fe7220f..000000000 Binary files a/streams/icons/flag_ma.png and /dev/null differ diff --git a/streams/icons/flag_mc.png b/streams/icons/flag_mc.png deleted file mode 100644 index 1df630981..000000000 Binary files a/streams/icons/flag_mc.png and /dev/null differ diff --git a/streams/icons/flag_md.png b/streams/icons/flag_md.png deleted file mode 100644 index 60cb71814..000000000 Binary files a/streams/icons/flag_md.png and /dev/null differ diff --git a/streams/icons/flag_me.png b/streams/icons/flag_me.png deleted file mode 100644 index 99882c7cf..000000000 Binary files a/streams/icons/flag_me.png and /dev/null differ diff --git a/streams/icons/flag_mf.png b/streams/icons/flag_mf.png deleted file mode 100644 index 21d6db6d9..000000000 Binary files a/streams/icons/flag_mf.png and /dev/null differ diff --git a/streams/icons/flag_mg.png b/streams/icons/flag_mg.png deleted file mode 100644 index 585428165..000000000 Binary files a/streams/icons/flag_mg.png and /dev/null differ diff --git a/streams/icons/flag_mh.png b/streams/icons/flag_mh.png deleted file mode 100644 index a4ca39383..000000000 Binary files a/streams/icons/flag_mh.png and /dev/null differ diff --git a/streams/icons/flag_mk.png b/streams/icons/flag_mk.png deleted file mode 100644 index ca41af9fa..000000000 Binary files a/streams/icons/flag_mk.png and /dev/null differ diff --git a/streams/icons/flag_ml.png b/streams/icons/flag_ml.png deleted file mode 100644 index 538fa94bc..000000000 Binary files a/streams/icons/flag_ml.png and /dev/null differ diff --git a/streams/icons/flag_mm.png b/streams/icons/flag_mm.png deleted file mode 100644 index cf553d5b2..000000000 Binary files a/streams/icons/flag_mm.png and /dev/null differ diff --git a/streams/icons/flag_mn.png b/streams/icons/flag_mn.png deleted file mode 100644 index 44d8bf7e5..000000000 Binary files a/streams/icons/flag_mn.png and /dev/null differ diff --git a/streams/icons/flag_mo.png b/streams/icons/flag_mo.png deleted file mode 100644 index dacb8ba9e..000000000 Binary files a/streams/icons/flag_mo.png and /dev/null differ diff --git a/streams/icons/flag_mp.png b/streams/icons/flag_mp.png deleted file mode 100644 index 98e49efea..000000000 Binary files a/streams/icons/flag_mp.png and /dev/null differ diff --git a/streams/icons/flag_mq.png b/streams/icons/flag_mq.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_mq.png and /dev/null differ diff --git a/streams/icons/flag_mr.png b/streams/icons/flag_mr.png deleted file mode 100644 index 81e731681..000000000 Binary files a/streams/icons/flag_mr.png and /dev/null differ diff --git a/streams/icons/flag_ms.png b/streams/icons/flag_ms.png deleted file mode 100644 index 0a1163718..000000000 Binary files a/streams/icons/flag_ms.png and /dev/null differ diff --git a/streams/icons/flag_mt.png b/streams/icons/flag_mt.png deleted file mode 100644 index eb345a95b..000000000 Binary files a/streams/icons/flag_mt.png and /dev/null differ diff --git a/streams/icons/flag_mu.png b/streams/icons/flag_mu.png deleted file mode 100644 index 82eb0b418..000000000 Binary files a/streams/icons/flag_mu.png and /dev/null differ diff --git a/streams/icons/flag_mv.png b/streams/icons/flag_mv.png deleted file mode 100644 index 527b2c7d8..000000000 Binary files a/streams/icons/flag_mv.png and /dev/null differ diff --git a/streams/icons/flag_mw.png b/streams/icons/flag_mw.png deleted file mode 100644 index 224150021..000000000 Binary files a/streams/icons/flag_mw.png and /dev/null differ diff --git a/streams/icons/flag_mx.png b/streams/icons/flag_mx.png deleted file mode 100644 index 24f71cf7c..000000000 Binary files a/streams/icons/flag_mx.png and /dev/null differ diff --git a/streams/icons/flag_my.png b/streams/icons/flag_my.png deleted file mode 100644 index 3c3a34cf7..000000000 Binary files a/streams/icons/flag_my.png and /dev/null differ diff --git a/streams/icons/flag_mz.png b/streams/icons/flag_mz.png deleted file mode 100644 index 1259ab48b..000000000 Binary files a/streams/icons/flag_mz.png and /dev/null differ diff --git a/streams/icons/flag_na.png b/streams/icons/flag_na.png deleted file mode 100644 index d77810032..000000000 Binary files a/streams/icons/flag_na.png and /dev/null differ diff --git a/streams/icons/flag_nc.png b/streams/icons/flag_nc.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_nc.png and /dev/null differ diff --git a/streams/icons/flag_ne.png b/streams/icons/flag_ne.png deleted file mode 100644 index 27e035f4b..000000000 Binary files a/streams/icons/flag_ne.png and /dev/null differ diff --git a/streams/icons/flag_nf.png b/streams/icons/flag_nf.png deleted file mode 100644 index d048c00ca..000000000 Binary files a/streams/icons/flag_nf.png and /dev/null differ diff --git a/streams/icons/flag_ng.png b/streams/icons/flag_ng.png deleted file mode 100644 index 05d1a0890..000000000 Binary files a/streams/icons/flag_ng.png and /dev/null differ diff --git a/streams/icons/flag_ni.png b/streams/icons/flag_ni.png deleted file mode 100644 index 087ced093..000000000 Binary files a/streams/icons/flag_ni.png and /dev/null differ diff --git a/streams/icons/flag_nl.png b/streams/icons/flag_nl.png deleted file mode 100644 index 3187c363c..000000000 Binary files a/streams/icons/flag_nl.png and /dev/null differ diff --git a/streams/icons/flag_no.png b/streams/icons/flag_no.png deleted file mode 100644 index c0979a67f..000000000 Binary files a/streams/icons/flag_no.png and /dev/null differ diff --git a/streams/icons/flag_np.png b/streams/icons/flag_np.png deleted file mode 100644 index 2e4560ab5..000000000 Binary files a/streams/icons/flag_np.png and /dev/null differ diff --git a/streams/icons/flag_nr.png b/streams/icons/flag_nr.png deleted file mode 100644 index 57757878b..000000000 Binary files a/streams/icons/flag_nr.png and /dev/null differ diff --git a/streams/icons/flag_nu.png b/streams/icons/flag_nu.png deleted file mode 100644 index b123fd86c..000000000 Binary files a/streams/icons/flag_nu.png and /dev/null differ diff --git a/streams/icons/flag_nz.png b/streams/icons/flag_nz.png deleted file mode 100644 index 3d8fe6f6e..000000000 Binary files a/streams/icons/flag_nz.png and /dev/null differ diff --git a/streams/icons/flag_om.png b/streams/icons/flag_om.png deleted file mode 100644 index e58d7ddb8..000000000 Binary files a/streams/icons/flag_om.png and /dev/null differ diff --git a/streams/icons/flag_pa.png b/streams/icons/flag_pa.png deleted file mode 100644 index 83da09b3f..000000000 Binary files a/streams/icons/flag_pa.png and /dev/null differ diff --git a/streams/icons/flag_pe.png b/streams/icons/flag_pe.png deleted file mode 100644 index 0c8dcb034..000000000 Binary files a/streams/icons/flag_pe.png and /dev/null differ diff --git a/streams/icons/flag_pf.png b/streams/icons/flag_pf.png deleted file mode 100644 index eb7102ea5..000000000 Binary files a/streams/icons/flag_pf.png and /dev/null differ diff --git a/streams/icons/flag_pg.png b/streams/icons/flag_pg.png deleted file mode 100644 index 2bb84ec87..000000000 Binary files a/streams/icons/flag_pg.png and /dev/null differ diff --git a/streams/icons/flag_ph.png b/streams/icons/flag_ph.png deleted file mode 100644 index 4aff86ee6..000000000 Binary files a/streams/icons/flag_ph.png and /dev/null differ diff --git a/streams/icons/flag_pk.png b/streams/icons/flag_pk.png deleted file mode 100644 index 5a2389108..000000000 Binary files a/streams/icons/flag_pk.png and /dev/null differ diff --git a/streams/icons/flag_pl.png b/streams/icons/flag_pl.png deleted file mode 100644 index 5ea5b0408..000000000 Binary files a/streams/icons/flag_pl.png and /dev/null differ diff --git a/streams/icons/flag_pm.png b/streams/icons/flag_pm.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_pm.png and /dev/null differ diff --git a/streams/icons/flag_pn.png b/streams/icons/flag_pn.png deleted file mode 100644 index b4b46f360..000000000 Binary files a/streams/icons/flag_pn.png and /dev/null differ diff --git a/streams/icons/flag_pr.png b/streams/icons/flag_pr.png deleted file mode 100644 index c6b64dfad..000000000 Binary files a/streams/icons/flag_pr.png and /dev/null differ diff --git a/streams/icons/flag_ps.png b/streams/icons/flag_ps.png deleted file mode 100644 index f49794067..000000000 Binary files a/streams/icons/flag_ps.png and /dev/null differ diff --git a/streams/icons/flag_pt.png b/streams/icons/flag_pt.png deleted file mode 100644 index ec4daa5be..000000000 Binary files a/streams/icons/flag_pt.png and /dev/null differ diff --git a/streams/icons/flag_pw.png b/streams/icons/flag_pw.png deleted file mode 100644 index 6aca784a2..000000000 Binary files a/streams/icons/flag_pw.png and /dev/null differ diff --git a/streams/icons/flag_py.png b/streams/icons/flag_py.png deleted file mode 100644 index be7f66110..000000000 Binary files a/streams/icons/flag_py.png and /dev/null differ diff --git a/streams/icons/flag_qa.png b/streams/icons/flag_qa.png deleted file mode 100644 index 8f0802ba5..000000000 Binary files a/streams/icons/flag_qa.png and /dev/null differ diff --git a/streams/icons/flag_re.png b/streams/icons/flag_re.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_re.png and /dev/null differ diff --git a/streams/icons/flag_ro.png b/streams/icons/flag_ro.png deleted file mode 100644 index 84d97a012..000000000 Binary files a/streams/icons/flag_ro.png and /dev/null differ diff --git a/streams/icons/flag_rs.png b/streams/icons/flag_rs.png deleted file mode 100644 index 32bbeece7..000000000 Binary files a/streams/icons/flag_rs.png and /dev/null differ diff --git a/streams/icons/flag_ru.png b/streams/icons/flag_ru.png deleted file mode 100644 index c1dfa582e..000000000 Binary files a/streams/icons/flag_ru.png and /dev/null differ diff --git a/streams/icons/flag_rw.png b/streams/icons/flag_rw.png deleted file mode 100644 index d5aea4670..000000000 Binary files a/streams/icons/flag_rw.png and /dev/null differ diff --git a/streams/icons/flag_sa.png b/streams/icons/flag_sa.png deleted file mode 100644 index da94f47db..000000000 Binary files a/streams/icons/flag_sa.png and /dev/null differ diff --git a/streams/icons/flag_sb.png b/streams/icons/flag_sb.png deleted file mode 100644 index 7f09f29d3..000000000 Binary files a/streams/icons/flag_sb.png and /dev/null differ diff --git a/streams/icons/flag_sc.png b/streams/icons/flag_sc.png deleted file mode 100644 index 8dc6e09fc..000000000 Binary files a/streams/icons/flag_sc.png and /dev/null differ diff --git a/streams/icons/flag_sd.png b/streams/icons/flag_sd.png deleted file mode 100644 index 1c65e20e6..000000000 Binary files a/streams/icons/flag_sd.png and /dev/null differ diff --git a/streams/icons/flag_se.png b/streams/icons/flag_se.png deleted file mode 100644 index e7aecab60..000000000 Binary files a/streams/icons/flag_se.png and /dev/null differ diff --git a/streams/icons/flag_sg.png b/streams/icons/flag_sg.png deleted file mode 100644 index f55d07051..000000000 Binary files a/streams/icons/flag_sg.png and /dev/null differ diff --git a/streams/icons/flag_sh.png b/streams/icons/flag_sh.png deleted file mode 100644 index 76b063a02..000000000 Binary files a/streams/icons/flag_sh.png and /dev/null differ diff --git a/streams/icons/flag_si.png b/streams/icons/flag_si.png deleted file mode 100644 index 2666782c5..000000000 Binary files a/streams/icons/flag_si.png and /dev/null differ diff --git a/streams/icons/flag_sj.png b/streams/icons/flag_sj.png deleted file mode 100644 index 0e315acdb..000000000 Binary files a/streams/icons/flag_sj.png and /dev/null differ diff --git a/streams/icons/flag_sk.png b/streams/icons/flag_sk.png deleted file mode 100644 index 689e27046..000000000 Binary files a/streams/icons/flag_sk.png and /dev/null differ diff --git a/streams/icons/flag_sl.png b/streams/icons/flag_sl.png deleted file mode 100644 index 3d85d5b0f..000000000 Binary files a/streams/icons/flag_sl.png and /dev/null differ diff --git a/streams/icons/flag_sm.png b/streams/icons/flag_sm.png deleted file mode 100644 index 5cc7f03d4..000000000 Binary files a/streams/icons/flag_sm.png and /dev/null differ diff --git a/streams/icons/flag_sn.png b/streams/icons/flag_sn.png deleted file mode 100644 index ca0b634fd..000000000 Binary files a/streams/icons/flag_sn.png and /dev/null differ diff --git a/streams/icons/flag_so.png b/streams/icons/flag_so.png deleted file mode 100644 index 5655d724f..000000000 Binary files a/streams/icons/flag_so.png and /dev/null differ diff --git a/streams/icons/flag_sr.png b/streams/icons/flag_sr.png deleted file mode 100644 index b843be835..000000000 Binary files a/streams/icons/flag_sr.png and /dev/null differ diff --git a/streams/icons/flag_ss.png b/streams/icons/flag_ss.png deleted file mode 100644 index dcda95c56..000000000 Binary files a/streams/icons/flag_ss.png and /dev/null differ diff --git a/streams/icons/flag_st.png b/streams/icons/flag_st.png deleted file mode 100644 index 7cfda1b09..000000000 Binary files a/streams/icons/flag_st.png and /dev/null differ diff --git a/streams/icons/flag_sv.png b/streams/icons/flag_sv.png deleted file mode 100644 index 78e56c5db..000000000 Binary files a/streams/icons/flag_sv.png and /dev/null differ diff --git a/streams/icons/flag_sx.png b/streams/icons/flag_sx.png deleted file mode 100644 index 93d71f30c..000000000 Binary files a/streams/icons/flag_sx.png and /dev/null differ diff --git a/streams/icons/flag_sy.png b/streams/icons/flag_sy.png deleted file mode 100644 index 5bd73581a..000000000 Binary files a/streams/icons/flag_sy.png and /dev/null differ diff --git a/streams/icons/flag_sz.png b/streams/icons/flag_sz.png deleted file mode 100644 index f98207502..000000000 Binary files a/streams/icons/flag_sz.png and /dev/null differ diff --git a/streams/icons/flag_tc.png b/streams/icons/flag_tc.png deleted file mode 100644 index 79e4e25f0..000000000 Binary files a/streams/icons/flag_tc.png and /dev/null differ diff --git a/streams/icons/flag_td.png b/streams/icons/flag_td.png deleted file mode 100644 index 65f850a0a..000000000 Binary files a/streams/icons/flag_td.png and /dev/null differ diff --git a/streams/icons/flag_tf.png b/streams/icons/flag_tf.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_tf.png and /dev/null differ diff --git a/streams/icons/flag_tg.png b/streams/icons/flag_tg.png deleted file mode 100644 index 7ff001cd8..000000000 Binary files a/streams/icons/flag_tg.png and /dev/null differ diff --git a/streams/icons/flag_th.png b/streams/icons/flag_th.png deleted file mode 100644 index 927564cec..000000000 Binary files a/streams/icons/flag_th.png and /dev/null differ diff --git a/streams/icons/flag_tj.png b/streams/icons/flag_tj.png deleted file mode 100644 index 38d2cd328..000000000 Binary files a/streams/icons/flag_tj.png and /dev/null differ diff --git a/streams/icons/flag_tk.png b/streams/icons/flag_tk.png deleted file mode 100644 index 06162ec57..000000000 Binary files a/streams/icons/flag_tk.png and /dev/null differ diff --git a/streams/icons/flag_tl.png b/streams/icons/flag_tl.png deleted file mode 100644 index edeac1cc9..000000000 Binary files a/streams/icons/flag_tl.png and /dev/null differ diff --git a/streams/icons/flag_tm.png b/streams/icons/flag_tm.png deleted file mode 100644 index b56059f2d..000000000 Binary files a/streams/icons/flag_tm.png and /dev/null differ diff --git a/streams/icons/flag_tn.png b/streams/icons/flag_tn.png deleted file mode 100644 index 0f904c805..000000000 Binary files a/streams/icons/flag_tn.png and /dev/null differ diff --git a/streams/icons/flag_to.png b/streams/icons/flag_to.png deleted file mode 100644 index c9ae81f03..000000000 Binary files a/streams/icons/flag_to.png and /dev/null differ diff --git a/streams/icons/flag_tr.png b/streams/icons/flag_tr.png deleted file mode 100644 index 40eecb18e..000000000 Binary files a/streams/icons/flag_tr.png and /dev/null differ diff --git a/streams/icons/flag_tt.png b/streams/icons/flag_tt.png deleted file mode 100644 index f2d09db4b..000000000 Binary files a/streams/icons/flag_tt.png and /dev/null differ diff --git a/streams/icons/flag_tv.png b/streams/icons/flag_tv.png deleted file mode 100644 index 4aa5c3fc9..000000000 Binary files a/streams/icons/flag_tv.png and /dev/null differ diff --git a/streams/icons/flag_tw.png b/streams/icons/flag_tw.png deleted file mode 100644 index 0ce1cb263..000000000 Binary files a/streams/icons/flag_tw.png and /dev/null differ diff --git a/streams/icons/flag_tz.png b/streams/icons/flag_tz.png deleted file mode 100644 index 6ee1b5ae2..000000000 Binary files a/streams/icons/flag_tz.png and /dev/null differ diff --git a/streams/icons/flag_ua.png b/streams/icons/flag_ua.png deleted file mode 100644 index 169259519..000000000 Binary files a/streams/icons/flag_ua.png and /dev/null differ diff --git a/streams/icons/flag_ug.png b/streams/icons/flag_ug.png deleted file mode 100644 index 3e0f89f95..000000000 Binary files a/streams/icons/flag_ug.png and /dev/null differ diff --git a/streams/icons/flag_us.png b/streams/icons/flag_us.png deleted file mode 100644 index 22391c3af..000000000 Binary files a/streams/icons/flag_us.png and /dev/null differ diff --git a/streams/icons/flag_uy.png b/streams/icons/flag_uy.png deleted file mode 100644 index 4d1a8fa0a..000000000 Binary files a/streams/icons/flag_uy.png and /dev/null differ diff --git a/streams/icons/flag_uz.png b/streams/icons/flag_uz.png deleted file mode 100644 index 7b76b01f7..000000000 Binary files a/streams/icons/flag_uz.png and /dev/null differ diff --git a/streams/icons/flag_va.png b/streams/icons/flag_va.png deleted file mode 100644 index cf50340cb..000000000 Binary files a/streams/icons/flag_va.png and /dev/null differ diff --git a/streams/icons/flag_vc.png b/streams/icons/flag_vc.png deleted file mode 100644 index 57f31f042..000000000 Binary files a/streams/icons/flag_vc.png and /dev/null differ diff --git a/streams/icons/flag_ve.png b/streams/icons/flag_ve.png deleted file mode 100644 index 21a653b6f..000000000 Binary files a/streams/icons/flag_ve.png and /dev/null differ diff --git a/streams/icons/flag_vg.png b/streams/icons/flag_vg.png deleted file mode 100644 index f81baae45..000000000 Binary files a/streams/icons/flag_vg.png and /dev/null differ diff --git a/streams/icons/flag_vi.png b/streams/icons/flag_vi.png deleted file mode 100644 index f81baae45..000000000 Binary files a/streams/icons/flag_vi.png and /dev/null differ diff --git a/streams/icons/flag_vn.png b/streams/icons/flag_vn.png deleted file mode 100644 index 407871696..000000000 Binary files a/streams/icons/flag_vn.png and /dev/null differ diff --git a/streams/icons/flag_vu.png b/streams/icons/flag_vu.png deleted file mode 100644 index 9e2dce97c..000000000 Binary files a/streams/icons/flag_vu.png and /dev/null differ diff --git a/streams/icons/flag_wf.png b/streams/icons/flag_wf.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_wf.png and /dev/null differ diff --git a/streams/icons/flag_ws.png b/streams/icons/flag_ws.png deleted file mode 100644 index ebe5d9c5f..000000000 Binary files a/streams/icons/flag_ws.png and /dev/null differ diff --git a/streams/icons/flag_ye.png b/streams/icons/flag_ye.png deleted file mode 100644 index 0d5f1c89b..000000000 Binary files a/streams/icons/flag_ye.png and /dev/null differ diff --git a/streams/icons/flag_yt.png b/streams/icons/flag_yt.png deleted file mode 100644 index 5d6af4d1e..000000000 Binary files a/streams/icons/flag_yt.png and /dev/null differ diff --git a/streams/icons/flag_za.png b/streams/icons/flag_za.png deleted file mode 100644 index 388616ece..000000000 Binary files a/streams/icons/flag_za.png and /dev/null differ diff --git a/streams/icons/flag_zm.png b/streams/icons/flag_zm.png deleted file mode 100644 index de9abd7d8..000000000 Binary files a/streams/icons/flag_zm.png and /dev/null differ diff --git a/streams/icons/flag_zw.png b/streams/icons/flag_zw.png deleted file mode 100644 index bbebabcd5..000000000 Binary files a/streams/icons/flag_zw.png and /dev/null differ diff --git a/streams/icons/football.svg b/streams/icons/football.svg deleted file mode 100644 index f2eec2302..000000000 --- a/streams/icons/football.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/globe.svg b/streams/icons/globe.svg deleted file mode 100644 index a4a6668c3..000000000 --- a/streams/icons/globe.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/streams/icons/guitar.svg b/streams/icons/guitar.svg deleted file mode 100644 index 324bf9cd2..000000000 --- a/streams/icons/guitar.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/keyboard.svg b/streams/icons/keyboard.svg deleted file mode 100644 index 13b1ad8f1..000000000 --- a/streams/icons/keyboard.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/lightning.svg b/streams/icons/lightning.svg deleted file mode 100644 index 90a49aa5d..000000000 --- a/streams/icons/lightning.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/microphone.svg b/streams/icons/microphone.svg deleted file mode 100644 index 1a06e178f..000000000 --- a/streams/icons/microphone.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/news.svg b/streams/icons/news.svg deleted file mode 100644 index 9d05ec45b..000000000 --- a/streams/icons/news.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/radio.svg b/streams/icons/radio.svg deleted file mode 100644 index 47779898d..000000000 --- a/streams/icons/radio.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/streams/icons/radioactive.svg b/streams/icons/radioactive.svg deleted file mode 100644 index 359960759..000000000 --- a/streams/icons/radioactive.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/streams/icons/rubikscube.svg b/streams/icons/rubikscube.svg deleted file mode 100644 index b56bd9024..000000000 --- a/streams/icons/rubikscube.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/smiley.svg b/streams/icons/smiley.svg deleted file mode 100644 index 1e530e7ed..000000000 --- a/streams/icons/smiley.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/streams/icons/star.svg b/streams/icons/star.svg deleted file mode 100644 index 2473f35de..000000000 --- a/streams/icons/star.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/icons/stream.png b/streams/icons/stream.png deleted file mode 100644 index cbcf94885..000000000 Binary files a/streams/icons/stream.png and /dev/null differ diff --git a/streams/icons/trebleclef.svg b/streams/icons/trebleclef.svg deleted file mode 100644 index 29808c4c0..000000000 --- a/streams/icons/trebleclef.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/streams/icons/violin.svg b/streams/icons/violin.svg deleted file mode 100644 index bf7ccb9e7..000000000 --- a/streams/icons/violin.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/streams/streamcategorydialog.cpp b/streams/streamcategorydialog.cpp deleted file mode 100644 index 00a8a8678..000000000 --- a/streams/streamcategorydialog.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Cantata - * - * Copyright (c) 2011-2013 Craig Drummond - * - * ---- - * - * 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 -#include -#include "streamcategorydialog.h" -#include "mainwindow.h" -#include "settings.h" -#include "streamsmodel.h" -#include "localize.h" -#include "buddylabel.h" -#include "icons.h" - -StreamCategoryDialog::StreamCategoryDialog(const QStringList &categories, QWidget *parent) - : Dialog(parent) - , iconCombo(0) -{ - existingCategories=categories.toSet(); - QWidget *wid = new QWidget(this); - QFormLayout *layout = new QFormLayout(wid); - - nameEntry = new LineEdit(wid); - QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - int row=0; - - layout->setWidget(row, QFormLayout::LabelRole, new BuddyLabel(i18n("Name:"), wid, nameEntry)); - layout->setWidget(row++, QFormLayout::FieldRole, nameEntry); - - QMap icons=StreamsModel::self()->icons(); - if (!icons.isEmpty()) { - iconCombo=new ComboBox(this); - iconCombo->addItem(Icons::self()->streamCategoryIcon, QString(), QString()); - int size=Icon::stdSize(fontMetrics().height()*1.5); - iconCombo->setIconSize(QSize(size,size)); - iconCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - QMap::ConstIterator it=icons.constBegin(); - QMap::ConstIterator end=icons.constEnd(); - - for (; it!=end; ++it) { - if (!it.value().isNull() && !it.key().startsWith(QLatin1String("flag_"))) { - iconCombo->addItem(it.value(), QString(), it.key()); - } - } - bool firstFlag=true; - for (it=icons.constBegin(); it!=end; ++it) { - if (!it.value().isNull() && it.key().startsWith(QLatin1String("flag_"))) { - if (firstFlag) { - if (iconCombo->count()) { - iconCombo->insertSeparator(iconCombo->count()); - } - firstFlag=false; - } - iconCombo->addItem(it.value(), QString(), it.key()); - } - } - layout->setWidget(row, QFormLayout::LabelRole, new BuddyLabel(i18n("Icon:"), wid, iconCombo)); - layout->setWidget(row++, QFormLayout::FieldRole, iconCombo); - connect(iconCombo, SIGNAL(currentIndexChanged(int)), SLOT(changed())); - } - setCaption(i18n("Add Category")); - setMainWidget(wid); - setButtons(Ok|Cancel); - enableButton(Ok, false); - - connect(nameEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); - nameEntry->setFocus(); - resize(400, 100); -} - -void StreamCategoryDialog::setEdit(const QString &editName, const QString &editIconName) -{ - prevIconName=editIconName; - if (iconCombo) { - iconCombo->blockSignals(true); - iconCombo->setCurrentIndex(0); - for (int i=0; icount(); ++i) { - if (iconCombo->itemData(i)==editIconName) { - iconCombo->setCurrentIndex(i); - break; - } - } - iconCombo->blockSignals(false); - } - setCaption(i18n("Edit Category")); - enableButton(Ok, false); - prevName=editName; - nameEntry->blockSignals(true); - nameEntry->setText(editName); - nameEntry->blockSignals(false); -} - -void StreamCategoryDialog::changed() -{ - QString n=name(); - bool enableOk=!n.isEmpty() && (n!=prevName || (iconCombo && icon()!=prevIconName) || !existingCategories.contains(n)); - enableButton(Ok, enableOk); -} diff --git a/streams/streamcategorydialog.h b/streams/streamcategorydialog.h deleted file mode 100644 index 3d064aa5f..000000000 --- a/streams/streamcategorydialog.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Cantata - * - * Copyright (c) 2011-2013 Craig Drummond - * - * ---- - * - * 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. - */ - -#ifndef STREAMCATEGORYDIALOG_H -#define STREAMCATEGORYDIALOG_H - -#include "dialog.h" -#include "lineedit.h" -#include "combobox.h" -#include - -class StreamCategoryDialog : public Dialog -{ - Q_OBJECT - -public: - StreamCategoryDialog(const QStringList &categories, QWidget *parent); - - void setEdit(const QString &editName, const QString &editIconName); - QString name() const { return nameEntry->text().trimmed(); } - QString icon() const { return iconCombo ? iconCombo->itemData(iconCombo->currentIndex()).toString() : prevIconName; } - -private Q_SLOTS: - void changed(); - -private: - QString prevName; - QString prevIconName; - ComboBox *iconCombo; - LineEdit *nameEntry; - QSet existingCategories; -}; - -#endif diff --git a/streams/streamdialog.cpp b/streams/streamdialog.cpp index 83ba77d20..f594e8daa 100644 --- a/streams/streamdialog.cpp +++ b/streams/streamdialog.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "streamdialog.h" #include "mainwindow.h" #include "settings.h" @@ -45,45 +46,9 @@ class NameValidator : public QValidator } }; -void IconCombo::load() -{ - if (0==count()) { - addItem(Icons::self()->radioStreamIcon, QString(), QString()); - QMap icons=StreamsModel::self()->icons(); - QMap::ConstIterator it=icons.constBegin(); - QMap::ConstIterator end=icons.constEnd(); - - for (; it!=end; ++it) { - if (!it.value().isNull() && !it.key().startsWith(QLatin1String("flag_"))) { - addItem(it.value(), QString(), it.key()); - } - } - bool firstFlag=true; - for (it=icons.constBegin(); it!=end; ++it) { - if (!it.value().isNull() && it.key().startsWith(QLatin1String("flag_"))) { - if (firstFlag) { - if (count()) { - insertSeparator(count()); - } - firstFlag=false; - } - addItem(it.value(), QString(), it.key()); - } - } - } -} - -void IconCombo::showEvent(QShowEvent *e) -{ - load(); - ComboBox::showEvent(e); -} - -StreamDialog::StreamDialog(const QStringList &categories, const QStringList &genres, QWidget *parent, bool addToPlayQueue) +StreamDialog::StreamDialog(QWidget *parent, bool addToPlayQueue) : Dialog(parent) , saveCombo(0) - , iconCombo(0) - , iconLabel(0) , urlHandlers(MPDConnection::self()->urlHandlers()) { QWidget *wid = new QWidget(this); @@ -94,52 +59,24 @@ StreamDialog::StreamDialog(const QStringList &categories, const QStringList &gen urlEntry = new LineEdit(wid); saveCombo=new QComboBox(wid); nameEntry = new LineEdit(wid); - if (!StreamsModel::self()->icons().isEmpty()) { - iconCombo=new IconCombo(wid); - } } else { nameEntry = new LineEdit(wid); urlEntry = new LineEdit(wid); - if (!StreamsModel::self()->icons().isEmpty()) { - iconCombo=new IconCombo(wid); - } } nameEntry->setValidator(new NameValidator(this)); - catCombo = new CompletionCombo(wid); - catCombo->setEditable(true); - genreCombo = new CompletionCombo(wid); statusText = new QLabel(this); - if (iconCombo) { - int size=Icon::stdSize(fontMetrics().height()*1.5); - iconCombo->setIconSize(QSize(size,size)); - iconCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - connect(iconCombo, SIGNAL(currentIndexChanged(int)), SLOT(changed())); - } - - QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(catCombo->sizePolicy().hasHeightForWidth()); - catCombo->setSizePolicy(sizePolicy); - genreCombo->setSizePolicy(sizePolicy); urlEntry->setMinimumWidth(300); - multipleGenresText=new QLabel(i18n("NOTE: Use '|' to split mutliple genres - e.g. 'Current|Classic'"), this); nameLabel=new BuddyLabel(i18n("Name:"), wid, nameEntry); - catLabel=new BuddyLabel(i18n("Category:"), wid, catCombo); - genreLabel=new BuddyLabel(i18n("Genre:"), wid, genreCombo); - if (iconCombo) { - iconLabel=new BuddyLabel(i18n("Icon:"), wid, iconCombo); - } BuddyLabel *urlLabel=new BuddyLabel(i18n("URL:"), wid, urlEntry); int row=0; if (addToPlayQueue) { saveCombo->addItem(i18n("Just add to play queue, do not save")); - saveCombo->addItem(i18n("Add to play queue, and save in list of streams")); + saveCombo->addItem(i18n("Add to play queue, and save to favourites")); saveCombo->setCurrentIndex(0); - saveCombo->setEnabled(StreamsModel::self()->isWritable()); + saveCombo->setEnabled(StreamsModel::self()->isFavoritesWritable()); layout->setWidget(row, QFormLayout::LabelRole, urlLabel); layout->setWidget(row++, QFormLayout::FieldRole, urlEntry); layout->setWidget(row++, QFormLayout::FieldRole, saveCombo); @@ -152,28 +89,14 @@ StreamDialog::StreamDialog(const QStringList &categories, const QStringList &gen layout->setWidget(row, QFormLayout::LabelRole, urlLabel); layout->setWidget(row++, QFormLayout::FieldRole, urlEntry); } - if (iconCombo) { - layout->setWidget(row, QFormLayout::LabelRole, iconLabel); - layout->setWidget(row++, QFormLayout::FieldRole, iconCombo); - } - layout->setWidget(row, QFormLayout::LabelRole, catLabel); - layout->setWidget(row++, QFormLayout::FieldRole, catCombo); - layout->setWidget(row, QFormLayout::LabelRole, genreLabel); - layout->setWidget(row++, QFormLayout::FieldRole, genreCombo); - layout->setWidget(row++, QFormLayout::SpanningRole, multipleGenresText); + layout->setWidget(row++, QFormLayout::SpanningRole, statusText); setCaption(i18n("Add Stream")); setMainWidget(wid); setButtons(Ok|Cancel); enableButton(Ok, false); - catCombo->clear(); - catCombo->insertItems(0, categories); - genreCombo->clear(); - genreCombo->insertItems(0, genres); connect(nameEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); connect(urlEntry, SIGNAL(textChanged(const QString &)), SLOT(changed())); - connect(catCombo, SIGNAL(editTextChanged(const QString &)), SLOT(changed())); - connect(genreCombo, SIGNAL(editTextChanged(const QString &)), SLOT(changed())); if (addToPlayQueue) { urlEntry->setFocus(); } else { @@ -182,33 +105,14 @@ StreamDialog::StreamDialog(const QStringList &categories, const QStringList &gen resize(400, 100); } -void StreamDialog::setEdit(const QString &cat, const QString &editName, const QString &editGenre, const QString &editIconName, const QString &editUrl) +void StreamDialog::setEdit(const QString &editName, const QString &editUrl) { - Q_UNUSED(editIconName) setCaption(i18n("Edit Stream")); enableButton(Ok, false); prevName=editName; prevUrl=editUrl; - prevCat=cat; - prevGenre=editGenre; - prevIconName=editIconName; nameEntry->setText(editName); urlEntry->setText(editUrl); - catCombo->setEditText(cat); - genreCombo->setEditText(editGenre); - - if (iconCombo) { - iconCombo->blockSignals(true); - iconCombo->load(); - iconCombo->setCurrentIndex(0); - for (int i=0; icount(); ++i) { - if (iconCombo->itemData(i)==editIconName) { - iconCombo->setCurrentIndex(i); - break; - } - } - iconCombo->blockSignals(false); - } } void StreamDialog::saveComboChanged() @@ -221,16 +125,7 @@ void StreamDialog::setWidgetVisiblity() { bool s=save(); nameEntry->setVisible(s); - catCombo->setVisible(s); - genreCombo->setVisible(s); nameLabel->setVisible(s); - catLabel->setVisible(s); - genreLabel->setVisible(s); - multipleGenresText->setVisible(s); - if (iconCombo) { - iconCombo->setVisible(s); - iconLabel->setVisible(s); - } QApplication::processEvents(); adjustSize(); } @@ -245,11 +140,7 @@ void StreamDialog::changed() enableOk=!u.isEmpty(); } else { QString n=name(); - QString c=category(); - QString g=genre(); - enableOk=!n.isEmpty() && !u.isEmpty() && !c.isEmpty() && - (n!=prevName || u!=prevUrl || c!=prevCat || g!=prevGenre || (iconCombo && icon()!=prevIconName)); - + enableOk=!n.isEmpty() && !u.isEmpty() && (n!=prevName || u!=prevUrl); statusText->setText(validProtocol ? QString() : i18n("ERROR: Invalid protocol")); } enableOk=enableOk && validProtocol; diff --git a/streams/streamdialog.h b/streams/streamdialog.h index 0ae9922ec..aa52f7d0b 100644 --- a/streams/streamdialog.h +++ b/streams/streamdialog.h @@ -28,32 +28,20 @@ #include "combobox.h" #include "dialog.h" #include "lineedit.h" -#include "completioncombo.h" class QLabel; class BuddyLabel; -class IconCombo : public ComboBox -{ -public: - IconCombo(QWidget *p) : ComboBox(p) { } - void load(); - void showEvent(QShowEvent *e); -}; - class StreamDialog : public Dialog { Q_OBJECT public: - StreamDialog(const QStringList &categories, const QStringList &genres, QWidget *parent, bool addToPlayQueue=false); + StreamDialog(QWidget *parent, bool addToPlayQueue=false); - void setEdit(const QString &cat, const QString &editName, const QString &editGenre, const QString &editIconName, const QString &editUrl); + void setEdit(const QString &editName, const QString &editUrl); QString name() const { return nameEntry->text().trimmed(); } QString url() const { return urlEntry->text().trimmed(); } - QString category() const { return catCombo->currentText().trimmed(); } - QString genre() const { return genreCombo->currentText().trimmed(); } - QString icon() const { return iconCombo ? iconCombo->itemData(iconCombo->currentIndex()).toString() : prevIconName; } bool save() const { return !saveCombo || 1==saveCombo->currentIndex(); } private Q_SLOTS: @@ -66,20 +54,10 @@ private: private: QString prevName; QString prevUrl; - QString prevCat; - QString prevGenre; - QString prevIconName; QComboBox *saveCombo; - IconCombo *iconCombo; LineEdit *nameEntry; LineEdit *urlEntry; - CompletionCombo *catCombo; - CompletionCombo *genreCombo; BuddyLabel *nameLabel; - BuddyLabel *catLabel; - BuddyLabel *genreLabel; - BuddyLabel *iconLabel; - QLabel *multipleGenresText; QLabel *statusText; QSet urlHandlers; }; diff --git a/streams/streamfetcher.cpp b/streams/streamfetcher.cpp index 83c3bf3fd..a8d568c89 100644 --- a/streams/streamfetcher.cpp +++ b/streams/streamfetcher.cpp @@ -136,6 +136,13 @@ static QString parse(const QByteArray &data) return parseExt3Mu(data, handlers); } + if (data.startsWith("http://")) { + QStringList lines=QString(data).split(QRegExp(QLatin1String("(\r\n|\n|\r)")), QString::SkipEmptyParts); + if (!lines.isEmpty()) { + return lines.first(); + } + } + return QString(); } @@ -248,7 +255,6 @@ void StreamFetcher::jobFinished(QNetworkReply *reply) redirected=true; } else { QString u=parse(data); - if (u.isEmpty() || u==current) { done.append(MPDParseUtils::addStreamName(current.startsWith(StreamsModel::constPrefix) ? current.mid(StreamsModel::constPrefix.length()) : current, currentName)); } else if (u.startsWith(QLatin1String("http://")) && ++redirects #ifdef ENABLE_KDE_SUPPORT @@ -46,89 +44,31 @@ #include #include -static const char * constUrlProperty("url"); -static const char * constStreamsOnly("streamsOnly"); - StreamsPage::StreamsPage(QWidget *p) : QWidget(p) , enabled(false) , modelIsDownloading(false) { setupUi(this); - importAction = ActionCollection::get()->createAction("importstreams", i18n("Import Streams"), "document-import"); - exportAction = ActionCollection::get()->createAction("exportstreams", i18n("Export Streams"), "document-export"); - addAction = ActionCollection::get()->createAction("addstream", i18n("Add Stream"), Icons::self()->addRadioStreamIcon); + importAction = ActionCollection::get()->createAction("importstreams", i18n("Import Streams Into Favourites"), "document-import"); + exportAction = ActionCollection::get()->createAction("exportstreams", i18n("Export Favourite Streams"), "document-export"); + addAction = ActionCollection::get()->createAction("addstream", i18n("Add New Stream To Favourites"), Icons::self()->addRadioStreamIcon); + addToFavouritesAction = ActionCollection::get()->createAction("addtofavourites", i18n("Add Stream To Favourites"), Icons::self()->addRadioStreamIcon); editAction = ActionCollection::get()->createAction("editstream", i18n("Edit"), Icons::self()->editIcon); - QMenu *importMenu=new QMenu(this); - QAction *importFileAction=importMenu->addAction("From File"); - QList webStreams=WebStream::getAll(); - QAction *radioAction=0; - QMenu *radioMenu=0; - QMap regions; - QAction *diAction=0; - QMenu *diMenu=0; - - foreach (const WebStream *ws, webStreams) { - if (dynamic_cast(ws)) { - if (!radioAction) { - radioAction=importMenu->addAction(i18n("Radio Stations")); - radioMenu=new QMenu(this); - radioAction->setMenu(radioMenu); - } - QMenu *menu=radioMenu; - if (!ws->getRegion().isEmpty()) { - if (!regions.contains(ws->getRegion())) { - QAction *regionAction=radioMenu->addAction(ws->getRegion()); - regions.insert(ws->getRegion(), new QMenu(this)); - regionAction->setMenu(regions[ws->getRegion()]); - } - menu=regions[ws->getRegion()]; - } - QAction *act=menu->addAction(ws->getName()); - act->setProperty(constUrlProperty, ws->getUrl()); - connect(act, SIGNAL(triggered(bool)), this, SLOT(importWebStreams())); - } else if (dynamic_cast(ws)) { - if (!diAction) { - diAction=importMenu->addAction(i18n("Digitally Imported")); - diMenu=new QMenu(this); - diAction->setMenu(diMenu); - } - QAction *act=diMenu->addAction(ws->getName()); - act->setProperty(constUrlProperty, ws->getUrl()); - connect(act, SIGNAL(triggered(bool)), this, SLOT(importWebStreams())); - } else { - QAction *act=importMenu->addAction(i18n("From %1").arg(ws->getName())); - act->setProperty(constUrlProperty, ws->getUrl()); - connect(act, SIGNAL(triggered(bool)), this, SLOT(importWebStreams())); - } - connect(ws, SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &))); - connect(ws, SIGNAL(finished()), this, SLOT(checkIfBusy())); - } - - importAction->setMenu(importMenu); - - QMenu *exportMenu=new QMenu(this); - QAction *exportCantataFileAction=exportMenu->addAction("Streams And Categories"); - exportCantataFileAction->setProperty(constStreamsOnly, false); - QAction *exportStreamsFileAction=exportMenu->addAction("Streams Only"); - exportStreamsFileAction->setProperty(constStreamsOnly, true); - exportAction->setMenu(exportMenu); - replacePlayQueue->setDefaultAction(StdActions::self()->replacePlayQueueAction); // connect(view, SIGNAL(itemsSelected(bool)), addToPlaylist, SLOT(setEnabled(bool))); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(view, SIGNAL(searchItems()), this, SLOT(searchItems())); connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions())); connect(addAction, SIGNAL(triggered(bool)), this, SLOT(add())); + connect(addToFavouritesAction, SIGNAL(triggered(bool)), this, SLOT(addToFavourites())); connect(editAction, SIGNAL(triggered(bool)), this, SLOT(edit())); - connect(importFileAction, SIGNAL(triggered(bool)), this, SLOT(importXml())); - connect(exportCantataFileAction, SIGNAL(triggered(bool)), this, SLOT(exportXml())); - connect(exportStreamsFileAction, SIGNAL(triggered(bool)), this, SLOT(exportXml())); - connect(genreCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(searchItems())); - connect(StreamsModel::self(), SIGNAL(updateGenres(const QSet &)), genreCombo, SLOT(update(const QSet &))); + connect(importAction, SIGNAL(triggered(bool)), this, SLOT(importXml())); + connect(exportAction, SIGNAL(triggered(bool)), this, SLOT(exportXml())); connect(StreamsModel::self(), SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &))); - connect(StreamsModel::self(), SIGNAL(downloading(bool)), this, SLOT(downloading(bool))); + connect(StreamsModel::self(), SIGNAL(loading()), view, SLOT(showSpinner())); + connect(StreamsModel::self(), SIGNAL(loaded()), view, SLOT(hideSpinner())); connect(MPDConnection::self(), SIGNAL(dirChanged()), SLOT(mpdDirChanged())); QMenu *menu=new QMenu(this); menu->addAction(addAction); @@ -147,8 +87,8 @@ StreamsPage::StreamsPage(QWidget *p) view->addAction(StdActions::self()->replacePlayQueueAction); view->addAction(StdActions::self()->addWithPriorityAction); view->addAction(editAction); - view->addAction(exportAction); view->addAction(StdActions::self()->removeAction); + view->addAction(addToFavouritesAction); proxy.setSourceModel(StreamsModel::self()); view->setModel(&proxy); view->setDeleteAction(StdActions::self()->removeAction); @@ -159,9 +99,6 @@ StreamsPage::StreamsPage(QWidget *p) StreamsPage::~StreamsPage() { - foreach (WebStream *ws, WebStream::getAll()) { - ws->cancelDownload(); - } } void StreamsPage::setEnabled(bool e) @@ -181,14 +118,14 @@ void StreamsPage::mpdDirChanged() void StreamsPage::checkWritable() { - bool wasWriteable=StreamsModel::self()->isWritable(); - bool nowWriteable=StreamsModel::self()->checkWritable(); + bool wasWriteable=StreamsModel::self()->isFavoritesWritable(); + bool nowWriteable=StreamsModel::self()->checkFavouritesWritable(); if (nowWriteable) { infoLabel->hide(); } else { infoLabel->setVisible(true); - infoLabel->setText(StreamsModel::dir().startsWith("http:/") ? i18n("Streams from HTTP server") : i18n("Read only.")); + infoLabel->setText(StreamsModel::favouritesDir().startsWith("http:/") ? i18n("Streams from HTTP server") : i18n("Read only.")); } if (wasWriteable!=nowWriteable) { controlActions(); @@ -200,14 +137,14 @@ void StreamsPage::refresh() if (enabled) { checkWritable(); view->setLevel(0); - StreamsModel::self()->reload(); + StreamsModel::self()->reloadFavourites(); exportAction->setEnabled(StreamsModel::self()->rowCount()>0); } } void StreamsPage::save() { - StreamsModel::self()->save(true); + StreamsModel::self()->saveFavourites(true); } void StreamsPage::addSelectionToPlaylist(bool replace, quint8 priorty) @@ -246,33 +183,9 @@ void StreamsPage::itemDoubleClicked(const QModelIndex &index) } } -void StreamsPage::downloading(bool dl) -{ - modelIsDownloading=dl; - checkIfBusy(); -} - -void StreamsPage::checkIfBusy() -{ - bool busy=modelIsDownloading; - if (!busy) { - foreach (WebStream *ws, WebStream::getAll()) { - if (ws->isDownloading()) { - busy=true; - break; - } - } - } - if (busy) { - view->showSpinner(); - } else { - view->hideSpinner(); - } -} - void StreamsPage::importXml() { - if (!StreamsModel::self()->isWritable()) { + if (!StreamsModel::self()->isFavoritesWritable()) { return; } #ifdef ENABLE_KDE_SUPPORT @@ -286,47 +199,18 @@ void StreamsPage::importXml() return; } - if (!StreamsModel::self()->import(fileName)) { + if (!StreamsModel::self()->importXml(fileName)) { MessageBox::error(this, i18n("Failed to import %1!
Please check this is of the correct type.").arg(fileName)); } } void StreamsPage::exportXml() { - QAction *act=qobject_cast(sender()); - bool streamsOnly=act && act->property(constStreamsOnly).toBool(); - QModelIndexList selected=view->selectedIndexes(); - QSet items; - QSet categories; - foreach (const QModelIndex &idx, selected) { - QModelIndex i=proxy.mapToSource(idx); - StreamsModel::Item *itm=static_cast(i.internalPointer()); - if (itm->isCategory()) { - if (!categories.contains(itm)) { - categories.insert(itm); - } - foreach (StreamsModel::StreamItem *s, static_cast(itm)->streams) { - items.insert(s); - } - } else { - items.insert(itm); - categories.insert(static_cast(itm)->parent); - } - } - - QLatin1String ext(streamsOnly ? ".xml" : ".cantata"); - QString name=1==categories.count() - ? static_cast(*(categories.begin()))->name+ext - : QLatin1String("Cantata")+ext; - + QLatin1String ext(".xml"); #ifdef ENABLE_KDE_SUPPORT - QString fileName=streamsOnly - ? KFileDialog::getSaveFileName(name, i18n("*.xml|XML Streams"), this, i18n("Export Streams")) - : KFileDialog::getSaveFileName(name, i18n("*.cantata|Cantata Streams"), this, i18n("Streams And Categories")); + QString fileName=KFileDialog::getSaveFileName(QLatin1String("Cantata")+ext, i18n("*.xml|XML Streams"), this, i18n("Export Streams")); #else - QString fileName=streamsOnly - ? QFileDialog::getSaveFileName(this, i18n("Export Streams"), name, i18n("XML Streams (*.xml)")) - : QFileDialog::getSaveFileName(this, i18n("Streams And Categories"), name, i18n("Cantata Streams (*.cantata)")); + QString fileName=QFileDialog::getSaveFileName(this, i18n("Export Streams"), QLatin1String("Cantata")+ext, i18n("XML Streams (*.xml)")); #endif if (fileName.isEmpty()) { @@ -337,124 +221,100 @@ void StreamsPage::exportXml() fileName+=ext; } - if (!StreamsModel::self()->save(fileName, categories+items, streamsOnly)) { + if (!StreamsModel::self()->saveXml(fileName, QList())) { MessageBox::error(this, i18n("Failed to create %1!").arg(fileName)); } } void StreamsPage::add() { - if (!StreamsModel::self()->isWritable()) { + if (!StreamsModel::self()->isFavoritesWritable()) { return; } - StreamDialog dlg(getCategories(), getGenres(), this); + StreamDialog dlg(this); if (QDialog::Accepted==dlg.exec()) { QString name=dlg.name(); QString url=dlg.url(); - QString cat=dlg.category(); - QString existing=StreamsModel::self()->name(cat, url); + QString existingNameForUrl=StreamsModel::self()->favouritesNameForUrl(url); - if (!existing.isEmpty()) { - MessageBox::error(this, i18n("Stream already exists!
%1").arg(existing)); - return; - } - - if (!StreamsModel::self()->add(cat, name, dlg.genre(), dlg.icon(), url)) { + if (!existingNameForUrl.isEmpty()) { + MessageBox::error(this, i18n("Stream already exists!
%1").arg(existingNameForUrl)); + } else if (StreamsModel::self()->nameExistsInFavourites(name)) { MessageBox::error(this, i18n("A stream named %1 already exists!").arg(name)); + } else { + StreamsModel::self()->addToFavourites(url, name); } } - exportAction->setEnabled(StreamsModel::self()->rowCount()>0); + exportAction->setEnabled(StreamsModel::self()->haveFavourites()); +} + +void StreamsPage::addToFavourites() +{ + if (!StreamsModel::self()->isFavoritesWritable()) { + return; + } + + QModelIndexList selected = view->selectedIndexes(); + QList items; + + foreach (const QModelIndex &i, selected) { + QModelIndex mapped=proxy.mapToSource(i); + const StreamsModel::Item *item=static_cast(mapped.internalPointer()); + if (!item->isCategory() && item->parent && !item->parent->isFavourites) { + items.append(item); + } + } + + foreach (const StreamsModel::Item *item, items) { + StreamsModel::self()->addToFavourites(item->url, item->name); + } } void StreamsPage::removeItems() { - if (!StreamsModel::self()->isWritable()) { + if (!StreamsModel::self()->isFavoritesWritable()) { return; } - QStringList streams; + QModelIndexList selected = view->selectedIndexes(); + QModelIndexList useable; - if (0==selected.size()) { + foreach (const QModelIndex &i, selected) { + QModelIndex mapped=proxy.mapToSource(i); + const StreamsModel::Item *item=static_cast(mapped.internalPointer()); + if (!item->isCategory() && item->parent && item->parent->isFavourites) { + useable.append(mapped); + } + } + + if (useable.isEmpty()) { return; } - - bool haveStreams=false; - bool haveCategories=false; - foreach(const QModelIndex &index, selected) { - if (static_cast(proxy.mapToSource(index).internalPointer())->isCategory()) { - haveCategories=true; - } else { - haveStreams=true; + if (useable.size()>1) { + if (MessageBox::No==MessageBox::warningYesNo(this, i18n("Are you sure you wish to remove the %1 selected streams?").arg(useable.size()))) { + return; } - - if (haveStreams && haveCategories) { - break; - } - } - - QModelIndex firstIndex=proxy.mapToSource(selected.first()); - QString firstName=StreamsModel::self()->data(firstIndex, Qt::DisplayRole).toString(); - QString message; - - if (selected.size()>1) { - if (haveStreams && haveCategories) { - message=i18n("Are you sure you wish to remove the selected categories & streams?"); - } else if (haveStreams) { - message=i18n("Are you sure you wish to remove the %1 selected streams?").arg(selected.size()); - } else { - message=i18n("Are you sure you wish to remove the %1 selected categories (and their streams)?").arg(selected.size()); - } - } else if (haveStreams) { - message=i18n("Are you sure you wish to remove %1?").arg(firstName); } else { - message=i18n("Are you sure you wish to remove the %1 category (and its streams)?").arg(firstName); - } - - if (MessageBox::No==MessageBox::warningYesNo(this, message, i18n("Remove"), StdGuiItem::remove(), StdGuiItem::cancel())) { - return; - } - - // Ensure that if we have a category selected, we dont also try to remove one of its children - QSet removeCategories; - QList removeStreams; - QModelIndexList remove; - //..obtain catagories to remove... - foreach(QModelIndex index, selected) { - QModelIndex idx=proxy.mapToSource(index); - StreamsModel::Item *item=static_cast(idx.internalPointer()); - - if (item->isCategory()) { - removeCategories.insert(static_cast(item)); - } - } - // Obtain streams in non-selected categories... - foreach(QModelIndex index, selected) { - QModelIndex idx=proxy.mapToSource(index); - StreamsModel::Item *item=static_cast(idx.internalPointer()); - - if (!item->isCategory()) { - removeStreams.append(static_cast(item)); + if (MessageBox::No==MessageBox::warningYesNo(this, i18n("Are you sure you wish to remove %1?") + .arg(StreamsModel::self()->data(useable.first(), Qt::DisplayRole).toString()))) { + return; } } - foreach (StreamsModel::CategoryItem *i, removeCategories) { - StreamsModel::self()->removeCategory(i); + foreach (const QModelIndex &i, useable) { + StreamsModel::self()->removeFromFavourites(i); } - foreach (StreamsModel::StreamItem *i, removeStreams) { - StreamsModel::self()->removeStream(i); - } - StreamsModel::self()->updateGenres(); - exportAction->setEnabled(StreamsModel::self()->rowCount()>0); + exportAction->setEnabled(StreamsModel::self()->haveFavourites()); } void StreamsPage::edit() { - if (!StreamsModel::self()->isWritable()) { + if (!StreamsModel::self()->isFavoritesWritable()) { return; } - QStringList streams; + QModelIndexList selected = view->selectedIndexes(); if (1!=selected.size()) { @@ -463,128 +323,78 @@ void StreamsPage::edit() QModelIndex index=proxy.mapToSource(selected.first()); StreamsModel::Item *item=static_cast(index.internalPointer()); - QString name=item->name; - QString icon=item->icon; - - if (item->isCategory()) { - StreamCategoryDialog dlg(getCategories(), this); - dlg.setEdit(name, icon); - if (QDialog::Accepted==dlg.exec()) { - StreamsModel::self()->editCategory(index, dlg.name(), dlg.icon()); - } + if (item->isCategory() || !item->parent || !item->parent->isFavourites) { return; } - StreamDialog dlg(getCategories(), getGenres(), this); - StreamsModel::StreamItem *stream=static_cast(item); - QString url=stream->url.toString(); - QString cat=stream->parent->name; - QString genre=stream->genreString(); + QString name=item->name; + QString url=item->url; - dlg.setEdit(cat, name, genre, icon, url); + StreamDialog dlg(this); + dlg.setEdit(name, url); if (QDialog::Accepted==dlg.exec()) { QString newName=dlg.name(); - QString newIcon=dlg.icon(); QString newUrl=dlg.url(); - QString newCat=dlg.category(); - QString newGenre=dlg.genre(); - QString existingNameForUrl=newUrl!=url ? StreamsModel::self()->name(newCat, newUrl) : QString(); + QString existingNameForUrl=newUrl!=url ? StreamsModel::self()->favouritesNameForUrl(newUrl) : QString(); if (!existingNameForUrl.isEmpty()) { - MessageBox::error(this, i18n("Stream already exists!
%1 (%2)").arg(existingNameForUrl).arg(newCat)); - } else if (newName!=name && StreamsModel::self()->entryExists(newCat, newName)) { - MessageBox::error(this, i18n("A stream named %1 (%2) already exists!").arg(newName).arg(newCat)); + MessageBox::error(this, i18n("Stream already exists!
%1").arg(existingNameForUrl)); + } else if (newName!=name && StreamsModel::self()->nameExistsInFavourites(newName)) { + MessageBox::error(this, i18n("A stream named %1 already exists!").arg(newName)); } else { - StreamsModel::self()->editStream(index, cat, newCat, newName, newGenre, newIcon, newUrl); + item->name=newName; + item->url=newUrl; + StreamsModel::self()->updateFavouriteStream(item); } } } +void StreamsPage::searchItems() +{ + QString text=view->searchText().trimmed(); + proxy.update(text, QString()); + if (proxy.enabled() && !text.isEmpty()) { + view->expandAll(); + } +} + void StreamsPage::controlActions() { QModelIndexList selected=view->selectedIndexes(); - editAction->setEnabled(1==selected.size() && StreamsModel::self()->isWritable()); - StdActions::self()->removeAction->setEnabled(selected.count() && StreamsModel::self()->isWritable()); - addAction->setEnabled(StreamsModel::self()->isWritable()); - exportAction->setEnabled(StreamsModel::self()->rowCount()); - importAction->setEnabled(StreamsModel::self()->isWritable()); + editAction->setEnabled(false); + addToFavouritesAction->setEnabled(false); + if (1==selected.size() && StreamsModel::self()->isFavoritesWritable()) { + const StreamsModel::Item *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); + if (!item->isCategory() && item->parent && item->parent->isFavourites) { + editAction->setEnabled(true); + } + } + StdActions::self()->removeAction->setEnabled(false); + if (!selected.isEmpty() && StreamsModel::self()->isFavoritesWritable()) { + bool enableRemove=true; + bool enableAddToFav=true; + foreach (const QModelIndex &idx, selected) { + const StreamsModel::Item *item=static_cast(proxy.mapToSource(idx).internalPointer()); + if (item->isCategory() || (item->parent && !item->parent->isFavourites)) { + enableRemove=false; + break; + } + if (item->isCategory() || (item->parent && item->parent->isFavourites)) { + enableAddToFav=false; + break; + } + if (!enableRemove && !enableAddToFav) { + break; + } + } + StdActions::self()->removeAction->setEnabled(enableRemove); + addToFavouritesAction->setEnabled(enableAddToFav); + } + addAction->setEnabled(StreamsModel::self()->isFavoritesWritable()); + exportAction->setEnabled(StreamsModel::self()->haveFavourites()); + importAction->setEnabled(StreamsModel::self()->isFavoritesWritable()); StdActions::self()->replacePlayQueueAction->setEnabled(selected.count()); StdActions::self()->addWithPriorityAction->setEnabled(selected.count()); menuButton->controlState(); } - -void StreamsPage::searchItems() -{ - QString text=view->searchText().trimmed(); - proxy.update(text, genreCombo->currentIndex()<=0 ? QString() : genreCombo->currentText()); - if (proxy.enabled() && !text.isEmpty()) { - view->expandAll(); - } -} - -QStringList StreamsPage::getCategories() -{ - QStringList categories; - for(int i=0; irowCount(); ++i) { - QModelIndex idx=StreamsModel::self()->index(i, 0, QModelIndex()); - if (idx.isValid()) { - categories.append(static_cast(idx.internalPointer())->name); - } - } - - if (categories.isEmpty()) { - categories.append(i18n("General")); - } - - qSort(categories); - return categories; -} - -QStringList StreamsPage::getGenres() -{ - QStringList g=genreCombo->entries().toList(); - qSort(g); - return g; -} - -void StreamsPage::importWebStreams() -{ - if (!StreamsModel::self()->isWritable()) { - return; - } - - QObject *obj=qobject_cast(sender()); - if (!obj) { - return; - } - - QUrl url=obj->property(constUrlProperty).toUrl(); - if (!url.isValid()) { - return; - } - - WebStream *ws=WebStream::get(url); - if (!ws) { - return; - } - - if (ws->isDownloading()) { - MessageBox::error(this, i18n("Download from %1 is already in progress!").arg(ws->getName())); - return; - } - - if (getCategories().contains(ws->getName())) { - if (MessageBox::No==MessageBox::warningYesNo(this, i18n("Update streams from %1?\n(This will replace any existing streams in this category)").arg(ws->getName()), - i18n("Update %1").arg(ws->getName()), GuiItem(i18n("Update")), StdGuiItem::cancel())) { - return; - } - } else { - if (MessageBox::No==MessageBox::questionYesNo(this, i18n("Download streams from %1?").arg(ws->getName()), i18n("Download %1").arg(ws->getName()), - GuiItem(i18n("Download")), StdGuiItem::cancel())) { - return; - } - } - ws->download(); - view->showSpinner(); -} diff --git a/streams/streamspage.h b/streams/streamspage.h index fe2c8ee52..376dac766 100644 --- a/streams/streamspage.h +++ b/streams/streamspage.h @@ -46,8 +46,6 @@ public: void setView(int v) { view->setMode((ItemView::Mode)v); } void focusSearch() { view->focusSearch(); } void goBack() { view->backActivated(); } - QStringList getCategories(); - QStringList getGenres(); Q_SIGNALS: void add(const QStringList &streams, bool replace, quint8 priorty); @@ -62,19 +60,16 @@ public Q_SLOTS: void controlActions(); private Q_SLOTS: - void importWebStreams(); void importXml(); void exportXml(); void add(); + void addToFavourites(); void edit(); void searchItems(); void itemDoubleClicked(const QModelIndex &index); - void downloading(bool dl); - void checkIfBusy(); private: void addItemsToPlayQueue(const QModelIndexList &indexes, bool replace, quint8 priorty=0); - void initWebStreams(); private: bool enabled; @@ -83,8 +78,7 @@ private: Action *exportAction; Action *addAction; Action *editAction; - QAction *importIceCastAction; - QAction *importSomaFmCastAction; + Action *addToFavouritesAction; StreamsProxyModel proxy; }; diff --git a/streams/streamspage.ui b/streams/streamspage.ui index f454bf877..6f106ce41 100644 --- a/streams/streamspage.ui +++ b/streams/streamspage.ui @@ -36,8 +36,18 @@ 0
- - + + + Qt::Horizontal + + + + 16 + 20 + + + +
@@ -54,11 +64,6 @@ QTreeView
itemview.h
- - GenreCombo - QComboBox -
genrecombo.h
-
StatusLabel QLabel diff --git a/streams/webstreams.cpp b/streams/webstreams.cpp deleted file mode 100644 index d7f72df88..000000000 --- a/streams/webstreams.cpp +++ /dev/null @@ -1,585 +0,0 @@ -/* - * Cantata - * - * Copyright (c) 2011-2013 Craig Drummond - * - * ---- - * - * 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 "webstreams.h" -#include "networkaccessmanager.h" -#include "streamsmodel.h" -#include "localize.h" -#include "song.h" -#include "qjson/parser.h" -#include -#include -#if QT_VERSION >= 0x050000 -#include -#endif - -static const char * constDiApiUsername="ephemeron"; -static const char * constDiApiPassword="dayeiph0ne@pp"; -//static const QString constDiAuthUrl=QLatin1String("http://api.audioaddict.com/v1/%1/members/authenticate"); -static const QString constDiChannelListUrl=QLatin1String("http://api.v2.audioaddict.com/v1/%1/mobile/batch_update?asset_group_key=mobile_icons&stream_set_key="); -static const QString constDiStdUrl=QLatin1String("http://%1/public3/%2.pls"); - -enum Type { - WS_IceCast, - WS_SomaFm, - WS_Radio, - WS_DigitallyImported, - - WS_Count -}; - -static QList providers; - -QList WebStream::getAll() -{ - if (providers.isEmpty()) { - QFile f(":streams.xml"); - if (f.open(QIODevice::ReadOnly)) { - QXmlStreamReader doc(&f); - while (!doc.atEnd()) { - doc.readNext(); - - if (doc.isStartElement() && QLatin1String("stream")==doc.name()) { - QString name=doc.attributes().value("name").toString(); - QString icon=doc.attributes().value("icon").toString(); - QString region=doc.attributes().value("region").toString(); - unsigned int type=doc.attributes().value("type").toString().toUInt(); - QUrl url=QUrl(doc.attributes().value("url").toString()); - switch (type) { - case WS_IceCast: providers.append(new IceCastWebStream(name, icon, region, url)); break; - case WS_SomaFm: providers.append(new SomaFmWebStream(name, icon, region, url)); break; - case WS_Radio: providers.append(new RadioWebStream(name, icon, region, url)); break; - case WS_DigitallyImported: providers.append(new DigitallyImportedWebStream(name, icon, region, url)); break; - default: break; - } - } - } - } - } - return providers; -} - -WebStream * WebStream::get(const QUrl &url) -{ - foreach (WebStream *p, providers) { - if (p->url==url) { - return p; - } - } - - return 0; -} - -void WebStream::download() -{ - if (job) { - return; - } - - QNetworkRequest req(channelListUrl()); - addHeaders(req); - job=NetworkAccessManager::self()->get(req); - connect(job, SIGNAL(finished()), this, SLOT(downloadFinished())); -} - -void WebStream::cancelDownload() -{ - if (job) { - disconnect(job, SIGNAL(finished()), this, SLOT(downloadFinished())); - job->deleteLater(); - job=0; - } -} - -void WebStream::downloadFinished() -{ - QNetworkReply *reply=qobject_cast(sender()); - if (!reply) { - return; - } - - if(QNetworkReply::NoError==reply->error()) { - QList streams=parse(reply); - - if (streams.isEmpty()) { - emit error(i18nc("message \n url", "No streams downloaded from %1\n(%2)").arg(name).arg(url.toString())); - } else { - StreamsModel::self()->add(name, icon, streams); - } - } else { - emit error(i18nc("message \n url", "Failed to download streams from %1\n(%2)").arg(name).arg(url.toString())); - } - job=0; - - reply->deleteLater(); - emit finished(); -} - -static QString fixSingleGenre(const QString &g) -{ - if (g.length()) { - QString genre=Song::capitalize(g); - genre[0]=genre[0].toUpper(); - genre=genre.trimmed(); - genre=genre.replace(QLatin1String("Afrocaribbean"), QLatin1String("Afro-Caribbean")); - genre=genre.replace(QLatin1String("Afro Caribbean"), QLatin1String("Afro-Caribbean")); - if (genre.length() < 3 || - QLatin1String("The")==genre || QLatin1String("All")==genre || - QLatin1String("Various")==genre || QLatin1String("Unknown")==genre || - QLatin1String("Misc")==genre || QLatin1String("Mix")==genre || QLatin1String("100%")==genre || - genre.contains("ÃÂ") || // Broken unicode. - genre.contains(QRegExp("^#x[0-9a-f][0-9a-f]"))) { // Broken XML entities. - return QString(); - } - - if (genre==QLatin1String("R&B") || genre==QLatin1String("R B") || genre==QLatin1String("Rnb") || genre==QLatin1String("RnB")) { - return QLatin1String("R&B"); - } - if (genre==QLatin1String("Classic") || genre==QLatin1String("Classical")) { - return QLatin1String("Classical"); - } - if (genre==QLatin1String("Christian") || genre.startsWith(QLatin1String("Christian "))) { - return QLatin1String("Christian"); - } - if (genre==QLatin1String("Rock") || genre.startsWith(QLatin1String("Rock "))) { - return QLatin1String("Rock"); - } - if (genre==QLatin1String("Easy") || genre==QLatin1String("Easy Listening")) { - return QLatin1String("Easy Listening"); - } - if (genre==QLatin1String("Hit") || genre==QLatin1String("Hits") || genre==QLatin1String("Easy listening")) { - return QLatin1String("Hits"); - } - if (genre==QLatin1String("Hip") || genre==QLatin1String("Hiphop") || genre==QLatin1String("Hip Hop") || genre==QLatin1String("Hop Hip")) { - return QLatin1String("Hip Hop"); - } - if (genre==QLatin1String("News") || genre==QLatin1String("News talk")) { - return QLatin1String("News"); - } - if (genre==QLatin1String("Top40") || genre==QLatin1String("Top 40") || genre==QLatin1String("40Top") || genre==QLatin1String("40 Top")) { - return QLatin1String("Top 40"); - } - - QStringList small=QStringList() << QLatin1String("Adult Contemporary") << QLatin1String("Alternative") - << QLatin1String("Community Radio") << QLatin1String("Local Service") - << QLatin1String("Multiultural") << QLatin1String("News") - << QLatin1String("Student") << QLatin1String("Urban"); - - foreach (const QString &s, small) { - if (genre==s || genre.startsWith(s+" ") || genre.endsWith(" "+s)) { - return s; - } - } - - // Convert XX's to XXs - if (genre.contains(QRegExp("^[0-9]0's$"))) { - genre=genre.remove('\''); - } - if (genre.length()>25 && (0==genre.indexOf(QRegExp("^[0-9]0s ")) || 0==genre.indexOf(QRegExp("^[0-9]0 ")))) { - int pos=genre.indexOf(' '); - if (pos>1) { - genre=genre.left(pos); - } - } - // Convert 80 -> 80s. - return genre.contains(QRegExp("^[0-9]0$")) ? genre + 's' : genre; - } - return g; -} - -static QString fixGenres(const QString &genre) -{ - QString g(genre); - int pos=g.indexOf("3) { - g=g.left(pos); - } - pos=g.indexOf("("); - if (pos>3) { - g=g.left(pos); - } - - g=Song::capitalize(g); - QStringList genres=g.split('|', QString::SkipEmptyParts); - QStringList allGenres; - - foreach (const QString &genre, genres) { - allGenres+=genre.split('/', QString::SkipEmptyParts); - } - - QStringList fixed; - foreach (const QString &genre, allGenres) { - fixed.append(fixSingleGenre(genre)); - } - return fixed.join(StreamsModel::constGenreSeparator); -} - -static void trimGenres(QMultiHash &genres) -{ - QSet genreSet = genres.keys().toSet(); - foreach (const QString &genre, genreSet) { - if (genres.count(genre) < 3) { - const QList &smallGnre = genres.values(genre); - foreach (StreamsModel::StreamItem* s, smallGnre) { - s->genres.remove(genre); - } - } - } -} - -QList IceCastWebStream::parse(QIODevice *dev) -{ - QList streams; - QString name; - QUrl url; - QString genre; - QSet names; - QMultiHash genres; - int level=0; - QXmlStreamReader doc(dev); - - while (!doc.atEnd()) { - doc.readNext(); - - if (doc.isStartElement()) { - ++level; - if (2==level && QLatin1String("entry")==doc.name()) { - name=QString(); - url=QUrl(); - genre=QString(); - } else if (3==level) { - if (QLatin1String("server_name")==doc.name()) { - name=doc.readElementText(); - --level; - } else if (QLatin1String("genre")==doc.name()) { - genre=fixGenres(doc.readElementText()); - --level; - } else if (QLatin1String("listen_url")==doc.name()) { - url=QUrl(doc.readElementText()); - --level; - } - } - } else if (doc.isEndElement()) { - if (2==level && QLatin1String("entry")==doc.name() && !name.isEmpty() && url.isValid() && !names.contains(name)) { - StreamsModel::StreamItem *item=new StreamsModel::StreamItem(name, genre, QString(), url); - streams.append(item); - foreach (const QString &genre, item->genres) { - genres.insert(genre, item); - } - names.insert(item->name); - } - --level; - } - } - trimGenres(genres); - return streams; -} - -QList SomaFmWebStream::parse(QIODevice *dev) -{ - QList streams; - QSet names; - QString streamFormat; - QMultiHash genres; - QString name; - QUrl url; - QString genre; - int level=0; - QXmlStreamReader doc(dev); - - while (!doc.atEnd()) { - doc.readNext(); - - if (doc.isStartElement()) { - ++level; - if (2==level && QLatin1String("channel")==doc.name()) { - name=QString(); - url=QUrl(); - genre=QString(); - streamFormat=QString(); - } else if (3==level) { - if (QLatin1String("title")==doc.name()) { - name=doc.readElementText(); - --level; - } else if (QLatin1String("genre")==doc.name()) { - genre=fixGenres(doc.readElementText()); - --level; - } else if (QLatin1String("fastpls")==doc.name()) { - if (streamFormat.isEmpty() || QLatin1String("mp3")!=streamFormat) { - streamFormat=doc.attributes().value("format").toString(); - url=QUrl(doc.readElementText()); - --level; - } - } - } - } else if (doc.isEndElement()) { - if (2==level && QLatin1String("channel")==doc.name() && !name.isEmpty() && url.isValid() && !names.contains(name)) { - StreamsModel::StreamItem *item=new StreamsModel::StreamItem(name, genre, QString(), url); - streams.append(item); - foreach (const QString &genre, item->genres) { - genres.insert(genre, item); - } - names.insert(item->name); - } - --level; - } - } - - trimGenres(genres); - return streams; -} - -struct Stream { - enum Format { - Unknown, - WMA, - OGG, - MP3, - AAC - }; - - Stream() : format(Unknown), bitrate(0) { } - bool operator<(const Stream &o) const { - return weight()>o.weight(); - } - - int weight() const { - return ((bitrate&0xff)<<8)+(format&0x0f); - } - - void setFormat(const QString &f) { - if (QLatin1String("mp3")==f.toLower()) { - format=MP3; - } else if (QLatin1String("aacplus")==f.toLower()) { - format=AAC; - } else if (QLatin1String("ogg vorbis")==f.toLower()) { - format=OGG; - } else if (QLatin1String("windows media")==f.toLower()) { - format=WMA; - } else { - format=Unknown; - } - } - - QUrl url; - Format format; - unsigned int bitrate; -}; - -struct StationEntry { - StationEntry() { clear(); } - void clear() { name=location=comment=QString(); streams.clear(); } - QString name; - QString location; - QString comment; - QList streams; -}; - -static QString getString(QString &str, const QString &start, const QString &end) -{ - QString rv; - int b=str.indexOf(start); - int e=-1==b ? -1 : str.indexOf(end, b+start.length()); - if (-1!=e) { - rv=str.mid(b+start.length(), e-(b+start.length())).trimmed(); - str=str.mid(e+end.length()); - } - return rv; -} - -QList RadioWebStream::parse(QIODevice *dev) -{ - QList streams; -// QMultiHash genres; - QSet names; - - if (dev) { - StationEntry entry; - - while (!dev->atEnd()) { - QString line=dev->readLine().trimmed().replace("> <", "><").replace("", "").replace("
", "
") - .replace(" ,", ","); - if (""==line) { - entry.clear(); - } else if (line.startsWith("", ""); - QString extra=getString(line, "", ""); - if (!extra.isEmpty()) { - entry.name+=" "+extra; - } - } else { - // Station URLs... - QString url; - QString bitrate; - int idx=0; - do { - url=getString(line, "href=\"", "\""); - bitrate=getString(line, ">", " Kbps"); - bool sameFormatAsLast=line.startsWith(","); - if (!url.isEmpty() && !bitrate.isEmpty() && !url.startsWith(QLatin1String("javascript")) && idx")) { - if (entry.location.isEmpty()) { - entry.location=getString(line, "", ""); - } else { - entry.comment=getString(line, "", ""); - } - } else if (""==line) { - if (entry.streams.count()) { - qSort(entry.streams); - QString name; - QUrl url=entry.streams.at(0).url; - - if (QLatin1String("National")==entry.location || entry.name.endsWith("("+entry.location+")")) { - name=entry.name; - } else if (entry.name.endsWith(")")) { - name=entry.name.left(entry.name.length()-1)+", "+entry.location+")"; - } else { - name=entry.name+" ("+entry.location+")"; - } - - if (!names.contains(name) && !name.isEmpty() && url.isValid()) { - QString genre=fixGenres(entry.comment); - StreamsModel::StreamItem *item=new StreamsModel::StreamItem(name, genre, QString(), url); - streams.append(item); -// foreach (const QString &genre, item->genres) { -// genres.insert(genre, item); -// } - names.insert(item->name); - } - } - } - } - } - -// trimGenres(genres); - return streams; -} - -DigitallyImportedWebStream::DigitallyImportedWebStream(const QString &n, const QString &i, const QString &r, const QUrl &u) - : WebStream(n, i, r, u) -// , premiumStream(-1) -{ - QStringList parts=u.host().split("."); - if (3==parts.count()) { - serviceName=parts.at(1); - } - listenHost=QLatin1String("listen.")+u.host().remove("www."); -} - -QList DigitallyImportedWebStream::parse(QIODevice *dev) -{ - QList streams; - QJson::Parser parser; - QVariantMap data = parser.parse(dev).toMap(); - - if (data.contains("channel_filters")) { - QVariantList filters = data["channel_filters"].toList(); - - foreach (const QVariant &filter, filters) { - // Find the filter called "All" - QVariantMap filterMap = filter.toMap(); - if (filterMap.value("name", QString()).toString() != "All") { - continue; - } - - // Add all its stations to the result - QVariantList channels = filterMap.value("channels", QVariantList()).toList(); - foreach (const QVariant &channel, channels) { - QVariantMap channelMap = channel.toMap(); - QString u=constDiStdUrl.arg(listenHost).arg(channelMap.value("key").toString()); - - StreamsModel::StreamItem *item=new StreamsModel::StreamItem(channelMap.value("name").toString(), QString(), QString(), QUrl(u)); - streams.append(item); - } - - break; - } - } - - return streams; -} - -QUrl DigitallyImportedWebStream::channelListUrl() const -{ - return QUrl(constDiChannelListUrl.arg(serviceName)); -} - -void DigitallyImportedWebStream::addHeaders(QNetworkRequest &r) -{ - #if QT_VERSION < 0x050000 - r.setRawHeader("Authorization", "Basic "+QString("%1:%2").arg(constDiApiUsername, constDiApiPassword).toAscii().toBase64()); - #else - r.setRawHeader("Authorization", "Basic "+QString("%1:%2").arg(constDiApiUsername, constDiApiPassword).toLatin1().toBase64()); - #endif -} - -//QUrl DigitallyImportedWebStream::modifyUrl(const QUrl &u) const -//{ -// if (premiumHash.isEmpty()) { -// return u; -// } -// QUrl modified(u); -// #if QT_VERSION < 0x050000 -// QUrl &query=url; -// #else -// QUrlQuery query; -// #endif -// QString host=modified.host(); -// if (2==premiumStream || premiumStream<0 || premiumStream>2) { -// host=host.replace("public3", "premium"); -// } else if (1==premiumStream) { -// host=host.replace("public3", "premium_medium"); -// } else if (0==premiumStream) { -// host=host.replace("public3", "premium_high"); -// } -// modified.setHost(host); -// query.addQueryItem("hash", premiumHash); -// #if QT_VERSION >= 0x050000 -// modified.setQuery(query); -// #endif -// return modified; -//} diff --git a/streams/webstreams.h b/streams/webstreams.h deleted file mode 100644 index c37e19aac..000000000 --- a/streams/webstreams.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Cantata - * - * Copyright (c) 2011-2013 Craig Drummond - * - * ---- - * - * 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. - */ - -#ifndef WEBSTREAMS_H -#define WEBSTREAMS_H - -#include "streamsmodel.h" -#include -#include - -class QNetworkReply; -class QNetworkRequest; - -class WebStream : public QObject -{ - Q_OBJECT -public: - static QList getAll(); - static WebStream * get(const QUrl &url); - - WebStream(const QString &n, const QString &i, const QString &r, const QUrl &u) - : name(n),icon(i), region(r), url(u), job(0) { } - virtual ~WebStream() { } - - virtual QList parse(QIODevice *dev)=0; - - const QString & getName() const { return name; } - const QString & getIcon() const { return icon; } - const QString & getRegion() const { return region; } - const QUrl & getUrl() const { return url; } - bool isDownloading() const { return 0!=job; } - void download(); - void cancelDownload(); -// virtual QUrl modifyUrl(const QUrl &u) const { return u; } - -Q_SIGNALS: - void finished(); - void error(const QString &); - -private Q_SLOTS: - void downloadFinished(); - -private: - virtual QUrl channelListUrl() const { return url; } - virtual void addHeaders(QNetworkRequest &) { } - -protected: - QString name; - QString icon; - QString region; - QUrl url; - QNetworkReply *job; -}; - -class IceCastWebStream : public WebStream -{ -public: - IceCastWebStream(const QString &n, const QString &i, const QString &r, const QUrl &u) - : WebStream(n, i, r, u) { } - QList parse(QIODevice *dev); -}; - -class SomaFmWebStream : public WebStream -{ -public: - SomaFmWebStream(const QString &n, const QString &i, const QString &r, const QUrl &u) - : WebStream(n, i, r, u) { } - QList parse(QIODevice *dev); -}; - -class RadioWebStream : public WebStream -{ -public: - RadioWebStream(const QString &n, const QString &i, const QString &r, const QUrl &u) - : WebStream(n, i, r, u) { } - QList parse(QIODevice *dev); -}; - -class DigitallyImportedWebStream: public WebStream -{ -public: - DigitallyImportedWebStream(const QString &n, const QString &i, const QString &r, const QUrl &u); - QList parse(QIODevice *dev); - -private: - QUrl channelListUrl() const; - void addHeaders(QNetworkRequest &r); -// QUrl modifyUrl(const QUrl &u) const; - -private: - QString listenHost; - QString serviceName; -// QString premiumHash; -// int premiumStream; -}; - -#endif