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