SongFilter.cxx 3.88 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2012 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"
22
#include "song.h"
23 24
#include "tag.h"

25 26
#include <glib.h>

27
#include <assert.h>
28 29
#include <stdlib.h>

30 31
#define LOCATE_TAG_FILE_KEY     "file"
#define LOCATE_TAG_FILE_KEY_OLD "filename"
32 33
#define LOCATE_TAG_ANY_KEY      "any"

34
unsigned
Max Kellermann's avatar
Max Kellermann committed
35
locate_parse_type(const char *str)
36
{
37 38
	if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY) ||
	    0 == g_ascii_strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD))
39 40
		return LOCATE_TAG_FILE_TYPE;

41
	if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
42 43
		return LOCATE_TAG_ANY_TYPE;

44
	return tag_name_parse_i(str);
45 46
}

47 48 49 50 51
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
	:tag(_tag), fold_case(_fold_case),
	 value(fold_case
	       ? g_utf8_casefold(_value, -1)
	       : g_strdup(_value))
52
{
53 54
}

55
SongFilter::Item::~Item()
56
{
57
	g_free(value);
58 59
}

60 61
bool
SongFilter::Item::StringMatch(const char *s) const
62
{
63 64
	assert(value != nullptr);
	assert(s != nullptr);
Eric Wong's avatar
Eric Wong committed
65

66 67 68
	if (fold_case) {
		char *p = g_utf8_casefold(s, -1);
		const bool result = strstr(p, value) != NULL;
69
		g_free(p);
70 71
		return result;
	} else {
72
		return strcmp(s, value) == 0;
73
	}
74
}
75

76 77
bool
SongFilter::Item::Match(const tag_item &item) const
78
{
79 80 81
	return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) &&
		StringMatch(item.value);
}
82

83 84 85
bool
SongFilter::Item::Match(const struct tag &_tag) const
{
86
	bool visited_types[TAG_NUM_OF_ITEM_TYPES];
87
	std::fill(visited_types, visited_types + TAG_NUM_OF_ITEM_TYPES, false);
88

89 90
	for (unsigned i = 0; i < _tag.num_items; i++) {
		visited_types[_tag.items[i]->type] = true;
91

92
		if (Match(*_tag.items[i]))
93
			return true;
94 95
	}

96 97 98 99
	/** 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 searched string is also
	 *  empty (first char is a \0), then it's a match as well and
100
	 *  we should return true.
101
	 */
102 103
	if (*value == 0 && tag < TAG_NUM_OF_ITEM_TYPES &&
	    !visited_types[tag])
104
		return true;
105

106
	return false;
107 108
}

109 110
bool
SongFilter::Item::Match(const song &song) const
111
{
112 113 114
	if (tag == LOCATE_TAG_FILE_TYPE || tag == LOCATE_TAG_ANY_TYPE) {
		char *uri = song_get_uri(&song);
		const bool result = StringMatch(uri);
115 116
		g_free(uri);

117
		if (result || tag == LOCATE_TAG_FILE_TYPE)
118
			return result;
119 120
	}

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
	return song.tag != NULL && Match(*song.tag);
}

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;

	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;
156 157
}

158
bool
159
SongFilter::Match(const song &song) const
160
{
161 162
	for (const auto &i : items)
		if (!i.Match(song))
163
			return false;
164

165
	return true;
166
}