tracker-query.vala 9.82 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
//
// Copyright 2010, Martyn Russell <martyn@lanedo.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301, USA.
//

using Tracker.Sparql;

public class Tracker.Query {
	public enum Type {
		ALL,
25 26 27 28 29 30 31
		CONTACTS,
		APPLICATIONS,
		MUSIC,
		IMAGES,
		VIDEOS,
		DOCUMENTS,
		MAIL,
32
		CALENDAR,
33 34
		FOLDERS,
		BOOKMARKS
35 36
	}

37 38 39 40 41
	public enum Match {
		NONE,
		FTS,
		FTS_INDIRECT,
		TITLES,
42 43 44
		TITLES_INDIRECT,
		TAGS_ONLY,
		TAGS_ONLY_INDIRECT
45 46 47 48 49 50 51
	}

	private string [] match_clauses = {
		// NONE (i.e. just show all)
		"",

		// FTS
52 53 54 55 56 57
		"{
		   ?urn fts:match \"%s\"
		 } UNION {
		   ?urn nao:hasTag ?tag .
                   FILTER (fn:contains (fn:lower-case (nao:prefLabel(?tag)), \"%s\"))
		 }",
58 59

		// FTS_INDIRECT (with sub-matching)
60 61 62 63 64 65
		"{
		   ?match fts:match \"%s\"
		 } UNION {
		   ?match nao:hasTag ?tag .
                   FILTER (fn:contains (fn:lower-case (nao:prefLabel(?tag)), \"%s\"))
		 }",
66 67 68 69

		// TITLES
		"FILTER (fn:contains (fn:lower-case (nfo:fileName(?urn)), \"%s\"))",

70 71 72 73 74 75
		// TITLES_INDIRECT (with sub-matching)
		"FILTER (fn:contains (fn:lower-case (nie:title(?match)), \"%s\"))",

		// TAGS_ONLY (no fts:match, just nao:prefLabel matching, %s is filled in by get_tags_filter()
		"?urn nao:hasTag ?tag .
		 FILTER (nao:prefLabel(?tag) IN (%s))",
76

77 78 79 80
		// TAGS_ONLY_INDIRECT (same as TAGS_ONLY for ?match)
		"?match nao:hasTag ?tag .
		 FILTER (nao:prefLabel(?tag) IN (%s))"
	};
81 82 83

	private string [] where_clauses = {
		// ALL
84
		"WHERE {
85 86
		  %s .
		  ?urn nfo:belongsToContainer ?parent ;
87 88
		  tracker:available true .
		}",
89

90
		// CONTACTS
91
		"",
92 93

		// APPLICATIONS
94
		"WHERE {
95 96
		  ?urn a nfo:Software .
		  %s
97 98 99
		}",

		// MUSIC
100 101 102 103 104 105 106 107 108 109
		"WHERE {
		  {
		    ?urn nmm:musicAlbum ?match
		  } UNION {
		    ?urn nmm:performer ?match
		  } UNION {
		    ?urn a nfo:Audio .
		    ?match a nfo:Audio
		    FILTER (?urn = ?match)
		  }
110
		  %s .
111 112
		  ?urn nmm:performer [ nmm:artistName ?performer ] ;
		       nmm:musicAlbum [ nie:title ?album ] ;
113
		       nie:url ?tooltip .
114 115 116
		}",

		// IMAGES
117 118
		"WHERE {
		  ?urn a nfo:Image ;
119 120
		         nie:url ?tooltip .
		  %s
121 122 123
		}",

		// VIDEOS
124 125
		"WHERE {
		  ?urn a nfo:Video ;
126 127
		         nie:url ?tooltip .
		  %s
128 129 130
		}",

		// DOCUMENTS
131
		"WHERE {
132 133 134 135 136 137
		  {
		    ?urn nco:creator ?match
		  } UNION {
		    ?urn nco:publisher ?match
		  } UNION {
		    ?urn a nfo:Document .
138
		    FILTER (! EXISTS { ?urn a nmo:Email } )
139 140 141 142 143
		    ?match a nfo:Document
		    FILTER (?urn = ?match)
		  }
		  %s .
		  ?urn nie:url ?tooltip .
144 145 146 147 148 149
		  OPTIONAL {
		    ?urn nco:creator ?creator .
		  }
		  OPTIONAL {
		    ?urn nco:publisher ?publisher .
		  }
150 151 152
		}",

		// MAIL
