Add mood methods {artist, title, album, comment}_matches.
[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 };