4ed72ad861384fa556a1e9a98b06929de6a6d82e
[paraslash.git] / mm.c
1 /*
2  * Copyright (C) 2007-2009 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 const char const *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 static int compare_int32(int32_t a, int32_t b, enum mood_comparator_id id)
49 {
50         int res;
51
52         switch (id) {
53         case MC_LESS:
54                 res = a < b; break;
55         case MC_LESS_OR_EQUAL:
56                 res = a <= b; break;
57         case MC_EQUAL:
58         case MC_EQUAL2:
59                 res = a == b; break;
60         case MC_NOT_EQUAL:
61         case MC_NOT_EQUAL2:
62                 res = a != b; break;
63         case MC_GREATER:
64                 res = a > b; break;
65         case MC_GREATER_OR_EQUAL:
66                 res = a >= b; break;
67         default:
68                 PARA_EMERG_LOG("BUG: invalid mood comparator\n");
69                 exit(EXIT_FAILURE);
70         }
71         return res? 100 : -100;
72 }
73
74 static int mm_regex_parser(int argc, char **argv, void **private)
75 {
76         regex_t *preg;
77         int ret;
78
79         if (argc != 1)
80                 return -E_MOOD_SYNTAX;
81         preg = para_malloc(sizeof(*preg));
82         ret = para_regcomp(preg, argv[1], REG_EXTENDED | REG_NOSUB);
83         if (ret < 0) {
84                 free(preg);
85                 return ret;
86         }
87         *private = preg;
88         return 1;
89 }
90
91 static int mm_regex_score_function(const regex_t *preg, const char *pattern)
92 {
93         return regexec(preg, pattern, 0, NULL, 0) == 0? 100 : -100;
94 }
95
96 static void mm_regex_cleanup(void *private)
97 {
98         regex_t *preg = private;
99         regfree(preg);
100         free(preg);
101 }
102
103 static int mm_artist_matches_score_function(__a_unused const char *path,
104                 __a_unused const struct afs_info *afsi,
105                 const struct afh_info *afhi,
106                 const void *private)
107 {
108         return mm_regex_score_function(private, afhi->tags.artist);
109 }
110
111 static int mm_title_matches_score_function(__a_unused const char *path,
112                 __a_unused const struct afs_info *afsi,
113                 const struct afh_info *afhi,
114                 const void *private)
115 {
116         return mm_regex_score_function(private, afhi->tags.title);
117 }
118
119 static int mm_album_matches_score_function(__a_unused const char *path,
120                 __a_unused const struct afs_info *afsi,
121                 const struct afh_info *afhi,
122                 const void *private)
123 {
124         return mm_regex_score_function(private, afhi->tags.album);
125 }
126
127 static int mm_comment_matches_score_function(__a_unused const char *path,
128                 __a_unused const struct afs_info *afsi,
129                 const struct afh_info *afhi,
130                 const void *private)
131 {
132         return mm_regex_score_function(private, afhi->tags.comment);
133 }
134
135 struct mm_year_data {
136         /** The year given at the mood line. */
137         int32_t year;
138         /** Used to detect Y2K issues. */
139         int32_t current_year;
140         /** <, <=, =, !=, >=, or >. */
141         enum mood_comparator_id id;
142 };
143
144 static int mm_year_parser(int argc, char **argv, void **private)
145 {
146         int ret = -E_MOOD_SYNTAX;
147         struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd));
148         time_t current_time;
149         struct tm *gmt;
150
151         if (argc != 2)
152                 goto err;
153         ret = parse_mood_comparator(argv[1]);
154         mmyd->id = ret;
155         if (ret < 0)
156                 goto err;
157         ret = para_atoi32(argv[2], &mmyd->year);
158         if (ret < 0)
159                 goto err;
160         current_time = time(NULL);
161         gmt = gmtime(&current_time);
162         /* tm_year is the number of years since 1900 */
163         mmyd->current_year = gmt->tm_year + 1900;
164         *private = mmyd;
165         return 1;
166 err:
167         free(mmyd);
168         return ret;
169 }
170
171 static int mm_year_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         const struct mm_year_data *mmyd = private;
177         int32_t tag_year;
178         int ret = para_atoi32(afhi->tags.year, &tag_year);
179
180         if (ret < 0) /* year tag not present or not a number */
181                 return -100;
182         if (tag_year < 0)
183                 return -100;
184         /* try to work around Y2K issues */
185         if (tag_year < 100) {
186                 tag_year += 1900;
187                 if (tag_year + 100 <= mmyd->current_year)
188                         tag_year += 100; /* assume tag_year >= 2000 */
189         }
190         return compare_int32(tag_year, mmyd->year, mmyd->id);
191 }
192
193 static void mm_year_cleanup(void *private)
194 {
195         free(private);
196 }
197
198 static int mm_no_attributes_set_parser(int argc, __a_unused char **argv,
199                 __a_unused void **ignored)
200 {
201         return argc? -E_MOOD_SYNTAX : 1;
202 }
203
204 static int mm_no_attributes_set_score_function(__a_unused const char *path,
205                 const struct afs_info *afsi,
206                 __a_unused const struct afh_info *afhi,
207                 __a_unused const void *data)
208 {
209         if (!afsi->attributes)
210                 return 100;
211         return -100;
212 }
213
214 static int mm_path_matches_score_function(const char *path,
215                 __a_unused const struct afs_info *afsi,
216                 __a_unused const struct afh_info *afhi,
217                 const void *data)
218 {
219         if (fnmatch(data, path, 0))
220                 return -100;
221         return 100;
222 }
223
224 static int mm_path_matches_parser(int argc, char **argv, void **data)
225 {
226         if (argc != 1)
227                 return -E_MOOD_SYNTAX;
228         *data = para_strdup(argv[1]);
229         return 1;
230 }
231
232 static void mm_path_matches_cleanup(void *data)
233 {
234         free(data);
235 }
236
237 static int mm_is_set_parser(int argc, char **argv, void **bitnum)
238 {
239         int ret;
240         unsigned char c, *res;
241
242         if (argc != 1)
243                 return -E_MOOD_SYNTAX;
244         ret = get_attribute_bitnum_by_name(argv[1], &c);
245         if (ret < 0)
246                 return ret;
247         res = para_malloc(1);
248         *res = c;
249         *bitnum = res;
250         return 1;
251 }
252
253 static int mm_is_set_score_function(__a_unused const char *path,
254                 __a_unused const struct afs_info *afsi,
255                 __a_unused const struct afh_info *afhi,
256                 const void *data)
257 {
258         const unsigned char *bn = data;
259         if (afsi->attributes & (1ULL << *bn))
260                 return 100;
261         return -100;
262 }
263
264 #define DEFINE_MOOD_METHOD(_name) \
265 .parser = mm_ ## _name ## _parser, \
266 .score_function = mm_ ## _name ## _score_function, \
267 .name = #_name
268
269 #define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \
270         DEFINE_MOOD_METHOD(_name), \
271         .cleanup = mm_ ## _name ## _cleanup
272
273 #define DEFINE_REGEX_MOOD_METHOD(_name) \
274         .name = #_name, \
275         .parser = mm_regex_parser, \
276         .score_function = mm_ ## _name ## _score_function, \
277         .cleanup = mm_regex_cleanup
278
279 const struct mood_method mood_methods[] = {
280         {DEFINE_MOOD_METHOD(no_attributes_set)},
281         {DEFINE_MOOD_METHOD(is_set)},
282         {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)},
283         {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)},
284         {DEFINE_REGEX_MOOD_METHOD(artist_matches)},
285         {DEFINE_REGEX_MOOD_METHOD(title_matches)},
286         {DEFINE_REGEX_MOOD_METHOD(album_matches)},
287         {DEFINE_REGEX_MOOD_METHOD(comment_matches)},
288         {.parser = NULL}
289 };