153 154 155
		"WHERE {
		  ?urn a nmo:Email ;
		         nmo:from ?sender ;
156 157
		         nmo:to ?to .
		  %s
158 159 160
		}",

		// CALENDAR
161
		"",
162 163

		// FOLDERS
164 165
		"WHERE {
		  ?urn a nfo:Folder ;
166 167
		         nie:url ?tooltip .
		  %s
168 169 170
		  OPTIONAL {
		    ?urn nfo:belongsToContainer ?parent .
		  }
171 172 173 174 175
		}",

		// BOOKMARKS
		"WHERE {
		  ?urn a nfo:Bookmark ;
176
                         nfo:bookmarks ?bookmark .
177
		  %s
178
		}"
179
	};
180

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
	private string [] sort_clauses = {
		// ALL
		"DESC(nfo:fileLastModified(?urn)) DESC(nie:contentCreated(?urn)) ASC(nie:title(?urn))",

		// CONTACTS
		"ASC(nco:fullname(?urn))",

		// APPLICATIONS
		"ASC(nie:title(?urn)) ASC(nie:comment(?urn))",

		// MUSIC
		"DESC(nfo:fileLastModified(?urn)) ASC(nie:title(?urn))",

		// IMAGES
		"DESC(nfo:fileLastModified(?urn)) ASC(nie:title(?urn))",

		// VIDEOS
		"DESC(nfo:fileLastModified(?urn)) ASC(nie:title(?urn))",

		// DOCUMENTS
		"DESC(nfo:fileLastModified(?urn)) ASC(nie:title(?urn))",

		// MAIL
		"DESC(nmo:receivedDate(?urn)) ASC(nmo:messageSubject(?urn))",

		// CALENDAR
		"DESC(nie:contentCreated(?urn))",

		// FOLDERS
		"DESC(nfo:fileLastModified(?urn)) ASC(nie:title(?urn))",

		// BOOKMARKS
		"DESC(nie:contentLastModified(?urn)) ASC(nie:title(?urn))"
	};

216 217 218 219 220
	public string criteria { get; set; }
	public uint offset { get; set; }
	public uint limit { get; set; }
	public string query { get; private set; }

221 222
	public GenericArray<string> tags { get; set; }

223 224 225 226 227
	private static Sparql.Connection connection;

	public Query () {
		try {
			connection = Sparql.Connection.get ();
228 229
		} catch (GLib.Error e) {
			warning ("Could not get Sparql connection: %s", e.message);
230
		}
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249

		tags = null;
	}

	private string get_tags_filter () {
		string filter = "";

		if (tags != null && tags.length > 0) {
			for (int i = 0; i < tags.length; i++) {
				string escaped = Tracker.Sparql.escape_string (tags[i]);

				if (filter.length > 1)
					filter += ", ";

				filter += "\"%s\"".printf (escaped);
			}
		}

		return filter;
250 251
	}

252 253 254 255 256 257 258 259 260 261 262 263
	private bool check_query_and_match_type (Type query_type, Match match_type) {
		if (query_type != Type.IMAGES && match_type == Match.NONE) {
			critical ("You can not use a non-IMAGES query (%d) with NONE matching", query_type);
			return false;
		}

		if (query_type == Type.MUSIC && !(match_type == Match.FTS_INDIRECT ||
		                                  match_type == Match.TITLES_INDIRECT)) {
			critical ("You can not use a MUSIC query with match TITLES or FTS, INDIRECT required");
			return false;
		}

264 265 266
		if ((query_type != Type.MUSIC && query_type != Type.DOCUMENTS) &&
			!(match_type == Match.NONE ||
			  match_type == Match.FTS ||
267 268
			  match_type == Match.TITLES ||
			  match_type == Match.TAGS_ONLY)) {
269
			critical ("You can not use a non-MUSIC or non-DOCUMENTS query (%d) with INDIRECT matching (%d)", query_type, match_type);
270 271 272 273 274 275 276
			return false;
		}

		return true;
	}

	public async uint get_count_async (Type query_type, Match match_type, Cancellable? cancellable = null) throws IOError
