SongFilter.cxx 5.45 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "config.h"
21
#include "SongFilter.hxx"
Max Kellermann's avatar
Max Kellermann committed
22 23
#include "db/Song.hxx"
#include "db/LightSong.hxx"
24
#include "DetachedSong.hxx"
25
#include "tag/Tag.hxx"
26
#include "util/ASCII.hxx"
27
#include "util/UriUtil.hxx"
28

29
#ifdef HAVE_GLIB
30
#include <glib.h>
31
#endif
32

33
#include <assert.h>
Max Kellermann's avatar
Max Kellermann committed
34
#include <string.h>
35 36
#include <stdlib.h>

37 38
#define LOCATE_TAG_FILE_KEY     "file"
#define LOCATE_TAG_FILE_KEY_OLD "filename"
39 40
#define LOCATE_TAG_ANY_KEY      "any"

41
unsigned
Max Kellermann's avatar
Max Kellermann committed
42
locate_parse_type(const char *str)
43
{
44 45
	if (StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY) ||
	    StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY_OLD))
46 47
		return LOCATE_TAG_FILE_TYPE;

48
	if (StringEqualsCaseASCII(str, LOCATE_TAG_ANY_KEY))
49 50
		return LOCATE_TAG_ANY_TYPE;

51 52 53
	if (strcmp(str, "base") == 0)
		return LOCATE_TAG_BASE_TYPE;

54
	return tag_name_parse_i(str);
55 56
}

57 58 59
gcc_pure
static std::string
CaseFold(const char *p)
60
{
61
#ifdef HAVE_GLIB
62 63 64 65
	char *q = g_utf8_casefold(p, -1);
	std::string result(q);
	g_free(q);
	return result;
66 67 68 69
#else
	// TODO: implement without GLib
	return p;
#endif
70 71
}

72 73 74 75 76 77 78 79 80 81 82 83
gcc_pure
static std::string
ImportString(const char *p, bool fold_case)
{
	return fold_case
		? CaseFold(p)
		: std::string(p);
}

SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
	:tag(_tag), fold_case(_fold_case),
	 value(ImportString(_value, _fold_case))
84
{
85 86
}

87 88
bool
SongFilter::Item::StringMatch(const char *s) const
89
{
90
	assert(s != nullptr);
Eric Wong's avatar
Eric Wong committed
91

92
	if (fold_case) {
93
#ifdef HAVE_GLIB
94
		char *p = g_utf8_casefold(s, -1);
95 96 97 98
#else
		// TODO: implement without GLib
		const char *p = s;
#endif
99
		const bool result = strstr(p, value.c_str()) != NULL;
100
#ifdef HAVE_GLIB
101
		g_free(p);
102
#endif
103 104
		return result;
	} else {
105
		return s == value;
106
	}
107
}
108

109
bool
Max Kellermann's avatar
Max Kellermann committed
110
SongFilter::Item::Match(const TagItem &item) const
111
{
112 113 114
	return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) &&
		StringMatch(item.value);
}
115

116
bool
Max Kellermann's avatar
Max Kellermann committed
117
SongFilter::Item::Match(const Tag &_tag) const
118
{
119
	bool visited_types[TAG_NUM_OF_ITEM_TYPES];
120
	std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
121

122 123
	for (unsigned i = 0; i < _tag.num_items; i++) {
		visited_types[_tag.items[i]->type] = true;
124

125
		if (Match(*_tag.items[i]))
126
			return true;
127 128
	}

129 130 131 132
	if (tag < TAG_NUM_OF_ITEM_TYPES && !visited_types[tag]) {
		/* If the search critieron was not visited during the
		   sweep through the song's tag, it means this field
		   is absent from the tag or empty. Thus, if the
133
		   searched string is also empty
134 135
		   then it's a match as well and we should return
		   true. */
136
		if (value.empty())
137 138 139 140 141 142 143 144 145 146 147 148 149
			return true;

		if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
			/* if we're looking for "album artist", but
			   only "artist" exists, use that */
			for (unsigned i = 0; i < _tag.num_items; i++) {
				const TagItem &item = *_tag.items[i];
				if (item.type == TAG_ARTIST &&
				    StringMatch(item.value))
					return true;
			}
		}
	}
150

151
	return false;
152 153
}

154
bool
155 156 157 158 159 160 161 162 163 164 165 166 167
SongFilter::Item::Match(const DetachedSong &song) const
{
	if (tag == LOCATE_TAG_BASE_TYPE)
		return uri_is_child_or_same(value.c_str(), song.GetURI());

	if (tag == LOCATE_TAG_FILE_TYPE)
		return StringMatch(song.GetURI());

	return Match(song.GetTag());
}

bool
SongFilter::Item::Match(const LightSong &song) const
168
{
169 170 171 172 173
	if (tag == LOCATE_TAG_BASE_TYPE) {
		const auto uri = song.GetURI();
		return uri_is_child_or_same(value.c_str(), uri.c_str());
	}

174
	if (tag == LOCATE_TAG_FILE_TYPE) {
175
		const auto uri = song.GetURI();
176
		return StringMatch(uri.c_str());
177 178
	}

179
	return Match(*song.tag);
180 181
}

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
{
	items.push_back(Item(tag, value, fold_case));
}

SongFilter::~SongFilter()
{
	/* this destructor exists here just so it won't get inlined */
}

bool
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{
	unsigned tag = locate_parse_type(tag_string);
	if (tag == TAG_NUM_OF_ITEM_TYPES)
		return false;

199 200 201 202 203 204 205 206
	if (tag == LOCATE_TAG_BASE_TYPE) {
		if (!uri_safe_local(value))
			return false;

		/* case folding doesn't work with "base" */
		fold_case = false;
	}

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	items.push_back(Item(tag, value, fold_case));
	return true;
}

bool
SongFilter::Parse(unsigned argc, char *argv[], bool fold_case)
{
	if (argc == 0 || argc % 2 != 0)
		return false;

	for (unsigned i = 0; i < argc; i += 2)
		if (!Parse(argv[i], argv[i + 1], fold_case))
			return false;

	return true;
222 223
}

224
bool
225
SongFilter::Match(const DetachedSong &song) const
226
{
227 228
	for (const auto &i : items)
		if (!i.Match(song))
229
			return false;
230

231
	return true;
232
}
233

234
bool
235
SongFilter::Match(const LightSong &song) const
236 237 238 239 240 241 242 243
{
	for (const auto &i : items)
		if (!i.Match(song))
			return false;

	return true;
}

244 245 246 247 248 249 250 251 252
std::string
SongFilter::GetBase() const
{
	for (const auto &i : items)
		if (i.GetTag() == LOCATE_TAG_BASE_TYPE)
			return i.GetValue();

	return std::string();
}