1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  *  Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
11  *  GStreamer plugins to be used and distributed together with GStreamer
12  *  and Rhythmbox. This permission is above and beyond the permissions granted
13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
14  *  you may extend this exception to your version of the code, but you are not
15  *  obligated to do so. If you do not wish to do so, delete this exception
16  *  statement from your version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
26  *
27  */
28 
29 #include <config.h>
30 
31 #include <string.h>
32 
33 #include <glib.h>
34 #include <glib-object.h>
35 #include <gobject/gvaluecollector.h>
36 
37 #include "rhythmdb.h"
38 #include "rhythmdb-private.h"
39 #include "rb-util.h"
40 
41 #define RB_PARSE_CONJ (xmlChar *) "conjunction"
42 #define RB_PARSE_SUBQUERY (xmlChar *) "subquery"
43 #define RB_PARSE_LIKE (xmlChar *) "like"
44 #define RB_PARSE_PROP (xmlChar *) "prop"
45 #define RB_PARSE_NOT_LIKE (xmlChar *) "not-like"
46 #define RB_PARSE_PREFIX (xmlChar *) "prefix"
47 #define RB_PARSE_SUFFIX (xmlChar *) "suffix"
48 #define RB_PARSE_EQUALS (xmlChar *) "equals"
49 #define RB_PARSE_NOT_EQUAL (xmlChar *) "not-equal"
50 #define RB_PARSE_DISJ (xmlChar *) "disjunction"
51 #define RB_PARSE_GREATER (xmlChar *) "greater"
52 #define RB_PARSE_LESS (xmlChar *) "less"
53 #define RB_PARSE_CURRENT_TIME_WITHIN (xmlChar *) "current-time-within"
54 #define RB_PARSE_CURRENT_TIME_NOT_WITHIN (xmlChar *) "current-time-not-within"
55 #define RB_PARSE_YEAR_EQUALS RB_PARSE_EQUALS
56 #define RB_PARSE_YEAR_NOT_EQUAL RB_PARSE_NOT_EQUAL
57 #define RB_PARSE_YEAR_GREATER RB_PARSE_GREATER
58 #define RB_PARSE_YEAR_LESS RB_PARSE_LESS
59 
60 /**
61  * rhythmdb_query_copy:
62  * @array: the query to copy.
63  *
64  * Creates a copy of a query.
65  *
66  * Return value: (transfer full): a copy of the passed query.
67  * It must be freed with rhythmdb_query_free()
68  **/
69 GPtrArray *
rhythmdb_query_copy(GPtrArray * array)70 rhythmdb_query_copy (GPtrArray *array)
71 {
72 	GPtrArray *ret;
73 
74 	if (!array)
75 		return NULL;
76 
77 	ret = g_ptr_array_sized_new (array->len);
78 	rhythmdb_query_concatenate (ret, array);
79 
80 	return ret;
81 }
82 
83 /**
84  * rhythmdb_query_concatenate:
85  * @query1: query to append to
86  * @query2: query to append
87  *
88  * Appends @query2 to @query1.
89  */
90 void
rhythmdb_query_concatenate(GPtrArray * query1,GPtrArray * query2)91 rhythmdb_query_concatenate (GPtrArray *query1, GPtrArray *query2)
92 {
93 	guint i;
94 
95 	g_assert (query2);
96 	if (!query2)
97 		return;
98 
99 	for (i = 0; i < query2->len; i++) {
100 		RhythmDBQueryData *data = g_ptr_array_index (query2, i);
101 		RhythmDBQueryData *new_data = g_new0 (RhythmDBQueryData, 1);
102 		new_data->type = data->type;
103 		new_data->propid = data->propid;
104 		if (data->val) {
105 			new_data->val = g_new0 (GValue, 1);
106 			g_value_init (new_data->val, G_VALUE_TYPE (data->val));
107 			g_value_copy (data->val, new_data->val);
108 		}
109 		if (data->subquery)
110 			new_data->subquery = rhythmdb_query_copy (data->subquery);
111 		g_ptr_array_add (query1, new_data);
112 	}
113 }
114 
115 /**
116  * rhythmdb_query_parse_valist:
117  * @db: the #RhythmDB
118  * @args: the arguments to parse
119  *
120  * Converts a va_list into a parsed query in the form of a @GPtrArray.
121  * See @rhythmdb_query_parse for more information on the parsing process.
122  *
123  * Return value: converted query
124  */
125 GPtrArray *
rhythmdb_query_parse_valist(RhythmDB * db,va_list args)126 rhythmdb_query_parse_valist (RhythmDB *db, va_list args)
127 {
128 	RhythmDBQueryType query;
129 	GPtrArray *ret = g_ptr_array_new ();
130 	char *error;
131 
132 	while ((query = va_arg (args, RhythmDBQueryType)) != RHYTHMDB_QUERY_END) {
133 		RhythmDBQueryData *data = g_new0 (RhythmDBQueryData, 1);
134 		data->type = query;
135 		switch (query) {
136 		case RHYTHMDB_QUERY_DISJUNCTION:
137 			break;
138 		case RHYTHMDB_QUERY_SUBQUERY:
139 			data->subquery = rhythmdb_query_copy (va_arg (args, GPtrArray *));
140 			break;
141 		case RHYTHMDB_QUERY_PROP_EQUALS:
142 		case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
143 		case RHYTHMDB_QUERY_PROP_LIKE:
144 		case RHYTHMDB_QUERY_PROP_NOT_LIKE:
145 		case RHYTHMDB_QUERY_PROP_PREFIX:
146 		case RHYTHMDB_QUERY_PROP_SUFFIX:
147 		case RHYTHMDB_QUERY_PROP_GREATER:
148 		case RHYTHMDB_QUERY_PROP_LESS:
149 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
150 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
151 		case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
152 		case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
153 		case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
154 		case RHYTHMDB_QUERY_PROP_YEAR_LESS:
155 			data->propid = va_arg (args, guint);
156 			data->val = g_new0 (GValue, 1);
157 			g_value_init (data->val, rhythmdb_get_property_type (db, data->propid));
158 			G_VALUE_COLLECT (data->val, args, 0, &error);
159 			break;
160 		case RHYTHMDB_QUERY_END:
161 			g_assert_not_reached ();
162 			break;
163 		}
164 		g_ptr_array_add (ret, data);
165 	}
166 	return ret;
167 }
168 
169 /**
170  * rhythmdb_query_parse:
171  * @db: a #RhythmDB instance
172  * @...: query criteria to parse
173  *
174  * Creates a query from a list of criteria.
175  *
176  * Most criteria consists of an operator (#RhythmDBQueryType),
177  * a property (#RhythmDBPropType) and the data to compare with. An entry
178  * matches a criteria if the operator returns true with the value of the
179  * entries property as the first argument, and the given data as the second
180  * argument.
181  *
182  * Three types criteria are special. Passing RHYTHMDB_QUERY_END indicates the
183  * end of the list of criteria, and must be the last passes parameter.
184  *
185  * The second special criteria is a subquery which is defined by passing
186  * RHYTHMDB_QUERY_SUBQUERY, followed by a query (#GPtrArray). An entry will
187  * match a subquery criteria if it matches all criteria in the subquery.
188  *
189  * The third special criteria is a disjunction which is defined by passing
190  * RHYTHMDB_QUERY_DISJUNCTION, which will make an entry match the query if
191  * it matches the criteria before the disjunction, the criteria after the
192  * disjunction, or both.
193  *
194  * Example:
195  * 	rhythmdb_query_parse (db,
196  * 		RHYTHMDB_QUERY_SUBQUERY, subquery,
197  * 		RHYTHMDB_QUERY_DISJUNCTION
198  * 		RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_TITLE, "cat",
199  *		RHYTHMDB_QUERY_DISJUNCTION
200  *		RHYTHMDB_QUERY_PROP_GREATER, RHYTHMDB_PROP_RATING, 2.5,
201  *		RHYTHMDB_QUERY_PROP_LESS, RHYTHMDB_PROP_PLAY_COUNT, 10,
202  * 		RHYTHMDB_QUERY_END);
203  *
204  * 	will create a query that matches entries:
205  * 	a) that match the query "subquery", or
206  * 	b) that have "cat" in their title, or
207  * 	c) have a rating of at least 2.5, and a play count of at most 10
208  *
209  * Returns: (transfer full): a the newly created query.
210  * It must be freed with rhythmdb_query_free()
211  **/
212 GPtrArray *
rhythmdb_query_parse(RhythmDB * db,...)213 rhythmdb_query_parse (RhythmDB *db, ...)
214 {
215 	GPtrArray *ret;
216 	va_list args;
217 
218 	va_start (args, db);
219 
220 	ret = rhythmdb_query_parse_valist (db, args);
221 
222 	va_end (args);
223 
224 	return ret;
225 }
226 
227 /**
228  * rhythmdb_query_append:
229  * @db: a #RhythmDB instance
230  * @query: a query.
231  * @...: query criteria to append
232  *
233  * Appends new criteria to the query @query.
234  *
235  * The list of criteria must be in the same format as for rhythmdb_query_parse,
236  * and ended by RHYTHMDB_QUERY_END.
237  **/
238 void
rhythmdb_query_append(RhythmDB * db,GPtrArray * query,...)239 rhythmdb_query_append (RhythmDB *db, GPtrArray *query, ...)
240 {
241 	va_list args;
242 	guint i;
243 	GPtrArray *new = g_ptr_array_new ();
244 
245 	va_start (args, query);
246 
247 	new = rhythmdb_query_parse_valist (db, args);
248 
249 	for (i = 0; i < new->len; i++)
250 		g_ptr_array_add (query, g_ptr_array_index (new, i));
251 
252 	g_ptr_array_free (new, TRUE);
253 
254 	va_end (args);
255 }
256 
257 /**
258  * rhythmdb_query_append_params:
259  * @db: the #RhythmDB
260  * @query: the query to append to
261  * @type: query type
262  * @prop: query property
263  * @value: query value
264  *
265  * Appends a new query term to @query.
266  */
267 void
rhythmdb_query_append_params(RhythmDB * db,GPtrArray * query,RhythmDBQueryType type,RhythmDBPropType prop,const GValue * value)268 rhythmdb_query_append_params (RhythmDB *db, GPtrArray *query,
269 			      RhythmDBQueryType type, RhythmDBPropType prop, const GValue *value)
270 {
271 	RhythmDBQueryData *data = g_new0 (RhythmDBQueryData, 1);
272 
273 	data->type = type;
274 	switch (type) {
275 	case RHYTHMDB_QUERY_END:
276 		g_assert_not_reached ();
277 		break;
278 	case RHYTHMDB_QUERY_DISJUNCTION:
279 		break;
280 	case RHYTHMDB_QUERY_SUBQUERY:
281 		data->subquery = rhythmdb_query_copy (g_value_get_pointer (value));
282 		break;
283 	case RHYTHMDB_QUERY_PROP_EQUALS:
284 	case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
285 	case RHYTHMDB_QUERY_PROP_LIKE:
286 	case RHYTHMDB_QUERY_PROP_NOT_LIKE:
287 	case RHYTHMDB_QUERY_PROP_PREFIX:
288 	case RHYTHMDB_QUERY_PROP_SUFFIX:
289 	case RHYTHMDB_QUERY_PROP_GREATER:
290 	case RHYTHMDB_QUERY_PROP_LESS:
291 	case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
292 	case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
293 	case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
294 	case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
295 	case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
296 	case RHYTHMDB_QUERY_PROP_YEAR_LESS:
297 		data->propid = prop;
298 		data->val = g_new0 (GValue, 1);
299 		g_value_init (data->val, rhythmdb_get_property_type (db, data->propid));
300 		g_value_transform (value, data->val);
301 		break;
302 	}
303 
304 	g_ptr_array_add (query, data);
305 }
306 
307 /**
308  * rhythmdb_query_free:
309  * @query: a query.
310  *
311  * Frees the query @query
312  */
313 void
rhythmdb_query_free(GPtrArray * query)314 rhythmdb_query_free (GPtrArray *query)
315 {
316 	guint i;
317 
318 	if (query == NULL)
319 		return;
320 
321 	for (i = 0; i < query->len; i++) {
322 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
323 		switch (data->type) {
324 		case RHYTHMDB_QUERY_DISJUNCTION:
325 			break;
326 		case RHYTHMDB_QUERY_SUBQUERY:
327 			rhythmdb_query_free (data->subquery);
328 			break;
329 		case RHYTHMDB_QUERY_PROP_EQUALS:
330 		case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
331 		case RHYTHMDB_QUERY_PROP_LIKE:
332 		case RHYTHMDB_QUERY_PROP_NOT_LIKE:
333 		case RHYTHMDB_QUERY_PROP_PREFIX:
334 		case RHYTHMDB_QUERY_PROP_SUFFIX:
335 		case RHYTHMDB_QUERY_PROP_GREATER:
336 		case RHYTHMDB_QUERY_PROP_LESS:
337 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
338 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
339 		case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
340 		case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
341 		case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
342 		case RHYTHMDB_QUERY_PROP_YEAR_LESS:
343 			g_value_unset (data->val);
344 			g_free (data->val);
345 			break;
346 		case RHYTHMDB_QUERY_END:
347 			g_assert_not_reached ();
348 			break;
349 		}
350 		g_free (data);
351 	}
352 
353 	g_ptr_array_free (query, TRUE);
354 }
355 
356 static char *
prop_gvalue_to_string(RhythmDB * db,RhythmDBPropType propid,GValue * val)357 prop_gvalue_to_string (RhythmDB *db,
358 		       RhythmDBPropType propid,
359 		       GValue *val)
360 {
361 	/* special-case some properties */
362 	switch (propid) {
363 	case RHYTHMDB_PROP_TYPE:
364 		{
365 			RhythmDBEntryType *type = g_value_get_object (val);
366 			return g_strdup (rhythmdb_entry_type_get_name (type));
367 		}
368 		break;
369 	default:
370 		break;
371 	}
372 
373 	/* otherwise just convert numbers to strings */
374 	switch (G_VALUE_TYPE (val)) {
375 	case G_TYPE_STRING:
376 		return g_value_dup_string (val);
377 	case G_TYPE_BOOLEAN:
378 		return g_strdup_printf ("%d", g_value_get_boolean (val));
379 	case G_TYPE_INT:
380 		return g_strdup_printf ("%d", g_value_get_int (val));
381 	case G_TYPE_LONG:
382 		return g_strdup_printf ("%ld", g_value_get_long (val));
383 	case G_TYPE_ULONG:
384 		return g_strdup_printf ("%lu", g_value_get_ulong (val));
385 	case G_TYPE_UINT64:
386 		return g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (val));
387 	case G_TYPE_FLOAT:
388 		return g_strdup_printf ("%f", g_value_get_float (val));
389 	case G_TYPE_DOUBLE:
390 		return g_strdup_printf ("%f", g_value_get_double (val));
391 	default:
392 		g_assert_not_reached ();
393 		return NULL;
394 	}
395 }
396 
397 static void
write_encoded_gvalue(RhythmDB * db,xmlNodePtr node,RhythmDBPropType propid,GValue * val)398 write_encoded_gvalue (RhythmDB *db,
399 		      xmlNodePtr node,
400 		      RhythmDBPropType propid,
401 		      GValue *val)
402 {
403 	char *strval = NULL;
404 	xmlChar *quoted;
405 
406 	strval = prop_gvalue_to_string (db, propid, val);
407 	quoted = xmlEncodeEntitiesReentrant (NULL, BAD_CAST strval);
408 	g_free (strval);
409 
410 	xmlNodeSetContent (node, quoted);
411 	g_free (quoted);
412 }
413 
414 /**
415  * rhythmdb_read_encoded_property:
416  * @db: the #RhythmDB
417  * @content: encoded property value
418  * @propid: property ID
419  * @val: returns the property value
420  *
421  * Converts a string containing a property value into a #GValue
422  * containing the native form of the property value.  For boolean
423  * and numeric properties, this converts the string to a number.
424  * For #RHYTHMDB_PROP_TYPE, this looks up the entry type by name.
425  * For strings, no conversion is required.
426  */
427 void
rhythmdb_read_encoded_property(RhythmDB * db,const char * content,RhythmDBPropType propid,GValue * val)428 rhythmdb_read_encoded_property (RhythmDB *db,
429 				const char *content,
430 				RhythmDBPropType propid,
431 				GValue *val)
432 {
433 	g_value_init (val, rhythmdb_get_property_type (db, propid));
434 
435 	switch (G_VALUE_TYPE (val)) {
436 	case G_TYPE_STRING:
437 		g_value_set_string (val, content);
438 		break;
439 	case G_TYPE_BOOLEAN:
440 		g_value_set_boolean (val, g_ascii_strtoull (content, NULL, 10));
441 		break;
442 	case G_TYPE_ULONG:
443 		g_value_set_ulong (val, g_ascii_strtoull (content, NULL, 10));
444 		break;
445 	case G_TYPE_UINT64:
446 		g_value_set_uint64 (val, g_ascii_strtoull (content, NULL, 10));
447 		break;
448 	case G_TYPE_DOUBLE:
449 		{
450 			gdouble d;
451 			char *end;
452 
453 			d = g_ascii_strtod (content, &end);
454 			if (*end != '\0') {
455 				/* conversion wasn't entirely successful.
456 				 * try locale-aware strtod().
457 				 */
458 				d = strtod (content, NULL);
459 			}
460 			g_value_set_double (val, d);
461 		}
462 		break;
463 	case G_TYPE_OBJECT:			/* hm, really? */
464 		if (propid == RHYTHMDB_PROP_TYPE) {
465 			RhythmDBEntryType *entry_type;
466 			entry_type = rhythmdb_entry_type_get_by_name (db, content);
467 			if (entry_type != NULL) {
468 				g_value_set_object (val, entry_type);
469 				break;
470 			} else {
471 				g_warning ("Unexpected entry type");
472 				/* Fall through */
473 			}
474 		}
475 		/* Falling through on purpose to get an assert for unexpected
476 		 * cases
477 		 */
478 	default:
479 		g_warning ("Attempt to read '%s' of unhandled type %s",
480 			   rhythmdb_nice_elt_name_from_propid (db, propid),
481 			   g_type_name (G_VALUE_TYPE (val)));
482 		g_assert_not_reached ();
483 		break;
484 	}
485 }
486 
487 /**
488  * rhythmdb_query_serialize:
489  * @db: the #RhythmDB
490  * @query: query to serialize
491  * @parent: XML node to attach the query to
492  *
493  * Converts @query into XML form as a child of @parent.  It can be converted
494  * back into a query by passing @parent to @rhythmdb_query_deserialize.
495  */
496 void
rhythmdb_query_serialize(RhythmDB * db,GPtrArray * query,xmlNodePtr parent)497 rhythmdb_query_serialize (RhythmDB *db, GPtrArray *query,
498 			  xmlNodePtr parent)
499 {
500 	guint i;
501 	xmlNodePtr node = xmlNewChild (parent, NULL, RB_PARSE_CONJ, NULL);
502 	xmlNodePtr subnode;
503 
504 	for (i = 0; i < query->len; i++) {
505 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
506 
507 		switch (data->type) {
508 		case RHYTHMDB_QUERY_SUBQUERY:
509 			subnode = xmlNewChild (node, NULL, RB_PARSE_SUBQUERY, NULL);
510 			rhythmdb_query_serialize (db, data->subquery, subnode);
511 			break;
512 		case RHYTHMDB_QUERY_PROP_LIKE:
513 			subnode = xmlNewChild (node, NULL, RB_PARSE_LIKE, NULL);
514 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
515 			write_encoded_gvalue (db, subnode, data->propid, data->val);
516 			break;
517 		case RHYTHMDB_QUERY_PROP_NOT_LIKE:
518 			subnode = xmlNewChild (node, NULL, RB_PARSE_NOT_LIKE, NULL);
519 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
520 			write_encoded_gvalue (db, subnode, data->propid, data->val);
521 			break;
522 		case RHYTHMDB_QUERY_PROP_PREFIX:
523 			subnode = xmlNewChild (node, NULL, RB_PARSE_PREFIX, NULL);
524 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
525 			write_encoded_gvalue (db, subnode, data->propid, data->val);
526 			break;
527 		case RHYTHMDB_QUERY_PROP_SUFFIX:
528 			subnode = xmlNewChild (node, NULL, RB_PARSE_SUFFIX, NULL);
529 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
530 			write_encoded_gvalue (db, subnode, data->propid, data->val);
531 			break;
532 		case RHYTHMDB_QUERY_PROP_EQUALS:
533 			subnode = xmlNewChild (node, NULL, RB_PARSE_EQUALS, NULL);
534 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
535 			write_encoded_gvalue (db, subnode, data->propid, data->val);
536 			break;
537 		case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
538 			subnode = xmlNewChild (node, NULL, RB_PARSE_NOT_EQUAL, NULL);
539 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
540 			write_encoded_gvalue (db, subnode, data->propid, data->val);
541 			break;
542 		case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
543 			subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_EQUALS, NULL);
544 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
545 			write_encoded_gvalue (db, subnode, data->propid, data->val);
546 			break;
547 		case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
548 			subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_NOT_EQUAL, NULL);
549 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
550 			write_encoded_gvalue (db, subnode, data->propid, data->val);
551 			break;
552 		case RHYTHMDB_QUERY_DISJUNCTION:
553 			subnode = xmlNewChild (node, NULL, RB_PARSE_DISJ, NULL);
554 			break;
555 		case RHYTHMDB_QUERY_END:
556 			break;
557 		case RHYTHMDB_QUERY_PROP_GREATER:
558 			subnode = xmlNewChild (node, NULL, RB_PARSE_GREATER, NULL);
559 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
560 			write_encoded_gvalue (db, subnode, data->propid, data->val);
561 			break;
562 		case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
563 			subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_GREATER, NULL);
564 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
565 			write_encoded_gvalue (db, subnode, data->propid, data->val);
566 			break;
567 		case RHYTHMDB_QUERY_PROP_LESS:
568 			subnode = xmlNewChild (node, NULL, RB_PARSE_LESS, NULL);
569 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
570 			write_encoded_gvalue (db, subnode, data->propid, data->val);
571 			break;
572 		case RHYTHMDB_QUERY_PROP_YEAR_LESS:
573 			subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_LESS, NULL);
574 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
575 			write_encoded_gvalue (db, subnode, data->propid, data->val);
576 			break;
577 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
578 			subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_WITHIN, NULL);
579 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
580 			write_encoded_gvalue (db, subnode, data->propid, data->val);
581 			break;
582 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
583 			subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_NOT_WITHIN, NULL);
584 			xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
585 			write_encoded_gvalue (db, subnode, data->propid, data->val);
586 			break;
587 		}
588 	}
589 }
590 
591 /**
592  * rhythmdb_query_deserialize:
593  * @db: the #RhythmDB
594  * @parent: parent XML node of serialized query
595  *
596  * Converts a serialized query back into a @GPtrArray query.
597  *
598  * Return value: (transfer full): deserialized query.
599  */
600 GPtrArray *
rhythmdb_query_deserialize(RhythmDB * db,xmlNodePtr parent)601 rhythmdb_query_deserialize (RhythmDB *db, xmlNodePtr parent)
602 {
603 	GPtrArray *query = g_ptr_array_new ();
604 	xmlNodePtr child;
605 
606 	g_assert (!xmlStrcmp (parent->name, RB_PARSE_CONJ));
607 
608 	for (child = parent->children; child; child = child->next) {
609 		RhythmDBQueryData *data;
610 
611 		if (xmlNodeIsText (child))
612 			continue;
613 
614 		data = g_new0 (RhythmDBQueryData, 1);
615 
616 		if (!xmlStrcmp (child->name, RB_PARSE_SUBQUERY)) {
617 			xmlNodePtr subquery;
618 			data->type = RHYTHMDB_QUERY_SUBQUERY;
619 			subquery = child->children;
620 			while (xmlNodeIsText (subquery))
621 				subquery = subquery->next;
622 
623 			data->subquery = rhythmdb_query_deserialize (db, subquery);
624 		} else if (!xmlStrcmp (child->name, RB_PARSE_DISJ)) {
625 			data->type = RHYTHMDB_QUERY_DISJUNCTION;
626 		} else if (!xmlStrcmp (child->name, RB_PARSE_LIKE)) {
627 			data->type = RHYTHMDB_QUERY_PROP_LIKE;
628 		} else if (!xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)) {
629 			data->type = RHYTHMDB_QUERY_PROP_NOT_LIKE;
630 		} else if (!xmlStrcmp (child->name, RB_PARSE_PREFIX)) {
631 			data->type = RHYTHMDB_QUERY_PROP_PREFIX;
632 		} else if (!xmlStrcmp (child->name, RB_PARSE_SUFFIX)) {
633 			data->type = RHYTHMDB_QUERY_PROP_SUFFIX;
634 		} else if (!xmlStrcmp (child->name, RB_PARSE_EQUALS)) {
635 			xmlChar* prop;
636 
637 			prop = xmlGetProp(child, RB_PARSE_PROP);
638 			if (!xmlStrcmp(prop, (xmlChar *)"date"))
639 				data->type = RHYTHMDB_QUERY_PROP_YEAR_EQUALS;
640 			else
641 				data->type = RHYTHMDB_QUERY_PROP_EQUALS;
642 			xmlFree (prop);
643 		} else if (!xmlStrcmp (child->name, RB_PARSE_NOT_EQUAL)) {
644 			xmlChar* prop;
645 
646 			prop = xmlGetProp(child, RB_PARSE_PROP);
647 			if (!xmlStrcmp(prop, (xmlChar *)"date"))
648 				data->type = RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL;
649 			else
650 				data->type = RHYTHMDB_QUERY_PROP_NOT_EQUAL;
651 			xmlFree (prop);
652 		} else if (!xmlStrcmp (child->name, RB_PARSE_GREATER)) {
653 			xmlChar* prop;
654 
655 			prop = xmlGetProp(child, RB_PARSE_PROP);
656 			if (!xmlStrcmp(prop, (xmlChar *)"date"))
657 				data->type = RHYTHMDB_QUERY_PROP_YEAR_GREATER;
658 			else
659 				data->type = RHYTHMDB_QUERY_PROP_GREATER;
660 			xmlFree (prop);
661 		} else if (!xmlStrcmp (child->name, RB_PARSE_LESS)) {
662 			xmlChar* prop;
663 
664 			prop = xmlGetProp(child, RB_PARSE_PROP);
665 			if (!xmlStrcmp(prop, (xmlChar *)"date"))
666 				data->type = RHYTHMDB_QUERY_PROP_YEAR_LESS;
667 			else
668 				data->type = RHYTHMDB_QUERY_PROP_LESS;
669 			xmlFree (prop);
670 		} else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)) {
671 			data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN;
672 		} else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
673 			data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN;
674 		} else
675  			g_assert_not_reached ();
676 
677 		if (!xmlStrcmp (child->name, RB_PARSE_LIKE)
678 		    || !xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)
679 		    || !xmlStrcmp (child->name, RB_PARSE_PREFIX)
680 		    || !xmlStrcmp (child->name, RB_PARSE_SUFFIX)
681 		    || !xmlStrcmp (child->name, RB_PARSE_EQUALS)
682 		    || !xmlStrcmp (child->name, RB_PARSE_NOT_EQUAL)
683 		    || !xmlStrcmp (child->name, RB_PARSE_GREATER)
684 		    || !xmlStrcmp (child->name, RB_PARSE_LESS)
685 		    || !xmlStrcmp (child->name, RB_PARSE_YEAR_EQUALS)
686 		    || !xmlStrcmp (child->name, RB_PARSE_YEAR_GREATER)
687 		    || !xmlStrcmp (child->name, RB_PARSE_YEAR_LESS)
688 		    || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)
689 		    || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
690 			char *content;
691 			xmlChar *propstr = xmlGetProp (child, RB_PARSE_PROP);
692 			gint propid = rhythmdb_propid_from_nice_elt_name (db, propstr);
693 			g_free (propstr);
694 
695 			g_assert (propid >= 0 && propid < RHYTHMDB_NUM_PROPERTIES);
696 
697 			data->propid = propid;
698 			data->val = g_new0 (GValue, 1);
699 
700 			content = (char *)xmlNodeGetContent (child);
701 			rhythmdb_read_encoded_property (db, content, data->propid, data->val);
702 			g_free (content);
703 		}
704 
705 		g_ptr_array_add (query, data);
706 	}
707 
708 	return query;
709 }
710 
711 /*
712  * This is used to "process" queries, before using them. It is mainly used to two things:
713  *
714  * 1) performing expensive data transformations once per query, rather than
715  *    once per entry we try to match against. e.g. RHYTHMDB_PROP_SEARCH_MATCH
716  *
717  * 2) defining criteria in terms of other lower-level ones that the db backend
718  *    actually implements. e.g. RHYTHMDB_QUERY_YEAR_*
719  */
720 
721 /**
722  * rhythmdb_query_preprocess:
723  * @db: the #RhythmDB
724  * @query: query to preprocess
725  *
726  * Preprocesses a query to prepare it for execution.  This has two main
727  * roles: to perform expensive data transformations once per query, rather
728  * than once per entry, and converting criteria to lower-level forms that
729  * are implemented by the database backend.
730  *
731  * For RHYTHMDB_PROP_SEARCH_MATCH, this converts the search terms into
732  * an array of case-folded words.
733  *
734  * When matching against case-folded properties such as
735  * #RHYTHMDB_PROP_TITLE_FOLDED, this case-folds the query value.
736  *
737  * When performing year-based criteria such as #RHYTHMDB_QUERY_PROP_YEAR_LESS,
738  * it converts the year into the Julian date such that a simple numeric
739  * comparison will work.
740  */
741 void
rhythmdb_query_preprocess(RhythmDB * db,GPtrArray * query)742 rhythmdb_query_preprocess (RhythmDB *db, GPtrArray *query)
743 {
744 	int i;
745 
746 	if (query == NULL)
747 		return;
748 
749 	for (i = 0; i < query->len; i++) {
750 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
751 		gboolean restart_criteria = FALSE;
752 
753 		if (data->subquery) {
754 			rhythmdb_query_preprocess (db, data->subquery);
755 		} else switch (data->propid) {
756 			case RHYTHMDB_PROP_TITLE_FOLDED:
757 			case RHYTHMDB_PROP_GENRE_FOLDED:
758 			case RHYTHMDB_PROP_ARTIST_FOLDED:
759 			case RHYTHMDB_PROP_COMPOSER_FOLDED:
760 			case RHYTHMDB_PROP_ALBUM_FOLDED:
761 			{
762 				/* as we are matching against a folded property, the string needs to also be folded */
763 				const char *orig = g_value_get_string (data->val);
764 				char *folded = rb_search_fold (orig);
765 
766 				g_value_reset (data->val);
767 				g_value_take_string (data->val, folded);
768 				break;
769 			}
770 
771 			case RHYTHMDB_PROP_SEARCH_MATCH:
772 			{
773 				const char *orig = g_value_get_string (data->val);
774 				char *folded = rb_search_fold (orig);
775 				char **words = rb_string_split_words (folded);
776 
777 				g_free (folded);
778 				g_value_unset (data->val);
779 				g_value_init (data->val, G_TYPE_STRV);
780 				g_value_take_boxed (data->val, words);
781 				break;
782 			}
783 
784 			case RHYTHMDB_PROP_DATE:
785 			{
786 				GDate date = {0,};
787 				gulong search_date;
788 				gulong begin;
789 				gulong end;
790 				gulong year;
791 
792 				search_date = g_value_get_ulong (data->val);
793 
794 				/* GDate functions don't handle Year="0", so we need to special case this */
795 				if (search_date != 0) {
796 					g_date_set_julian (&date, search_date);
797 					year = g_date_get_year (&date);
798 					g_date_clear (&date, 1);
799 
800 					/* get Julian dates for beginning and end of year */
801 					g_date_set_dmy (&date, 1, G_DATE_JANUARY, year);
802 					begin = g_date_get_julian (&date);
803 					g_date_clear (&date, 1);
804 
805 					/* and the day before the beginning of the next year */
806 					g_date_set_dmy (&date, 1, G_DATE_JANUARY, year + 1);
807 					end =  g_date_get_julian (&date) - 1;
808 				} else {
809 					begin = 0;
810 					end = 0;
811 				}
812 
813 				switch (data->type)
814 				{
815 				case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
816 					restart_criteria = TRUE;
817 					data->type = RHYTHMDB_QUERY_SUBQUERY;
818 					data->subquery = rhythmdb_query_parse (db,
819 									       RHYTHMDB_QUERY_PROP_GREATER, data->propid, begin,
820 									       RHYTHMDB_QUERY_PROP_LESS, data->propid, end,
821 									       RHYTHMDB_QUERY_END);
822 					break;
823 				case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
824 					restart_criteria = TRUE;
825 					data->type = RHYTHMDB_QUERY_SUBQUERY;
826 					data->subquery = rhythmdb_query_parse (db,
827 									       RHYTHMDB_QUERY_PROP_LESS, data->propid, begin-1,
828 									       RHYTHMDB_QUERY_DISJUNCTION,
829 									       RHYTHMDB_QUERY_PROP_GREATER, data->propid, end+1,
830 									       RHYTHMDB_QUERY_END);
831 					break;
832 
833 				case RHYTHMDB_QUERY_PROP_YEAR_LESS:
834 					restart_criteria = TRUE;
835 					data->type = RHYTHMDB_QUERY_PROP_LESS;
836 					g_value_set_ulong (data->val, end);
837 					break;
838 
839 				case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
840 					restart_criteria = TRUE;
841 					data->type = RHYTHMDB_QUERY_PROP_GREATER;
842 					g_value_set_ulong (data->val, begin);
843 					break;
844 
845 				default:
846 					break;
847 				}
848 
849 				break;
850 			}
851 
852 			default:
853 				break;
854 		}
855 
856 		/* re-do this criteria, in case it needs further transformation */
857 		if (restart_criteria)
858 			i--;
859 	}
860 }
861 
862 /**
863  * rhythmdb_query_append_prop_multiple:
864  * @db: the #RhythmDB
865  * @query: the query to append to
866  * @propid: property ID to match
867  * @items: (element-type GObject.Value): #GList of values to match against
868  *
869  * Appends a set of criteria to a query to match against any of the values
870  * listed in @items.
871  */
872 void
rhythmdb_query_append_prop_multiple(RhythmDB * db,GPtrArray * query,RhythmDBPropType propid,GList * items)873 rhythmdb_query_append_prop_multiple (RhythmDB *db, GPtrArray *query, RhythmDBPropType propid, GList *items)
874 {
875 	GPtrArray *subquery;
876 
877 	if (items == NULL)
878 		return;
879 
880 	if (items->next == NULL) {
881 		rhythmdb_query_append (db,
882 				       query,
883 				       RHYTHMDB_QUERY_PROP_EQUALS,
884 				       propid,
885 				       items->data,
886 				       RHYTHMDB_QUERY_END);
887 		return;
888 	}
889 
890 	subquery = g_ptr_array_new ();
891 
892 	rhythmdb_query_append (db,
893 			       subquery,
894 			       RHYTHMDB_QUERY_PROP_EQUALS,
895 			       propid,
896 			       items->data,
897 			       RHYTHMDB_QUERY_END);
898 	items = items->next;
899 	while (items) {
900 		rhythmdb_query_append (db,
901 				       subquery,
902 				       RHYTHMDB_QUERY_DISJUNCTION,
903 				       RHYTHMDB_QUERY_PROP_EQUALS,
904 				       propid,
905 				       items->data,
906 				       RHYTHMDB_QUERY_END);
907 		items = items->next;
908 	}
909 	rhythmdb_query_append (db, query, RHYTHMDB_QUERY_SUBQUERY, subquery,
910 			       RHYTHMDB_QUERY_END);
911 }
912 
913 /**
914  * rhythmdb_query_is_time_relative:
915  * @db: the #RhythmDB
916  * @query: the query to check
917  *
918  * Checks if a query contains any time-relative criteria.
919  *
920  * Return value: %TRUE if time-relative criteria found
921  */
922 gboolean
rhythmdb_query_is_time_relative(RhythmDB * db,GPtrArray * query)923 rhythmdb_query_is_time_relative (RhythmDB *db, GPtrArray *query)
924 {
925 	int i;
926 	if (query == NULL)
927 		return FALSE;
928 
929 	for (i=0; i < query->len; i++) {
930 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
931 
932 		if (data->subquery) {
933 			if (rhythmdb_query_is_time_relative (db, data->subquery))
934 				return TRUE;
935 			else
936 				continue;
937 		}
938 
939 		switch (data->type) {
940 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
941 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
942 			return TRUE;
943 		default:
944 			break;
945 		}
946 	}
947 
948 	return FALSE;
949 }
950 
951 /**
952  * rhythmdb_query_to_string:
953  * @db: a #RhythmDB instance
954  * @query: a query.
955  *
956  * Returns a supposedly human-readable form of the query.
957  * This is only intended for debug usage.
958  *
959  * Returns: allocated string form of the query
960  **/
961 char *
rhythmdb_query_to_string(RhythmDB * db,GPtrArray * query)962 rhythmdb_query_to_string (RhythmDB *db, GPtrArray *query)
963 {
964 	GString *buf;
965 	int i;
966 
967 	buf = g_string_sized_new (100);
968 	for (i = 0; i < query->len; i++) {
969 		char *fmt = NULL;
970 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
971 
972 		switch (data->type) {
973 		case RHYTHMDB_QUERY_SUBQUERY:
974 			{
975 				char *s;
976 
977 				s = rhythmdb_query_to_string (db, data->subquery);
978 				g_string_append_printf (buf, "{ %s }",  s);
979 				g_free (s);
980 			}
981 			break;
982 		case RHYTHMDB_QUERY_PROP_LIKE:
983 			fmt = "(%s =~ %s)";
984 			break;
985 		case RHYTHMDB_QUERY_PROP_NOT_LIKE:
986 			fmt = "(%s !~ %s)";
987 			break;
988 		case RHYTHMDB_QUERY_PROP_PREFIX:
989 			fmt = "(%s |< %s)";
990 			break;
991 		case RHYTHMDB_QUERY_PROP_SUFFIX:
992 			fmt = "(%s >| %s)";
993 			break;
994 		case RHYTHMDB_QUERY_PROP_EQUALS:
995 			fmt = "(%s == %s)";
996 			break;
997 		case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
998 			fmt = "(%s != %s)";
999 			break;
1000 		case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
1001 			fmt = "(year(%s) == %s)";
1002 			break;
1003 		case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
1004 			fmt = "(year(%s) != %s)";
1005 			break;
1006 		case RHYTHMDB_QUERY_DISJUNCTION:
1007 			g_string_append_printf (buf, " || ");
1008 			break;
1009 		case RHYTHMDB_QUERY_END:
1010 			break;
1011 		case RHYTHMDB_QUERY_PROP_GREATER:
1012 			fmt = "(%s > %s)";
1013 			break;
1014 		case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
1015 			fmt = "(year(%s) > %s)";
1016 			break;
1017 		case RHYTHMDB_QUERY_PROP_LESS:
1018 			fmt = "(%s < %s)";
1019 			break;
1020 		case RHYTHMDB_QUERY_PROP_YEAR_LESS:
1021 			fmt = "(year(%s) < %s)";
1022 			break;
1023 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1024 			fmt = "(%s <> %s)";
1025 			break;
1026 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1027 			fmt = "(%s >< %s)";
1028 			break;
1029 		}
1030 
1031 		if (fmt) {
1032 			char *value;
1033 
1034 			value = prop_gvalue_to_string (db, data->propid, data->val);
1035 			g_string_append_printf (buf, fmt,
1036 						rhythmdb_nice_elt_name_from_propid (db, data->propid),
1037 						value);
1038 			g_free (value);
1039 			fmt = NULL;
1040 		}
1041 	}
1042 
1043 	return g_string_free (buf, FALSE);
1044 }
1045 
1046 GType
rhythmdb_query_get_type(void)1047 rhythmdb_query_get_type (void)
1048 {
1049 	static GType type = 0;
1050 
1051 	if (G_UNLIKELY (type == 0)) {
1052 		type = g_boxed_type_register_static ("RhythmDBQuery",
1053 						     (GBoxedCopyFunc)rhythmdb_query_copy,
1054 						     (GBoxedFreeFunc)rhythmdb_query_free);
1055 	}
1056 
1057 	return type;
1058 }
1059