testsuite: Introduce a new test for para_server.
[paraslash.git] / mm.c
1 /*
2 * Copyright (C) 2007-2011 Andre Noll <maan@systemlinux.org>
3 *
4 * Licensed under the GPL v2. For licencing details see COPYING.
5 */
6
7 /** \file mm.c Paraslash's mood methods. */
8
9 #include <time.h>
10 #include <regex.h>
11 #include <fnmatch.h>
12 #include <osl.h>
13
14 #include "para.h"
15 #include "error.h"
16 #include "string.h"
17 #include "afh.h"
18 #include "afs.h"
19 #include "mm.h"
20
21 #define MOOD_COMPARATORS \
22 MC(LESS, <) \
23 MC(LESS_OR_EQUAL, <=) \
24 MC(EQUAL, =) \
25 MC(EQUAL2, ==) \
26 MC(NOT_EQUAL, !=) \
27 MC(NOT_EQUAL2, <>) \
28 MC(GREATER, >) \
29 MC(GREATER_OR_EQUAL, >=) \
30
31 #define MC(a, b) MC_ ## a,
32 enum mood_comparator_id {MOOD_COMPARATORS NUM_MOOD_COMPARATORS};
33 #undef MC
34 #define MC(a, b) # b,
35 static const char *mood_comparators[] = {MOOD_COMPARATORS};
36 #undef MC
37
38 static int parse_mood_comparator(const char *word)
39 {
40 int i;
41
42 for (i = 0; i < NUM_MOOD_COMPARATORS; i++)
43 if (!strcmp(word, mood_comparators[i]))
44 return i;
45 return -E_MOOD_SYNTAX;
46 }
47
48 struct mm_compare_num_data {
49 /** <, <=, =, !=, >=, or >. */
50 enum mood_comparator_id id;
51 /** The value given at the mood line. */
52 int32_t arg;
53 };
54
55 static int mm_compare_num_score_function(int32_t val,
56 const struct mm_compare_num_data *cnd)
57 {
58 int res;
59 int32_t arg = cnd->arg;
60
61 switch (cnd->id) {
62 case MC_LESS:
63 res = val < arg; break;
64 case MC_LESS_OR_EQUAL:
65 res = val <= arg; break;
66 case MC_EQUAL:
67 case MC_EQUAL2:
68 res = val == arg; break;
69 case MC_NOT_EQUAL:
70 case MC_NOT_EQUAL2:
71 res = val != arg; break;
72 case MC_GREATER:
73 res = val > arg; break;
74 case MC_GREATER_OR_EQUAL:
75 res = val >= arg; break;
76 default:
77 PARA_EMERG_LOG("BUG: invalid mood comparator\n");
78 exit(EXIT_FAILURE);
79 }
80 return res? 100 : -100;
81 }
82
83 static int mm_compare_num_parser(int argc, char **argv, void **private)
84 {
85 int ret;
86 enum mood_comparator_id id;
87 int32_t arg;
88 struct mm_compare_num_data *cnd;
89 if (argc != 2)
90 return -E_MOOD_SYNTAX;
91 ret = parse_mood_comparator(argv[1]);
92 if (ret < 0)
93 return ret;
94 id = ret;
95 ret = para_atoi32(argv[2], &arg);
96 if (ret < 0)
97 return ret;
98 cnd = para_malloc(sizeof(struct mm_compare_num_data));
99 cnd->id = id;
100 cnd->arg = arg;
101 *private = cnd;
102 return 1;
103 }
104
105 static int mm_regex_parser(int argc, char **argv, void **private)
106 {
107 regex_t *preg;
108 int ret;
109
110 if (argc != 1)
111 return -E_MOOD_SYNTAX;
112 preg = para_malloc(sizeof(*preg));
113 ret = para_regcomp(preg, argv[1], REG_EXTENDED | REG_NOSUB);
114 if (ret < 0) {
115 free(preg);
116 return ret;
117 }
118 *private = preg;
119 return 1;
120 }
121
122 static int mm_regex_score_function(const regex_t *preg, const char *pattern)
123 {
124 return regexec(preg, pattern, 0, NULL, 0) == 0? 100 : -100;
125 }
126
127 static void mm_regex_cleanup(void *private)
128 {
129 regex_t *preg = private;
130 regfree(preg);
131 free(preg);
132 }
133
134 static int mm_artist_matches_score_function(__a_unused const char *path,
135 __a_unused const struct afs_info *afsi,
136 const struct afh_info *afhi,
137 const void *private)
138 {
139 return mm_regex_score_function(private, afhi->tags.artist);
140 }
141
142 static int mm_title_matches_score_function(__a_unused const char *path,
143 __a_unused const struct afs_info *afsi,
144 const struct afh_info *afhi,
145 const void *private)
146 {
147 return mm_regex_score_function(private, afhi->tags.title);
148 }
149
150 static int mm_album_matches_score_function(__a_unused const char *path,
151 __a_unused const struct afs_info *afsi,
152 const struct afh_info *afhi,
153 const void *private)
154 {
155 return mm_regex_score_function(private, afhi->tags.album);
156 }
157
158 static int mm_comment_matches_score_function(__a_unused const char *path,
159 __a_unused const struct afs_info *afsi,
160 const struct afh_info *afhi,
161 const void *private)
162 {
163 return mm_regex_score_function(private, afhi->tags.comment);
164 }
165
166 static int mm_bitrate_score_function(__a_unused const char *path,
167 __a_unused const struct afs_info *afsi,
168 const struct afh_info *afhi,
169 const void *private)
170 {
171 return mm_compare_num_score_function(afhi->bitrate, private);
172 }
173
174 static int mm_frequency_score_function(__a_unused const char *path,
175 __a_unused const struct afs_info *afsi,
176 const struct afh_info *afhi,
177 const void *private)
178 {
179 return mm_compare_num_score_function(afhi->frequency, private);
180 }
181
182 static int mm_channels_score_function(__a_unused const char *path,
183 __a_unused const struct afs_info *afsi,
184 const struct afh_info *afhi,
185 const void *private)
186 {
187 return mm_compare_num_score_function(afhi->channels, private);
188 }
189
190 static int mm_num_played_score_function(__a_unused const char *path,
191 const struct afs_info *afsi,
192 __a_unused const struct afh_info *afhi,
193 const void *private)
194 {
195 return mm_compare_num_score_function(afsi->num_played, private);
196 }
197
198 struct mm_year_data {
199 /** Comparator and year given at the mood line. */
200 struct mm_compare_num_data *cnd;
201 /** Used to detect Y2K issues. */
202 int32_t current_year;
203 };
204
205 static int mm_year_parser(int argc, char **argv, void **private)
206 {
207 int ret = -E_MOOD_SYNTAX;
208 struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd));
209 time_t current_time;
210 struct tm *gmt;
211
212 ret = mm_compare_num_parser(argc, argv, (void **)&mmyd->cnd);
213 if (ret < 0)
214 goto err;
215 current_time = time(NULL);
216 gmt = gmtime(&current_time);
217 /* tm_year is the number of years since 1900 */
218 mmyd->current_year = gmt->tm_year + 1900;
219 *private = mmyd;
220 return 1;
221 err:
222 free(mmyd);
223 return ret;
224 }
225
226 static int mm_year_score_function(__a_unused const char *path,
227 __a_unused const struct afs_info *afsi,
228 const struct afh_info *afhi,
229 const void *private)
230 {
231 const struct mm_year_data *mmyd = private;
232 int32_t tag_year;
233 int ret = para_atoi32(afhi->tags.year, &tag_year);
234
235 if (ret < 0) /* year tag not present or not a number */
236 return -100;
237 if (tag_year < 0)
238 return -100;
239 /* try to work around Y2K issues */
240 if (tag_year < 100) {
241 tag_year += 1900;
242 if (tag_year + 100 <= mmyd->current_year)
243 tag_year += 100; /* assume tag_year >= 2000 */
244 }
245 return mm_compare_num_score_function(tag_year, mmyd->cnd);
246 }
247
248 static void mm_year_cleanup(void *private)
249 {
250 struct mm_year_data *mmyd = private;
251
252 free(mmyd->cnd);
253 free(mmyd);
254 }
255
256 static int mm_no_attributes_set_parser(int argc, __a_unused char **argv,
257 __a_unused void **ignored)
258 {
259 return argc? -E_MOOD_SYNTAX : 1;
260 }
261
262 static int mm_no_attributes_set_score_function(__a_unused const char *path,
263 const struct afs_info *afsi,
264 __a_unused const struct afh_info *afhi,
265 __a_unused const void *data)
266 {
267 if (!afsi->attributes)
268 return 100;
269 return -100;
270 }
271
272 static int mm_path_matches_score_function(const char *path,
273 __a_unused const struct afs_info *afsi,
274 __a_unused const struct afh_info *afhi,
275 const void *data)
276 {
277 if (fnmatch(data, path, 0))
278 return -100;
279 return 100;
280 }
281
282 static int mm_path_matches_parser(int argc, char **argv, void **data)
283 {
284 if (argc != 1)
285 return -E_MOOD_SYNTAX;
286 *data = para_strdup(argv[1]);
287 return 1;
288 }
289
290 static void mm_path_matches_cleanup(void *data)
291 {
292 free(data);
293 }
294
295 static int mm_is_set_parser(int argc, char **argv, void **bitnum)
296 {
297 int ret;
298 unsigned char c, *res;
299
300 if (argc != 1)
301 return -E_MOOD_SYNTAX;
302 ret = get_attribute_bitnum_by_name(argv[1], &c);
303 if (ret < 0)
304 return ret;
305 res = para_malloc(1);
306 *res = c;
307 *bitnum = res;
308 return 1;
309 }
310
311 static int mm_is_set_score_function(__a_unused const char *path,
312 __a_unused const struct afs_info *afsi,
313 __a_unused const struct afh_info *afhi,
314 const void *data)
315 {
316 const unsigned char *bn = data;
317 if (afsi->attributes & (1ULL << *bn))
318 return 100;
319 return -100;
320 }
321
322 #define DEFINE_MOOD_METHOD(_name) \
323 .parser = mm_ ## _name ## _parser, \
324 .score_function = mm_ ## _name ## _score_function, \
325 .name = #_name
326
327 #define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \
328 DEFINE_MOOD_METHOD(_name), \
329 .cleanup = mm_ ## _name ## _cleanup
330
331 #define DEFINE_REGEX_MOOD_METHOD(_name) \
332 .name = #_name, \
333 .parser = mm_regex_parser, \
334 .score_function = mm_ ## _name ## _score_function, \
335 .cleanup = mm_regex_cleanup
336
337 #define DEFINE_COMPARE_NUM_MOOD_METHOD(_name) \
338 .name = #_name, \
339 .parser = mm_compare_num_parser, \
340 .score_function = mm_ ## _name ## _score_function
341
342 const struct mood_method mood_methods[] = {
343 {DEFINE_MOOD_METHOD(no_attributes_set)},
344 {DEFINE_MOOD_METHOD(is_set)},
345 {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)},
346 {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)},
347 {DEFINE_REGEX_MOOD_METHOD(artist_matches)},
348 {DEFINE_REGEX_MOOD_METHOD(title_matches)},
349 {DEFINE_REGEX_MOOD_METHOD(album_matches)},
350 {DEFINE_REGEX_MOOD_METHOD(comment_matches)},
351 {DEFINE_COMPARE_NUM_MOOD_METHOD(bitrate)},
352 {DEFINE_COMPARE_NUM_MOOD_METHOD(frequency)},
353 {DEFINE_COMPARE_NUM_MOOD_METHOD(channels)},
354 {DEFINE_COMPARE_NUM_MOOD_METHOD(num_played)},
355 {.parser = NULL}
356 };