2010-10-25 01:46:05 +02:00
|
|
|
/* This file is part of Clementine.
|
2010-11-20 14:27:10 +01:00
|
|
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
2010-10-25 01:46:05 +02:00
|
|
|
|
|
|
|
Clementine 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 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
#include "searchterm.h"
|
2010-10-25 01:46:05 +02:00
|
|
|
#include "playlist/playlist.h"
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
namespace smart_playlists {
|
|
|
|
|
|
|
|
SearchTerm::SearchTerm()
|
2010-11-17 21:21:04 +01:00
|
|
|
: field_(Field_Title),
|
|
|
|
operator_(Op_Equals)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
SearchTerm::SearchTerm(
|
2010-11-17 21:21:04 +01:00
|
|
|
Field field, Operator op, const QVariant& value)
|
|
|
|
: field_(field),
|
|
|
|
operator_(op),
|
|
|
|
value_(value)
|
2010-10-25 01:46:05 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
QString SearchTerm::ToSql() const {
|
2011-01-20 19:35:38 +01:00
|
|
|
QString col = FieldColumnName(field_);
|
2011-04-24 19:52:16 +02:00
|
|
|
QString date = DateName(date_, true);
|
2010-10-25 01:46:05 +02:00
|
|
|
QString value = value_.toString();
|
|
|
|
value.replace('\'', "''");
|
|
|
|
|
2011-04-24 19:52:16 +02:00
|
|
|
QString second_value;
|
|
|
|
|
2011-05-04 20:47:48 +02:00
|
|
|
bool special_date_query = (operator_ == SearchTerm::Op_NumericDate || operator_ == SearchTerm::Op_NumericDateNot ||
|
|
|
|
operator_ == SearchTerm::Op_RelativeDate);
|
2011-04-24 19:52:16 +02:00
|
|
|
|
2011-01-20 19:35:38 +01:00
|
|
|
// Floating point problems...
|
|
|
|
// Theoretically 0.0 == 0 stars, 0.1 == 0.5 star, 0.2 == 1 star etc.
|
|
|
|
// but in reality we need to consider anything from [0.05, 0.15) range
|
|
|
|
// to be 0.5 star etc.
|
|
|
|
// To make this simple, I transform the ranges to integeres and then
|
|
|
|
// operate on ints: [0.0, 0.05) -> 0, [0.05, 0.15) -> 1 etc.
|
|
|
|
if (TypeOf(field_) == Type_Rating) {
|
|
|
|
col = "CAST ((" + col + " + 0.05) * 10 AS INTEGER)";
|
|
|
|
value = "CAST ((" + value + " + 0.05) * 10 AS INTEGER)";
|
2011-01-20 19:55:28 +01:00
|
|
|
} else if (TypeOf(field_) == Type_Date) {
|
2011-04-24 19:52:16 +02:00
|
|
|
if (!special_date_query) {
|
|
|
|
// We have the exact date
|
|
|
|
// The calendar widget specifies no time so ditch the possible time part
|
|
|
|
// from integers representing the dates.
|
|
|
|
col = "DATE(" + col + ", 'unixepoch', 'localtime')";
|
|
|
|
value = "DATE(" + value + ", 'unixepoch', 'localtime')";
|
|
|
|
} else {
|
|
|
|
// We have a numeric date, consider also the time for more precision
|
|
|
|
col = "DATETIME(" + col + ", 'unixepoch', 'localtime')";
|
|
|
|
second_value = second_value_.toString();
|
|
|
|
second_value.replace('\'', "''");
|
|
|
|
if (date == "weeks") {
|
|
|
|
// Sqlite doesn't know weeks, transform them to days
|
|
|
|
date = "days";
|
|
|
|
value = QString::number(value_.toInt()*7);
|
|
|
|
second_value = QString::number(second_value_.toInt()*7);
|
|
|
|
}
|
|
|
|
}
|
2011-04-24 21:07:01 +02:00
|
|
|
} else if (TypeOf(field_) == Type_Time) {
|
|
|
|
// Convert seconds to nanoseconds
|
|
|
|
value = "CAST (" + value + " *1000000000 AS INTEGER)";
|
2011-01-20 19:35:38 +01:00
|
|
|
}
|
|
|
|
|
2010-10-25 01:46:05 +02:00
|
|
|
switch (operator_) {
|
|
|
|
case Op_Contains:
|
|
|
|
return col + " LIKE '%" + value + "%'";
|
|
|
|
case Op_NotContains:
|
|
|
|
return col + " NOT LIKE '%" + value + "%'";
|
|
|
|
case Op_StartsWith:
|
|
|
|
return col + " LIKE '" + value + "%'";
|
|
|
|
case Op_EndsWith:
|
|
|
|
return col + " LIKE '%" + value + "'";
|
|
|
|
case Op_Equals:
|
2010-10-29 21:26:37 +02:00
|
|
|
if (TypeOf(field_) == Type_Text)
|
|
|
|
return col + " LIKE '" + value + "'";
|
2011-01-20 19:55:28 +01:00
|
|
|
else if (TypeOf(field_) == Type_Rating ||
|
2011-04-24 21:07:01 +02:00
|
|
|
TypeOf(field_) == Type_Date ||
|
|
|
|
TypeOf(field_) == Type_Time)
|
2011-01-20 19:35:38 +01:00
|
|
|
return col + " = " + value + "";
|
|
|
|
else
|
|
|
|
return col + " = '" + value + "'";
|
2010-10-25 01:46:05 +02:00
|
|
|
case Op_GreaterThan:
|
2011-01-20 19:55:28 +01:00
|
|
|
if (TypeOf(field_) == Type_Rating ||
|
2011-04-24 21:07:01 +02:00
|
|
|
TypeOf(field_) == Type_Date ||
|
|
|
|
TypeOf(field_) == Type_Time)
|
2011-01-20 19:35:38 +01:00
|
|
|
return col + " > " + value + "";
|
|
|
|
else
|
|
|
|
return col + " > '" + value + "'";
|
2010-10-25 01:46:05 +02:00
|
|
|
case Op_LessThan:
|
2011-01-20 19:55:28 +01:00
|
|
|
if (TypeOf(field_) == Type_Rating ||
|
2011-04-24 21:07:01 +02:00
|
|
|
TypeOf(field_) == Type_Date ||
|
|
|
|
TypeOf(field_) == Type_Time)
|
2011-01-20 19:35:38 +01:00
|
|
|
return col + " < " + value + "";
|
|
|
|
else
|
|
|
|
return col + " < '" + value + "'";
|
2011-04-24 19:52:16 +02:00
|
|
|
case Op_NumericDate:
|
|
|
|
return col + " > " + "DATETIME('now', '-" + value + " " + date +"', 'localtime')";
|
2011-05-04 20:47:48 +02:00
|
|
|
case Op_NumericDateNot:
|
|
|
|
return col + " < " + "DATETIME('now', '-" + value + " " + date +"', 'localtime')";
|
2011-04-24 19:52:16 +02:00
|
|
|
case Op_RelativeDate:
|
|
|
|
// Consider the time range before the first date but after the second one
|
|
|
|
return "(" + col + " < " + "DATETIME('now', '-" + value + " " + date +"', 'localtime') AND " +
|
|
|
|
col + " > " + "DATETIME('now', '-" + second_value + " " + date +"', 'localtime'))";
|
2011-05-04 20:47:48 +02:00
|
|
|
case Op_NotEquals:
|
|
|
|
return col + " <> " + value + "";
|
2010-10-25 01:46:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
bool SearchTerm::is_valid() const {
|
2011-04-24 19:52:16 +02:00
|
|
|
// We can accept also a zero value in these cases
|
|
|
|
if (operator_ == SearchTerm::Op_NumericDate) {
|
|
|
|
return value_.toInt() >= 0;
|
|
|
|
} else if (operator_ == SearchTerm::Op_RelativeDate) {
|
|
|
|
return (value_.toInt() >= 0 && value_.toInt() < second_value_.toInt());
|
|
|
|
}
|
|
|
|
|
2010-10-29 20:41:49 +02:00
|
|
|
switch (TypeOf(field_)) {
|
|
|
|
case Type_Text: return !value_.toString().isEmpty();
|
|
|
|
case Type_Date: return value_.toInt() != 0;
|
|
|
|
case Type_Number: return value_.toInt() >= 0;
|
|
|
|
case Type_Rating: return value_.toFloat() >= 0.0;
|
|
|
|
case Type_Time: return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-11-20 13:20:26 +01:00
|
|
|
bool SearchTerm::operator ==(const SearchTerm& other) const {
|
|
|
|
return field_ == other.field_ &&
|
|
|
|
operator_ == other.operator_ &&
|
2011-04-24 19:52:16 +02:00
|
|
|
value_ == other.value_ &&
|
|
|
|
date_ == other.date_ &&
|
|
|
|
second_value_ == other.second_value_;
|
2010-11-20 13:20:26 +01:00
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
SearchTerm::Type SearchTerm::TypeOf(Field field) {
|
2010-10-25 01:46:05 +02:00
|
|
|
switch (field) {
|
|
|
|
case Field_Length:
|
|
|
|
return Type_Time;
|
|
|
|
|
|
|
|
case Field_Track:
|
|
|
|
case Field_Disc:
|
|
|
|
case Field_Year:
|
|
|
|
case Field_BPM:
|
|
|
|
case Field_Bitrate:
|
|
|
|
case Field_Samplerate:
|
|
|
|
case Field_Filesize:
|
|
|
|
case Field_PlayCount:
|
|
|
|
case Field_SkipCount:
|
2010-11-17 21:21:04 +01:00
|
|
|
case Field_Score:
|
2010-10-25 01:46:05 +02:00
|
|
|
return Type_Number;
|
|
|
|
|
|
|
|
case Field_LastPlayed:
|
|
|
|
case Field_DateCreated:
|
|
|
|
case Field_DateModified:
|
|
|
|
return Type_Date;
|
|
|
|
|
|
|
|
case Field_Rating:
|
|
|
|
return Type_Rating;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return Type_Text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
OperatorList SearchTerm::OperatorsForType(Type type) {
|
2010-10-25 01:46:05 +02:00
|
|
|
switch (type) {
|
|
|
|
case Type_Text:
|
|
|
|
return OperatorList() << Op_Contains << Op_NotContains << Op_Equals
|
|
|
|
<< Op_StartsWith << Op_EndsWith;
|
2011-04-24 19:52:16 +02:00
|
|
|
case Type_Date:
|
2011-05-04 20:47:48 +02:00
|
|
|
return OperatorList() << Op_Equals << Op_NotEquals << Op_GreaterThan << Op_LessThan
|
|
|
|
<< Op_NumericDate << Op_NumericDateNot << Op_RelativeDate;
|
2010-10-25 01:46:05 +02:00
|
|
|
default:
|
2011-05-04 20:47:48 +02:00
|
|
|
return OperatorList() << Op_Equals << Op_NotEquals << Op_GreaterThan << Op_LessThan;
|
2010-10-25 01:46:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
QString SearchTerm::OperatorText(Type type, Operator op) {
|
2010-10-25 01:46:05 +02:00
|
|
|
if (type == Type_Date) {
|
|
|
|
switch (op) {
|
2011-05-04 20:47:48 +02:00
|
|
|
case Op_GreaterThan: return QObject::tr("after");
|
|
|
|
case Op_LessThan: return QObject::tr("before");
|
|
|
|
case Op_Equals: return QObject::tr("on");
|
|
|
|
case Op_NotEquals: return QObject::tr("not on");
|
|
|
|
case Op_NumericDate: return QObject::tr("in the last");
|
|
|
|
case Op_NumericDateNot: return QObject::tr("not in the last");
|
|
|
|
case Op_RelativeDate: return QObject::tr("between");
|
|
|
|
default: return QString();
|
2010-10-25 01:46:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
case Op_Contains: return QObject::tr("contains");
|
|
|
|
case Op_NotContains: return QObject::tr("does not contain");
|
|
|
|
case Op_StartsWith: return QObject::tr("starts with");
|
|
|
|
case Op_EndsWith: return QObject::tr("ends with");
|
|
|
|
case Op_GreaterThan: return QObject::tr("greater than");
|
|
|
|
case Op_LessThan: return QObject::tr("less than");
|
|
|
|
case Op_Equals: return QObject::tr("equals");
|
2011-05-04 20:47:48 +02:00
|
|
|
case Op_NotEquals: return QObject::tr("not equals");
|
2011-04-24 19:52:16 +02:00
|
|
|
default: return QString();
|
2010-10-25 01:46:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
QString SearchTerm::FieldColumnName(Field field) {
|
2010-10-25 01:46:05 +02:00
|
|
|
switch (field) {
|
|
|
|
case Field_Length: return "length";
|
|
|
|
case Field_Track: return "track";
|
|
|
|
case Field_Disc: return "disc";
|
|
|
|
case Field_Year: return "year";
|
|
|
|
case Field_BPM: return "bpm";
|
|
|
|
case Field_Bitrate: return "bitrate";
|
|
|
|
case Field_Samplerate: return "samplerate";
|
|
|
|
case Field_Filesize: return "filesize";
|
|
|
|
case Field_PlayCount: return "playcount";
|
|
|
|
case Field_SkipCount: return "skipcount";
|
|
|
|
case Field_LastPlayed: return "lastplayed";
|
|
|
|
case Field_DateCreated: return "ctime";
|
|
|
|
case Field_DateModified:return "mtime";
|
|
|
|
case Field_Rating: return "rating";
|
2010-11-17 21:21:04 +01:00
|
|
|
case Field_Score: return "score";
|
2010-10-25 01:46:05 +02:00
|
|
|
case Field_Title: return "title";
|
|
|
|
case Field_Artist: return "artist";
|
|
|
|
case Field_Album: return "album";
|
|
|
|
case Field_AlbumArtist: return "albumartist";
|
|
|
|
case Field_Composer: return "composer";
|
|
|
|
case Field_Genre: return "genre";
|
|
|
|
case Field_Comment: return "comment";
|
2010-12-15 16:50:22 +01:00
|
|
|
case Field_Filepath: return "filename";
|
2010-10-25 01:46:05 +02:00
|
|
|
case FieldCount: Q_ASSERT(0);
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
QString SearchTerm::FieldName(Field field) {
|
2010-10-25 01:46:05 +02:00
|
|
|
switch (field) {
|
|
|
|
case Field_Length: return Playlist::column_name(Playlist::Column_Length);
|
|
|
|
case Field_Track: return Playlist::column_name(Playlist::Column_Track);
|
|
|
|
case Field_Disc: return Playlist::column_name(Playlist::Column_Disc);
|
|
|
|
case Field_Year: return Playlist::column_name(Playlist::Column_Year);
|
|
|
|
case Field_BPM: return Playlist::column_name(Playlist::Column_BPM);
|
|
|
|
case Field_Bitrate: return Playlist::column_name(Playlist::Column_Bitrate);
|
|
|
|
case Field_Samplerate: return Playlist::column_name(Playlist::Column_Samplerate);
|
|
|
|
case Field_Filesize: return Playlist::column_name(Playlist::Column_Filesize);
|
|
|
|
case Field_PlayCount: return Playlist::column_name(Playlist::Column_PlayCount);
|
|
|
|
case Field_SkipCount: return Playlist::column_name(Playlist::Column_SkipCount);
|
|
|
|
case Field_LastPlayed: return Playlist::column_name(Playlist::Column_LastPlayed);
|
|
|
|
case Field_DateCreated: return Playlist::column_name(Playlist::Column_DateCreated);
|
|
|
|
case Field_DateModified:return Playlist::column_name(Playlist::Column_DateModified);
|
|
|
|
case Field_Rating: return Playlist::column_name(Playlist::Column_Rating);
|
2010-11-17 21:21:04 +01:00
|
|
|
case Field_Score: return Playlist::column_name(Playlist::Column_Score);
|
2010-10-25 01:46:05 +02:00
|
|
|
case Field_Title: return Playlist::column_name(Playlist::Column_Title);
|
|
|
|
case Field_Artist: return Playlist::column_name(Playlist::Column_Artist);
|
|
|
|
case Field_Album: return Playlist::column_name(Playlist::Column_Album);
|
|
|
|
case Field_AlbumArtist: return Playlist::column_name(Playlist::Column_AlbumArtist);
|
|
|
|
case Field_Composer: return Playlist::column_name(Playlist::Column_Composer);
|
|
|
|
case Field_Genre: return Playlist::column_name(Playlist::Column_Genre);
|
|
|
|
case Field_Comment: return QObject::tr("Comment");
|
2010-12-15 16:50:22 +01:00
|
|
|
case Field_Filepath: return Playlist::column_name(Playlist::Column_Filename);
|
2010-10-25 01:46:05 +02:00
|
|
|
case FieldCount: Q_ASSERT(0);
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
QString SearchTerm::FieldSortOrderText(Type type, bool ascending) {
|
2010-11-01 21:24:44 +01:00
|
|
|
switch (type) {
|
|
|
|
case Type_Text: return ascending ? QObject::tr("A-Z") : QObject::tr("Z-A");
|
|
|
|
case Type_Date: return ascending ? QObject::tr("oldest first") : QObject::tr("newest first");
|
|
|
|
case Type_Time: return ascending ? QObject::tr("shortest first") : QObject::tr("longest first");
|
|
|
|
case Type_Number:
|
|
|
|
case Type_Rating: return ascending ? QObject::tr("smallest first") : QObject::tr("biggest first");
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2011-04-24 19:52:16 +02:00
|
|
|
QString SearchTerm::DateName(DateType date, bool forQuery) {
|
|
|
|
// If forQuery is true, untranslated keywords are returned
|
|
|
|
switch (date) {
|
|
|
|
case Date_Hour: return (forQuery ? "hours" : QObject::tr("Hours"));
|
|
|
|
case Date_Day: return (forQuery ? "days" : QObject::tr("Days"));
|
|
|
|
case Date_Week: return (forQuery ? "weeks" : QObject::tr("Weeks"));
|
|
|
|
case Date_Month: return (forQuery ? "months" : QObject::tr("Months"));
|
|
|
|
case Date_Year: return (forQuery ? "years" : QObject::tr("Years"));
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
QDataStream& operator <<(QDataStream& s, const smart_playlists::SearchTerm& term) {
|
2010-10-25 01:46:05 +02:00
|
|
|
s << quint8(term.field_);
|
|
|
|
s << quint8(term.operator_);
|
|
|
|
s << term.value_;
|
2011-04-24 19:52:16 +02:00
|
|
|
s << term.second_value_;
|
|
|
|
s << quint8(term.date_);
|
2010-10-25 01:46:05 +02:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2010-11-18 21:19:33 +01:00
|
|
|
QDataStream& operator >>(QDataStream& s, smart_playlists::SearchTerm& term) {
|
2011-04-24 19:52:16 +02:00
|
|
|
quint8 field, op, date;
|
|
|
|
s >> field >> op >> term.value_ >> term.second_value_ >> date;
|
2010-11-18 21:19:33 +01:00
|
|
|
term.field_ = smart_playlists::SearchTerm::Field(field);
|
|
|
|
term.operator_ = smart_playlists::SearchTerm::Operator(op);
|
2011-04-24 19:52:16 +02:00
|
|
|
term.date_ = smart_playlists::SearchTerm::DateType(date);
|
2010-10-25 01:46:05 +02:00
|
|
|
return s;
|
|
|
|
}
|