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