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