277
	requires (connection != null) {
278 279
		Sparql.Cursor cursor = null;

280 281 282 283
		if (!check_query_and_match_type (query_type, match_type)) {
			return 0;
		}

284 285 286 287 288 289 290 291 292
		// If we have tags supplied, we ONLY show files from those tags
		if (tags != null && tags.length > 0) {
			if (match_type == Match.FTS_INDIRECT ||
			    match_type == Match.TITLES_INDIRECT) {
				match_type = Match.TAGS_ONLY_INDIRECT;
			} else {
				match_type = Match.TAGS_ONLY;
			}
		}
293
		debug ("match_type:%d", match_type);
294
		if (match_type != Match.NONE &&
295 296 297
		    match_type != Match.TAGS_ONLY &&
		    match_type != Match.TAGS_ONLY_INDIRECT &&
		    (criteria == null || criteria.length < 1)) {
298
			warning ("Criteria was NULL or an empty string no query performed");
299 300 301
			return 0;
		}

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
		string match;

		if (match_type == Match.TAGS_ONLY ||
		    match_type == Match.TAGS_ONLY_INDIRECT) {
			match = match_clauses[match_type].printf (get_tags_filter ());
		} else {
			string criteria_escaped = Tracker.Sparql.escape_string (criteria);

			// FTS queries take 2 arguments for tags and fts:match
			if (match_type == Match.FTS ||
			    match_type == Match.FTS_INDIRECT) {
				match = match_clauses[match_type].printf (criteria_escaped, criteria_escaped);
			} else {
				match = match_clauses[match_type].printf (criteria_escaped);
			}
		}
318

319 320 321 322 323 324 325 326 327
		query  = "SELECT count(?urn)";

		if (where_clauses[query_type].length > 0) {
			query += " " + where_clauses[query_type].printf (match);
		}

		if (sort_clauses[query_type].length > 0) {
			query += " ORDER BY " + sort_clauses[query_type];
		}
328 329 330

		try {
			cursor = yield connection.query_async (query, null);
331
			yield cursor.next_async ();
332 333
		} catch (GLib.Error e) {
			warning ("Could not run Sparql count query: %s", e.message);
334
		}
335

336 337
		return (uint) cursor.get_integer (0);
	}
338

339
	public async Sparql.Cursor? perform_async (Type query_type, Match match_type, string[] ?args, Cancellable? cancellable = null) throws IOError
340 341 342
	requires (connection != null) {
		Sparql.Cursor cursor = null;

343 344 345 346
		if (!check_query_and_match_type (query_type, match_type)) {
			return null;
		}

347 348 349 350 351 352 353 354 355 356 357
		// If we have tags supplied, we ONLY show files from those tags
		if (tags != null && tags.length > 0) {
			if (match_type == Match.FTS_INDIRECT ||
			    match_type == Match.TITLES_INDIRECT) {
				match_type = Match.TAGS_ONLY_INDIRECT;
			} else {
				match_type = Match.TAGS_ONLY;
			}
		}

		if (match_type != Match.NONE &&
358 359 360
		    match_type != Match.TAGS_ONLY &&
		    match_type != Match.TAGS_ONLY_INDIRECT &&
		    (criteria == null || criteria.length < 1)) {
361
			warning ("Criteria was NULL or an empty string no query performed");
362 363 364 365 366 367 368 369
			return null;
		}

		if (limit < 1) {
			warning ("Limit was < 1, no query performed");
			return null;
		}

370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
		string match;

		if (match_type == Match.TAGS_ONLY ||
		    match_type == Match.TAGS_ONLY_INDIRECT) {
			match = match_clauses[match_type].printf (get_tags_filter ());
		} else {
			string criteria_escaped = Tracker.Sparql.escape_string (criteria);

			// FTS queries take 2 arguments for tags and fts:match
			if (match_type == Match.FTS ||
			    match_type == Match.FTS_INDIRECT) {
				match = match_clauses[match_type].printf (criteria_escaped, criteria_escaped);
			} else {
				match = match_clauses[match_type].printf (criteria_escaped);
			}
		}
386

387 388 389 390 391 392 393 394 395 396
		query  = "SELECT " + string.joinv (" ", args);
		if (where_clauses[query_type].length > 0) {
			query += " " + where_clauses[query_type].printf (match);
		}

		if (sort_clauses[query_type].length > 0) {
			query += " ORDER BY " + sort_clauses[query_type];
		}

		query += " OFFSET %u LIMIT %u".printf (offset, limit);
397 398 399 400

		debug ("Running query: '%s'", query);

		try {
401
			cursor = yield connection.query_async (query, null);
402 403
		} catch (GLib.Error e) {
			warning ("Could not run Sparql query: %s", e.message);
404 405
		}

406 407
		debug ("Done");

408 409 410
		return cursor;
	}
}