381 lines
13 KiB
C++
381 lines
13 KiB
C++
/*
|
|
* Cantata
|
|
*
|
|
* Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@gmail.com>
|
|
*
|
|
* ----
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "dynamicplaylists.h"
|
|
#include "config.h"
|
|
#include "support/utils.h"
|
|
#include "support/monoicon.h"
|
|
#include "widgets/icons.h"
|
|
#include "models/roles.h"
|
|
#include "gui/settings.h"
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
#include <QProcess>
|
|
#include <QTimer>
|
|
#include <QIcon>
|
|
#include <QUrl>
|
|
#include <QNetworkProxy>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
|
|
const QString RulesPlaylists::constExtension=QLatin1String(".rules");
|
|
const QString RulesPlaylists::constRuleKey=QLatin1String("Rule");
|
|
const QString RulesPlaylists::constArtistKey=QLatin1String("Artist");
|
|
const QString RulesPlaylists::constSimilarArtistsKey=QLatin1String("SimilarArtists");
|
|
const QString RulesPlaylists::constAlbumArtistKey=QLatin1String("AlbumArtist");
|
|
const QString RulesPlaylists::constComposerKey=QLatin1String("Composer");
|
|
const QString RulesPlaylists::constCommentKey=QLatin1String("Comment");
|
|
const QString RulesPlaylists::constAlbumKey=QLatin1String("Album");
|
|
const QString RulesPlaylists::constTitleKey=QLatin1String("Title");
|
|
const QString RulesPlaylists::constGenreKey=QLatin1String("Genre");
|
|
const QString RulesPlaylists::constDateKey=QLatin1String("Date");
|
|
const QString RulesPlaylists::constRatingKey=QLatin1String("Rating");
|
|
const QString RulesPlaylists::constIncludeUnratedKey=QLatin1String("IncludeUnrated");
|
|
const QString RulesPlaylists::constDurationKey=QLatin1String("Duration");
|
|
const QString RulesPlaylists::constNumTracksKey=QLatin1String("NumTracks");
|
|
const QString RulesPlaylists::constMaxAgeKey=QLatin1String("MaxAge");
|
|
const QString RulesPlaylists::constFileKey=QLatin1String("File");
|
|
const QString RulesPlaylists::constExactKey=QLatin1String("Exact");
|
|
const QString RulesPlaylists::constExcludeKey=QLatin1String("Exclude");
|
|
const QString RulesPlaylists::constOrderKey=QLatin1String("Order");
|
|
const QString RulesPlaylists::constOrderAscendingKey=QLatin1String("OrderAscending");
|
|
const QChar RulesPlaylists::constRangeSep=QLatin1Char('-');
|
|
const QChar RulesPlaylists::constKeyValSep=QLatin1Char(':');
|
|
|
|
RulesPlaylists::Order RulesPlaylists::toOrder(const QString &str)
|
|
{
|
|
for (int i=0; i<Order_Count; ++i) {
|
|
if (orderStr((Order)i)==str) {
|
|
return (Order)i;
|
|
}
|
|
}
|
|
return Order_Random;
|
|
}
|
|
|
|
QString RulesPlaylists::orderStr(Order order)
|
|
{
|
|
switch(order) {
|
|
case Order_AlbumArtist: return constAlbumArtistKey;
|
|
case Order_Artist: return constArtistKey;
|
|
case Order_Album: return constAlbumKey;
|
|
case Order_Composer: return constComposerKey;
|
|
case Order_Date: return constDateKey;
|
|
case Order_Genre: return constGenreKey;
|
|
case Order_Rating: return constRatingKey;
|
|
case Order_Title: return constOrderKey;
|
|
case Order_Age: return "Age";
|
|
default:
|
|
case Order_Random: return "Random";
|
|
}
|
|
}
|
|
|
|
QString RulesPlaylists::orderName(Order order)
|
|
{
|
|
switch(order) {
|
|
case Order_AlbumArtist: return tr("Album Artist");
|
|
case Order_Artist: return tr("Artist");
|
|
case Order_Album: return tr("Album");
|
|
case Order_Composer: return tr("Composer");
|
|
case Order_Date: return tr("Date");
|
|
case Order_Genre: return tr("Genre");
|
|
case Order_Rating: return tr("Rating");
|
|
case Order_Title: return tr("Title");
|
|
case Order_Age: return tr("File Age");
|
|
default:
|
|
case Order_Random: return tr("Random");
|
|
}
|
|
}
|
|
|
|
RulesPlaylists::RulesPlaylists(int icon, const QString &dir)
|
|
: rulesDir(dir)
|
|
{
|
|
icn=MonoIcon::icon((FontAwesome::icon)icon, Utils::monoIconColor());
|
|
loadLocal();
|
|
}
|
|
|
|
QVariant RulesPlaylists::headerData(int, Qt::Orientation, int) const
|
|
{
|
|
return QVariant();
|
|
}
|
|
|
|
int RulesPlaylists::rowCount(const QModelIndex &parent) const
|
|
{
|
|
return parent.isValid() ? 0 : entryList.count();
|
|
}
|
|
|
|
bool RulesPlaylists::hasChildren(const QModelIndex &parent) const
|
|
{
|
|
return !parent.isValid();
|
|
}
|
|
|
|
QModelIndex RulesPlaylists::parent(const QModelIndex &) const
|
|
{
|
|
return QModelIndex();
|
|
}
|
|
|
|
QModelIndex RulesPlaylists::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid() || !hasIndex(row, column, parent) || row>=entryList.count()) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
return createIndex(row, column);
|
|
}
|
|
|
|
QVariant RulesPlaylists::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
switch (role) {
|
|
case Cantata::Role_TitleText:
|
|
return title();
|
|
case Cantata::Role_SubText:
|
|
return descr();
|
|
case Qt::DecorationRole:
|
|
return icon();
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
if (index.parent().isValid() || index.row()>=entryList.count()) {
|
|
return QVariant();
|
|
}
|
|
|
|
switch (role) {
|
|
case Qt::ToolTipRole:
|
|
if (!Settings::self()->infoTooltips()) {
|
|
return QVariant();
|
|
}
|
|
case Qt::DisplayRole:
|
|
return entryList.at(index.row()).name;
|
|
case Cantata::Role_SubText: {
|
|
const Entry &e=entryList.at(index.row());
|
|
return tr("%n Rule(s)", "", e.rules.count())+(e.haveRating() ? tr(", Rating: %1..%2")
|
|
.arg((double)e.ratingFrom/Song::Rating_Step).arg((double)e.ratingTo/Song::Rating_Step) : QString()) +
|
|
(isDynamic() ? QString() : (QLatin1String(", ") + orderName(e.order))) +
|
|
(isDynamic() || Order_Random==e.order ? QString() : (" ("+(e.orderAscending ? tr("Ascending") : tr("Descending"))+")"));
|
|
}
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
Qt::ItemFlags RulesPlaylists::flags(const QModelIndex &index) const
|
|
{
|
|
if (index.isValid()) {
|
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
|
}
|
|
return Qt::NoItemFlags;
|
|
}
|
|
|
|
RulesPlaylists::Entry RulesPlaylists::entry(const QString &e)
|
|
{
|
|
if (!e.isEmpty()) {
|
|
QList<Entry>::Iterator it=find(e);
|
|
if (it!=entryList.end()) {
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
Entry def;
|
|
def.numTracks=defaultNumTracks();
|
|
return def;
|
|
}
|
|
|
|
bool RulesPlaylists::save(const Entry &e)
|
|
{
|
|
if (e.name.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
QString string;
|
|
QTextStream str(&string);
|
|
if (e.numTracks >= minTracks() && e.numTracks <= maxTracks()) {
|
|
str << constNumTracksKey << constKeyValSep << e.numTracks << '\n';
|
|
}
|
|
if (0!=e.ratingFrom || 0!=e.ratingTo) {
|
|
str << constRatingKey << constKeyValSep << e.ratingFrom << constRangeSep << e.ratingTo << '\n';
|
|
str << constIncludeUnratedKey << constKeyValSep << (e.includeUnrated ? "true" : "false") << '\n';
|
|
}
|
|
if (0!=e.minDuration || 0!=e.maxDuration) {
|
|
str << constDurationKey << constKeyValSep << e.minDuration << constRangeSep << e.maxDuration << '\n';
|
|
}
|
|
if (0!=e.maxAge) {
|
|
str << constMaxAgeKey << constKeyValSep << e.maxAge << '\n';
|
|
}
|
|
if (Order_Random!=e.order) {
|
|
str << constOrderKey << constKeyValSep << orderStr(e.order) << '\n'
|
|
<< constOrderAscendingKey << constKeyValSep << (e.orderAscending ? "true" : "false") << '\n';
|
|
}
|
|
for (const Rule &rule: e.rules) {
|
|
if (!rule.isEmpty()) {
|
|
str << constRuleKey << '\n';
|
|
Rule::ConstIterator it(rule.constBegin());
|
|
Rule::ConstIterator end(rule.constEnd());
|
|
for (; it!=end; ++it) {
|
|
str << it.key() << constKeyValSep << it.value() << '\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isRemote()) {
|
|
return saveRemote(string, e);
|
|
}
|
|
|
|
QFile f(Utils::dataDir(rulesDir, true)+e.name+constExtension);
|
|
if (f.open(QIODevice::WriteOnly|QIODevice::Text)) {
|
|
QTextStream out(&f);
|
|
out.setCodec("UTF-8");
|
|
out << string;
|
|
updateEntry(e);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RulesPlaylists::updateEntry(const Entry &e)
|
|
{
|
|
QList<RulesPlaylists::Entry>::Iterator it=find(e.name);
|
|
if (it!=entryList.end()) {
|
|
entryList.replace(it-entryList.begin(), e);
|
|
QModelIndex idx=index(it-entryList.begin(), 0, QModelIndex());
|
|
emit dataChanged(idx, idx);
|
|
} else {
|
|
beginInsertRows(QModelIndex(), entryList.count(), entryList.count());
|
|
entryList.append(e);
|
|
endInsertRows();
|
|
}
|
|
}
|
|
|
|
void RulesPlaylists::del(const QString &name)
|
|
{
|
|
QList<RulesPlaylists::Entry>::Iterator it=find(name);
|
|
if (it==entryList.end()) {
|
|
return;
|
|
}
|
|
QString fName(Utils::dataDir(rulesDir, false)+name+constExtension);
|
|
bool isCurrent=currentEntry==name;
|
|
|
|
if (!QFile::exists(fName) || QFile::remove(fName)) {
|
|
if (isCurrent) {
|
|
stop();
|
|
}
|
|
beginRemoveRows(QModelIndex(), it-entryList.begin(), it-entryList.begin());
|
|
entryList.erase(it);
|
|
endRemoveRows();
|
|
return;
|
|
}
|
|
}
|
|
|
|
QList<RulesPlaylists::Entry>::Iterator RulesPlaylists::find(const QString &e)
|
|
{
|
|
QList<RulesPlaylists::Entry>::Iterator it(entryList.begin());
|
|
QList<RulesPlaylists::Entry>::Iterator end(entryList.end());
|
|
|
|
for (; it!=end; ++it) {
|
|
if ((*it).name==e) {
|
|
break;
|
|
}
|
|
}
|
|
return it;
|
|
}
|
|
|
|
void RulesPlaylists::loadLocal()
|
|
{
|
|
beginResetModel();
|
|
entryList.clear();
|
|
currentEntry=QString();
|
|
|
|
// Load all current enttries...
|
|
QString dirName=Utils::dataDir(rulesDir);
|
|
QDir d(dirName);
|
|
if (d.exists()) {
|
|
QStringList rulesFiles=d.entryList(QStringList() << QChar('*')+constExtension);
|
|
for (const QString &rf: rulesFiles) {
|
|
QFile f(dirName+rf);
|
|
if (f.open(QIODevice::ReadOnly|QIODevice::Text)) {
|
|
QStringList keys=QStringList() << constArtistKey << constSimilarArtistsKey << constAlbumArtistKey << constDateKey
|
|
<< constExactKey << constAlbumKey << constTitleKey << constGenreKey << constFileKey << constExcludeKey;
|
|
|
|
Entry e;
|
|
e.name=rf.left(rf.length()-constExtension.length());
|
|
e.numTracks=defaultNumTracks();
|
|
Rule r;
|
|
QTextStream in(&f);
|
|
in.setCodec("UTF-8");
|
|
QStringList lines = in.readAll().split('\n', CANTATA_SKIP_EMPTY);
|
|
for (const QString &line: lines) {
|
|
QString str=line.trimmed();
|
|
|
|
if (str.isEmpty() || str.startsWith('#')) {
|
|
continue;
|
|
}
|
|
|
|
if (str==constRuleKey) {
|
|
if (!r.isEmpty()) {
|
|
e.rules.append(r);
|
|
r.clear();
|
|
}
|
|
} else if (str.startsWith(constRatingKey+constKeyValSep)) {
|
|
QStringList vals=str.mid(constRatingKey.length()+1).split(constRangeSep);
|
|
if (2==vals.count()) {
|
|
e.ratingFrom=vals.at(0).toUInt();
|
|
e.ratingTo=vals.at(1).toUInt();
|
|
}
|
|
} else if (str.startsWith(constIncludeUnratedKey+constKeyValSep)) {
|
|
e.includeUnrated="true"==str.mid(constIncludeUnratedKey.length()+1);
|
|
} else if (str.startsWith(constDurationKey+constKeyValSep)) {
|
|
QStringList vals=str.mid(constDurationKey.length()+1).split(constRangeSep);
|
|
if (2==vals.count()) {
|
|
e.minDuration=vals.at(0).toUInt();
|
|
e.maxDuration=vals.at(1).toUInt();
|
|
}
|
|
} else if (str.startsWith(constOrderKey+constKeyValSep)) {
|
|
e.order=toOrder(str.mid(constOrderKey.length()+1));
|
|
} else if (str.startsWith(constOrderAscendingKey+constKeyValSep)) {
|
|
e.orderAscending="true"==str.mid(constOrderAscendingKey.length()+1);
|
|
} else if (str.startsWith(constNumTracksKey+constKeyValSep)) {
|
|
e.numTracks=str.mid(constNumTracksKey.length()+1).toUInt();
|
|
} else if (str.startsWith(constMaxAgeKey+constKeyValSep)) {
|
|
e.maxAge=str.mid(constMaxAgeKey.length()+1).toUInt();
|
|
} else {
|
|
for (const QString &k: keys) {
|
|
if (str.startsWith(k+constKeyValSep)) {
|
|
r.insert(k, str.mid(k.length()+1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!r.isEmpty()) {
|
|
e.rules.append(r);
|
|
r.clear();
|
|
}
|
|
entryList.append(e);
|
|
}
|
|
}
|
|
}
|
|
endResetModel();
|
|
}
|
|
|
|
#include "moc_rulesplaylists.cpp"
|