1 /*
2  * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3  * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 /* Documentation:
21  * http://www.sqlite.org/capi3ref.html
22  */
23 
24 /* Database layout:
25  *
26  * library
27  *   id
28  *   filename
29  *   identifier
30  *
31  * tags
32  *   id
33  *   tagname
34  *
35  * phototags
36  *   photo
37  *   tag
38  *   autotag
39  *
40  * version
41  *   version
42  */
43 /*
44 #include <glib.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <sqlite3.h>
48 #include <string.h>
49 #include "rawstudio.h"
50 #include "rs-metadata.h"
51 #include "rs-library.h"
52 #include "application.h"
53 */
54 
55 #include "rs-library.h"
56 #include "conf_interface.h"
57 #include "config.h"
58 #include "gettext.h"
59 #include <libxml/encoding.h>
60 #include <libxml/xmlwriter.h>
61 #include <sqlite3.h>
62 
63 #define LIBRARY_VERSION 2
64 #define TAGS_XML_FILE "tags.xml"
65 #define MAX_SEARCH_RESULTS 1000
66 #include "rs-types.h"
67 
68 struct _RSLibrary {
69 	GObject parent;
70 	gboolean dispose_has_run;
71 
72 	sqlite3 *db;
73 	gchar *error_init;
74 
75 	/* This mutex must be used when inserting data in a table with an
76 	   autocrementing column - which is ALWAYS for sqlite */
77 	GMutex *id_lock;
78 };
79 
80 G_DEFINE_TYPE(RSLibrary, rs_library, G_TYPE_OBJECT)
81 
82 static gint library_execute_sql(sqlite3 *db, const gchar *sql);
83 static void library_sqlite_error(sqlite3 *db, const gint result);
84 static gint library_create_tables(sqlite3 *db);
85 static gint library_find_tag_id(RSLibrary *library, const gchar *tagname);
86 static gint library_find_photo_id(RSLibrary *library, const gchar *photo);
87 static void library_photo_add_tag(RSLibrary *library, const gint photo_id, const gint tag_id, const gboolean autotag);
88 static gboolean library_is_photo_tagged(RSLibrary *library, const gint photo_id, const gint tag_id);
89 static gint library_add_photo(RSLibrary *library, const gchar *filename);
90 static gint library_add_tag(RSLibrary *library, const gchar *tagname);
91 static void library_delete_photo(RSLibrary *library, const gint photo_id);
92 static void library_delete_tag(RSLibrary *library, const gint tag_id);
93 static void library_photo_delete_tags(RSLibrary *library, const gint photo_id);
94 static void library_tag_delete_photos(RSLibrary *library, const gint tag_id);
95 static gboolean library_tag_is_used(RSLibrary *library, const gint tag_id);
96 static void library_photo_default_tags(RSLibrary *library, const gint photo_id, RSMetadata *metadata);
97 
98 static GtkWidget *tag_search_entry = NULL;
99 
100 static void
rs_library_dispose(GObject * object)101 rs_library_dispose(GObject *object)
102 {
103 	RSLibrary *library = RS_LIBRARY(object);
104 
105 	if (!library->dispose_has_run)
106 	{
107 		library->dispose_has_run = TRUE;
108 
109 		sqlite3_close(library->db);
110 
111 		g_mutex_free(library->id_lock);
112 	}
113 
114 	G_OBJECT_CLASS(rs_library_parent_class)->dispose (object);
115 }
116 
117 static void
rs_library_finalize(GObject * object)118 rs_library_finalize(GObject *object)
119 {
120 	G_OBJECT_CLASS(rs_library_parent_class)->finalize (object);
121 }
122 
123 static void
rs_library_class_init(RSLibraryClass * klass)124 rs_library_class_init(RSLibraryClass *klass)
125 {
126 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
127 
128 	sqlite3_config(SQLITE_CONFIG_SERIALIZED);
129 	object_class->dispose = rs_library_dispose;
130 	object_class->finalize = rs_library_finalize;
131 }
132 
133 gboolean
rs_library_has_database_connection(RSLibrary * library)134 rs_library_has_database_connection(RSLibrary *library)
135 {
136   if (library_execute_sql(library->db, "PRAGMA user_version;") == 0)
137     return TRUE;
138   else
139     return FALSE;
140 }
141 
142 gchar *
rs_library_get_init_error_msg(RSLibrary * library)143 rs_library_get_init_error_msg(RSLibrary *library)
144 {
145   return g_strdup(library->error_init);
146 }
147 
148 static gint
library_set_version(sqlite3 * db,gint version)149 library_set_version(sqlite3 *db, gint version)
150 {
151 	sqlite3_stmt *stmt;
152 	gint rc;
153 
154 	rc = sqlite3_prepare_v2(db, "update version set version = ?1;", -1, &stmt, NULL);
155 	rc = sqlite3_bind_int(stmt, 1, version);
156 	rc = sqlite3_step(stmt);
157 	library_sqlite_error(db, rc);
158 	sqlite3_finalize(stmt);
159 
160 	return SQLITE_OK;
161 }
162 
163 static void
library_check_version(sqlite3 * db)164 library_check_version(sqlite3 *db)
165 {
166 	sqlite3_stmt *stmt, *stmt_update;
167 	gint rc, version = 0, id;
168 	gchar *filename;
169 
170 	rc = sqlite3_prepare_v2(db, "SELECT version FROM version", -1, &stmt, NULL);
171 	rc = sqlite3_step(stmt);
172 	if (rc == SQLITE_ROW)
173 		version = sqlite3_column_int(stmt, 0);
174 	rc = sqlite3_finalize(stmt);
175 
176 	while (version < LIBRARY_VERSION)
177 	{
178 		switch (version)
179 		{
180 		case 0:
181 			/* Alter table library - add identifier column */
182 			sqlite3_prepare_v2(db, "alter table library add column identifier varchar(32)", -1, &stmt, NULL);
183 			rc = sqlite3_step(stmt);
184 			library_sqlite_error(db, rc);
185 			sqlite3_finalize(stmt);
186 
187 			/* Run through all photos in library and insert unique identifier in library */
188 			gchar *identifier;
189 			sqlite3_prepare_v2(db, "select filename from library", -1, &stmt, NULL);
190 			while (sqlite3_step(stmt) == SQLITE_ROW)
191 			{
192 				filename = (gchar *) sqlite3_column_text(stmt, 0);
193 				if (g_file_test(filename, G_FILE_TEST_EXISTS))
194 				{
195 					identifier = rs_file_checksum(filename);
196 					rc = sqlite3_prepare_v2(db, "update library set identifier = ?1 WHERE filename = ?2;", -1, &stmt_update, NULL);
197 					rc = sqlite3_bind_text(stmt_update, 1, identifier, -1, SQLITE_TRANSIENT);
198 					rc = sqlite3_bind_text(stmt_update, 2, filename, -1, SQLITE_TRANSIENT);
199 					rc = sqlite3_step(stmt_update);
200 					library_sqlite_error(db, rc);
201 					sqlite3_finalize(stmt_update);
202 					g_free(identifier);
203 				}
204 			}
205 			sqlite3_finalize(stmt);
206 
207 			library_set_version(db, version+1);
208 			break;
209 
210 		case 1:
211 			library_execute_sql(db, "BEGIN TRANSACTION;");
212 			sqlite3_prepare_v2(db, "select id,filename from library", -1, &stmt, NULL);
213 			while (sqlite3_step(stmt) == SQLITE_ROW)
214 			{
215 				id = (gint) sqlite3_column_int(stmt, 0);
216 				filename = rs_normalize_path((gchar *) sqlite3_column_text(stmt, 1));
217 				if (filename) /* FIXME: This will only work for paths that exists */
218 				{
219 					rc = sqlite3_prepare_v2(db, "update library set filename = ?1 WHERE id = ?2;", -1, &stmt_update, NULL);
220 					rc = sqlite3_bind_text(stmt_update, 1, filename, -1, SQLITE_TRANSIENT);
221 					rc = sqlite3_bind_int(stmt_update, 2, id);
222 					rc = sqlite3_step(stmt_update);
223 					library_sqlite_error(db, rc);
224 					sqlite3_finalize(stmt_update);
225 					g_free(filename);
226 				}
227 			}
228 			sqlite3_finalize(stmt);
229 			library_set_version(db, version+1);
230 			library_execute_sql(db, "COMMIT;");
231 			break;
232 
233 		default:
234 			/* We should never hit this */
235 			g_debug("Some error occured in library_check_version() - please notify developers");
236 			break;
237 		}
238 
239 		version++;
240 		g_debug("Updated library database to version %d", version);
241 	}
242 }
243 
244 static void
rs_library_init(RSLibrary * library)245 rs_library_init(RSLibrary *library)
246 {
247 	int rc;
248 
249 	gchar *database = g_strdup_printf("%s/.rawstudio/library.db", g_get_home_dir());
250 
251 	/* If unable to create database we exit */
252 	if(sqlite3_open(database, &(library->db)))
253 	{
254 		gchar *msg = g_strdup_printf(_("Could not open database %s"), database);
255 		g_debug("sqlite3 debug: %s\n", msg);
256 		if (library->error_init)
257 		  g_free(library->error_init);
258 		library->error_init = g_strdup(msg);
259 		sqlite3_close(library->db);
260 	}
261 	g_free(database);
262 
263 	if (rs_library_has_database_connection(library))
264 	{
265 	  /* This is not FULL synchronous mode as default, since all data is re-creatable by local xml files.
266 	     From the sqlite3 manual:
267 	     With synchronous OFF (0), SQLite continues without syncing as soon as it has handed data off to
268 	     the operating system. If the application running SQLite crashes, the data will be safe, but the
269 	     database might become corrupted if the operating system crashes or the computer loses power before
270 	     that data has been written to the disk surface. On the other hand,
271 	     some operations are as much as 50 or more times faster with synchronous OFF. " */
272 	  library_execute_sql(library->db, "PRAGMA synchronous = OFF;");
273 
274 	  /* Move our journal to memory, we're not doing banking for the Mafia */
275 	  library_execute_sql(library->db, "PRAGMA journal_mode = memory;");
276 
277 	  /* Place temp tables in memory */
278 	  library_execute_sql(library->db, "PRAGMA temp_store = memory;");
279 
280 	  rc = library_create_tables(library->db);
281 	  library_sqlite_error(library->db, rc);
282 
283 	  library_check_version(library->db);
284 
285 	  library->id_lock = g_mutex_new();
286 	}
287 }
288 
289 RSLibrary *
rs_library_get_singleton(void)290 rs_library_get_singleton(void)
291 {
292 	static GStaticMutex singleton_lock = G_STATIC_MUTEX_INIT;
293 	static RSLibrary *singleton = NULL;
294 
295 	g_static_mutex_lock(&singleton_lock);
296 	if (!singleton)
297 		singleton = g_object_new(RS_TYPE_LIBRARY, NULL);
298 	g_static_mutex_unlock(&singleton_lock);
299 
300 	return singleton;
301 }
302 
303 static gint
library_execute_sql(sqlite3 * db,const gchar * sql)304 library_execute_sql(sqlite3 *db, const gchar *sql)
305 {
306 	sqlite3_stmt *statement;
307 
308 	if(SQLITE_OK != sqlite3_prepare(db, sql, -1, &statement, 0))
309 		return sqlite3_errcode(db);
310 
311 	while (SQLITE_ROW == sqlite3_step(statement));
312 
313 	return sqlite3_finalize(statement);
314 }
315 
316 static void
library_sqlite_error(sqlite3 * db,gint result)317 library_sqlite_error(sqlite3 *db, gint result)
318 {
319 	if (result != SQLITE_OK && result != SQLITE_DONE)
320 	{
321 		g_warning("sqlite3 warning: %s\n", sqlite3_errmsg(db));
322 	}
323 }
324 
325 static gint
library_create_tables(sqlite3 * db)326 library_create_tables(sqlite3 *db)
327 {
328 	sqlite3_stmt *stmt;
329 	gint rc;
330 
331 	/* Create table (library) to hold all known photos */
332 	sqlite3_prepare_v2(db, "create table library (id integer primary key, filename varchar(1024), identifier varchar(32))", -1, &stmt, NULL);
333 	rc = sqlite3_step(stmt);
334 	sqlite3_finalize(stmt);
335 
336 	/* Create table (tags) with all known tags */
337 	sqlite3_prepare_v2(db, "create table tags (id integer primary key, tagname varchar(128))", -1, &stmt, NULL);
338 	rc = sqlite3_step(stmt);
339 	sqlite3_finalize(stmt);
340 
341 	/* Create table (phototags) to bind tags and photos together */
342 	sqlite3_prepare_v2(db, "create table phototags (photo integer, tag integer, autotag integer)", -1, &stmt, NULL);
343 	rc = sqlite3_step(stmt);
344 	sqlite3_finalize(stmt);
345 
346 	/* Create table (version) to help keeping track of database version */
347 	sqlite3_prepare_v2(db, "create table version (version integer)", -1, &stmt, NULL);
348 	rc = sqlite3_step(stmt);
349 	sqlite3_finalize(stmt);
350 
351 	rc = sqlite3_prepare_v2(db, "select * from version", -1, &stmt, NULL);
352 	rc = sqlite3_step(stmt);
353 	sqlite3_finalize(stmt);
354 	if (rc != SQLITE_ROW)
355 	{
356 		/* Set current version */
357 		rc = sqlite3_prepare_v2(db, "insert into version (version) values (?1);", -1, &stmt, NULL);
358 		rc = sqlite3_bind_int(stmt, 1, LIBRARY_VERSION);
359 		rc = sqlite3_step(stmt);
360 		sqlite3_finalize(stmt);
361 
362 		rc = sqlite3_prepare_v2(db, "select identifier from library", -1, &stmt, NULL);
363 		rc = sqlite3_step(stmt);
364 		sqlite3_finalize(stmt);
365 
366 		/* Check if library.identifier exists */
367 		if (rc == SQLITE_MISUSE)
368 		{
369 			library_set_version(db, 0);
370 		}
371 	}
372 
373 	return SQLITE_OK;
374 }
375 
376 static gint
library_find_tag_id(RSLibrary * library,const gchar * tagname)377 library_find_tag_id(RSLibrary *library, const gchar *tagname)
378 {
379 	sqlite3 *db = library->db;
380 	sqlite3_stmt *stmt;
381 	gint rc, tag_id = -1;
382 
383 	rc = sqlite3_prepare_v2(db, "SELECT id FROM tags WHERE tagname = ?1;", -1, &stmt, NULL);
384 	rc = sqlite3_bind_text(stmt, 1, tagname, -1, SQLITE_TRANSIENT);
385 	rc = sqlite3_step(stmt);
386 	if (rc == SQLITE_ROW)
387 		tag_id = sqlite3_column_int(stmt, 0);
388 	rc = sqlite3_finalize(stmt);
389 	return tag_id;
390 }
391 
392 static gint
library_find_photo_id(RSLibrary * library,const gchar * photo)393 library_find_photo_id(RSLibrary *library, const gchar *photo)
394 {
395 	sqlite3 *db = library->db;
396 	sqlite3_stmt *stmt;
397 	gint rc, photo_id = -1;
398 
399 	rc = sqlite3_prepare_v2(db, "SELECT id FROM library WHERE filename = ?1;", -1, &stmt, NULL);
400 	rc = sqlite3_bind_text(stmt, 1, photo, -1, SQLITE_TRANSIENT);
401 	library_sqlite_error(db, rc);
402 	rc = sqlite3_step(stmt);
403 	if (rc == SQLITE_ROW)
404 		photo_id = sqlite3_column_int(stmt, 0);
405 	rc = sqlite3_finalize(stmt);
406 	return photo_id;
407 }
408 
409 static void
library_photo_add_tag(RSLibrary * library,const gint photo_id,const gint tag_id,const gboolean autotag)410 library_photo_add_tag(RSLibrary *library, const gint photo_id, const gint tag_id, const gboolean autotag)
411 {
412 	sqlite3 *db = library->db;
413 	gint rc;
414 	sqlite3_stmt *stmt;
415 
416 	gint autotag_tag = 0;
417 	if (autotag)
418 		autotag_tag = 1;
419 
420 	g_mutex_lock(library->id_lock);
421 	rc = sqlite3_prepare_v2(db, "INSERT INTO phototags (photo, tag, autotag) VALUES (?1, ?2, ?3);", -1, &stmt, NULL);
422 	rc = sqlite3_bind_int (stmt, 1, photo_id);
423 	rc = sqlite3_bind_int (stmt, 2, tag_id);
424 	rc = sqlite3_bind_int (stmt, 3, autotag_tag);
425 	rc = sqlite3_step(stmt);
426 	g_mutex_unlock(library->id_lock);
427 	if (rc != SQLITE_DONE)
428 		library_sqlite_error(db, rc);
429 	sqlite3_finalize(stmt);
430 }
431 
432 static gboolean
library_is_photo_tagged(RSLibrary * library,gint photo_id,gint tag_id)433 library_is_photo_tagged(RSLibrary *library, gint photo_id, gint tag_id)
434 {
435 	sqlite3 *db = library->db;
436 	gint rc;
437 	sqlite3_stmt *stmt;
438 
439 	rc = sqlite3_prepare_v2(db, "SELECT * FROM phototags WHERE photo = ?1 AND tag = ?2;", -1, &stmt, NULL);
440 	rc = sqlite3_bind_int (stmt, 1, photo_id);
441 	rc = sqlite3_bind_int (stmt, 2, tag_id);
442 	rc = sqlite3_step(stmt);
443 	sqlite3_finalize(stmt);
444 
445 	if (rc == SQLITE_ROW)
446 		return TRUE;
447 	else
448 		return FALSE;
449 }
450 
451 static void
got_checksum(const gchar * checksum,gpointer user_data)452 got_checksum(const gchar *checksum, gpointer user_data)
453 {
454 	RSLibrary *library = rs_library_get_singleton();
455 	sqlite3 *db = library->db;
456 	sqlite3_stmt *stmt;
457 
458 	sqlite3_prepare_v2(db, "UPDATE LIBRARY SET  identifier=?1 WHERE id=?2;", -1, &stmt, NULL);
459 	sqlite3_bind_text(stmt, 1, checksum, -1, SQLITE_TRANSIENT);
460 	sqlite3_bind_int(stmt, 2, GPOINTER_TO_INT(user_data));
461 	sqlite3_step(stmt);
462 	sqlite3_finalize(stmt);
463 }
464 
465 static gint
library_add_photo(RSLibrary * library,const gchar * filename)466 library_add_photo(RSLibrary *library, const gchar *filename)
467 {
468 	gint id;
469 	sqlite3 *db = library->db;
470 	gint rc;
471 	sqlite3_stmt *stmt;
472 
473 	g_mutex_lock(library->id_lock);
474 	sqlite3_prepare_v2(db, "INSERT INTO library (filename) VALUES (?1);", -1, &stmt, NULL);
475 	rc = sqlite3_bind_text(stmt, 1, filename, -1, SQLITE_TRANSIENT);
476 	rc = sqlite3_step(stmt);
477 	id = sqlite3_last_insert_rowid(db);
478 	g_mutex_unlock(library->id_lock);
479 	if (rc != SQLITE_DONE)
480 		library_sqlite_error(db, rc);
481 	sqlite3_finalize(stmt);
482 
483 	rs_io_idle_read_checksum(filename, -1, got_checksum, GINT_TO_POINTER(id));
484 
485 	return id;
486 }
487 
488 static gint
library_add_tag(RSLibrary * library,const gchar * tagname)489 library_add_tag(RSLibrary *library, const gchar *tagname)
490 {
491 	gint id;
492 	sqlite3 *db = library->db;
493 	gint rc;
494 	sqlite3_stmt *stmt;
495 
496 	g_mutex_lock(library->id_lock);
497 	sqlite3_prepare_v2(db, "INSERT INTO tags (tagname) VALUES (?1);", -1, &stmt, NULL);
498 	rc = sqlite3_bind_text(stmt, 1, tagname, -1, SQLITE_TRANSIENT);
499 	rc = sqlite3_step(stmt);
500 	id = sqlite3_last_insert_rowid(db);
501 	g_mutex_unlock(library->id_lock);
502 	if (rc != SQLITE_DONE)
503 		library_sqlite_error(db, rc);
504 	sqlite3_finalize(stmt);
505 
506 	return id;
507 }
508 
509 static void
library_delete_photo(RSLibrary * library,gint photo_id)510 library_delete_photo(RSLibrary *library, gint photo_id)
511 {
512 	sqlite3 *db = library->db;
513 	sqlite3_stmt *stmt;
514 	gint rc;
515 
516 	rc = sqlite3_prepare_v2(db, "DELETE FROM library WHERE id = ?1;", -1, &stmt, NULL);
517 	rc = sqlite3_bind_int(stmt, 1, photo_id);
518 	library_sqlite_error(db, rc);
519 	rc = sqlite3_step(stmt);
520 	if (rc != SQLITE_DONE)
521 		library_sqlite_error(db, rc);
522 	rc = sqlite3_finalize(stmt);
523 }
524 
525 static void
library_delete_tag(RSLibrary * library,gint tag_id)526 library_delete_tag(RSLibrary *library, gint tag_id)
527 {
528 	sqlite3 *db = library->db;
529 	sqlite3_stmt *stmt;
530 	gint rc;
531 
532 	rc = sqlite3_prepare_v2(db, "DELETE FROM library WHERE filename = ?1;", -1, &stmt, NULL);
533 	rc = sqlite3_bind_int(stmt, 1, tag_id);
534 	library_sqlite_error(db, rc);
535 	rc = sqlite3_step(stmt);
536 	if (rc != SQLITE_DONE)
537 		library_sqlite_error(db, rc);
538 	rc = sqlite3_finalize(stmt);
539 }
540 
541 static void
library_photo_delete_tags(RSLibrary * library,gint photo_id)542 library_photo_delete_tags(RSLibrary *library, gint photo_id)
543 {
544 	sqlite3 *db = library->db;
545 	sqlite3_stmt *stmt;
546 	gint rc;
547 
548 	rc = sqlite3_prepare_v2(db, "DELETE FROM phototags WHERE photo = ?1;", -1, &stmt, NULL);
549 	rc = sqlite3_bind_int(stmt, 1, photo_id);
550 	library_sqlite_error(db, rc);
551 	rc = sqlite3_step(stmt);
552 	if (rc != SQLITE_DONE)
553 		library_sqlite_error(db, rc);
554 	rc = sqlite3_finalize(stmt);
555 }
556 
557 static void
library_tag_delete_photos(RSLibrary * library,gint tag_id)558 library_tag_delete_photos(RSLibrary *library, gint tag_id)
559 {
560 	sqlite3 *db = library->db;
561 	sqlite3_stmt *stmt;
562 	gint rc;
563 
564 	rc = sqlite3_prepare_v2(db, "DELETE FROM phototags WHERE tag = ?1;", -1, &stmt, NULL);
565 	rc = sqlite3_bind_int(stmt, 1, tag_id);
566 	library_sqlite_error(db, rc);
567 	rc = sqlite3_step(stmt);
568 	if (rc != SQLITE_DONE)
569 		library_sqlite_error(db, rc);
570 	rc = sqlite3_finalize(stmt);
571 }
572 
573 static gboolean
library_tag_is_used(RSLibrary * library,gint tag_id)574 library_tag_is_used(RSLibrary *library, gint tag_id)
575 {
576 	sqlite3 *db = library->db;
577 	gint rc;
578 	sqlite3_stmt *stmt;
579 
580 	rc = sqlite3_prepare_v2(db, "SELECT * FROM phototags WHERE tag = ?1;", -1, &stmt, NULL);
581 	rc = sqlite3_bind_int (stmt, 1, tag_id);
582 	rc = sqlite3_step(stmt);
583 	sqlite3_finalize(stmt);
584 
585 	if (rc == SQLITE_ROW)
586 		return TRUE;
587 	else
588 		return FALSE;
589 }
590 
591 gint
rs_library_add_photo(RSLibrary * library,const gchar * filename)592 rs_library_add_photo(RSLibrary *library, const gchar *filename)
593 {
594 	gint photo_id;
595 
596 	g_assert(RS_IS_LIBRARY(library));
597 	if (!rs_library_has_database_connection(library)) return 0; /* FIXME */
598 
599 	photo_id = library_find_photo_id(library, filename);
600 	if (photo_id == -1)
601 	{
602 		g_debug("Adding photo to library: %s",filename);
603 		photo_id = library_add_photo(library, filename);
604 	}
605 
606 	return photo_id;
607 }
608 
609 gint
rs_library_add_tag(RSLibrary * library,const gchar * tagname)610 rs_library_add_tag(RSLibrary *library, const gchar *tagname)
611 {
612 	gint tag_id;
613 
614 	g_assert(RS_IS_LIBRARY(library));
615 	if (!rs_library_has_database_connection(library)) return 0; /* FIXME */
616 
617 	tag_id = library_find_tag_id(library, tagname);
618 	if (tag_id == -1)
619 	{
620 		g_debug("Adding tag to tags: %s",tagname);
621 		tag_id = library_add_tag(library, tagname);
622 	}
623 
624 	return tag_id;
625 }
626 
627 void
rs_library_photo_add_tag(RSLibrary * library,const gchar * filename,gint tag_id,const gboolean autotag)628 rs_library_photo_add_tag(RSLibrary *library, const gchar *filename, gint tag_id, const gboolean autotag)
629 {
630 	g_assert(RS_IS_LIBRARY(library));
631 	if (!rs_library_has_database_connection(library)) return;
632 
633 	gint photo_id;
634 
635 	if (tag_id == -1)
636 	{
637 		g_warning("Tag not known...");
638 		return;
639 	}
640 
641 	photo_id = library_find_photo_id(library, filename);
642 	if (photo_id == -1)
643 	{
644 		g_warning("Photo not known...");
645 		return;
646 	}
647 
648 	if (!library_is_photo_tagged(library, photo_id, tag_id))
649 		library_photo_add_tag(library, photo_id, tag_id, autotag);
650 
651 	return;
652 }
653 
654 void
rs_library_delete_photo(RSLibrary * library,const gchar * photo)655 rs_library_delete_photo(RSLibrary *library, const gchar *photo)
656 {
657 	g_assert(RS_IS_LIBRARY(library));
658 	if (!rs_library_has_database_connection(library)) return;
659 
660 	gint photo_id = -1;
661 
662 	photo_id = library_find_photo_id(library, photo);
663 	if (photo_id == -1)
664 	{
665 		g_warning("Photo not known...");
666 		return;
667 	}
668 
669 	library_photo_delete_tags(library, photo_id);
670 	library_delete_photo(library, photo_id);
671 	rs_library_backup_tags(library, photo);
672 }
673 
674 gboolean
rs_library_delete_tag(RSLibrary * library,const gchar * tag,const gboolean force)675 rs_library_delete_tag(RSLibrary *library, const gchar *tag, const gboolean force)
676 {
677 	g_assert(RS_IS_LIBRARY(library));
678 	if (!rs_library_has_database_connection(library)) return FALSE;
679 
680 	gint tag_id = -1;
681 
682 	tag_id = library_find_tag_id(library, tag);
683 	if (tag_id == -1)
684 	{
685 		g_warning("Tag not known...");
686 		return FALSE;
687 	}
688 
689 	if (library_tag_is_used(library, tag_id))
690 		if (force)
691 		{
692 			library_tag_delete_photos(library, tag_id);
693 			library_delete_tag(library, tag_id);
694 		}
695 		else
696 		{
697 			g_warning("Tag is in use...");
698 			return FALSE;
699 		}
700 	else
701 		library_delete_tag(library, tag_id);
702 	return TRUE;
703 }
704 
705 GList *
rs_library_search(RSLibrary * library,GList * tags)706 rs_library_search(RSLibrary *library, GList *tags)
707 {
708 	g_assert(RS_IS_LIBRARY(library));
709 	if (!rs_library_has_database_connection(library)) return NULL;
710 
711 	sqlite3_stmt *stmt;
712 	gint rc;
713 	sqlite3 *db = library->db;
714 	gchar *tag;
715 	gint n, num_tags = g_list_length(tags);
716 	GList *photos = NULL;
717 	GTimer *gt = g_timer_new();
718 	gchar *filename;
719 
720 	sqlite3_prepare_v2(db, "create temp table filter (photo integer)", -1, &stmt, NULL);
721 	rc = sqlite3_step(stmt);
722 	sqlite3_finalize(stmt);
723 	library_sqlite_error(db, rc);
724 
725 	for (n = 0; n < num_tags; n++)
726 	{
727 		tag = (gchar *) g_list_nth_data(tags, n);
728 
729 		g_mutex_lock(library->id_lock);
730 		sqlite3_prepare_v2(db, "insert into filter select phototags.photo from phototags, tags where phototags.tag = tags.id and lower(tags.tagname) = lower(?1) ;", -1, &stmt, NULL);
731 		rc = sqlite3_bind_text(stmt, 1, tag, -1, SQLITE_TRANSIENT);
732 		rc = sqlite3_step(stmt);
733 		sqlite3_finalize(stmt);
734 		g_mutex_unlock(library->id_lock);
735 	}
736 
737 	sqlite3_prepare_v2(db, "create temp table result (photo integer, count integer)", -1, &stmt, NULL);
738 	rc = sqlite3_step(stmt);
739 	sqlite3_finalize(stmt);
740 	library_sqlite_error(db, rc);
741 
742 	g_mutex_lock(library->id_lock);
743 	sqlite3_prepare_v2(db, "insert into result select photo, count(photo) from filter group by photo;", -1, &stmt, NULL);
744 	rc = sqlite3_step(stmt);
745 	sqlite3_finalize(stmt);
746 	g_mutex_unlock(library->id_lock);
747 	library_sqlite_error(db, rc);
748 
749 	sqlite3_prepare_v2(db, "select library.filename from library,result where library.id = result.photo and result.count = ?1 order by library.filename;", -1, &stmt, NULL);
750         rc = sqlite3_bind_int(stmt, 1, num_tags);
751 
752 	gint count = 0;
753 	while (sqlite3_step(stmt) == SQLITE_ROW && count < MAX_SEARCH_RESULTS)
754 	{
755 		filename = g_strdup((gchar *) sqlite3_column_text(stmt, 0));
756 		if (g_file_test(filename, G_FILE_TEST_EXISTS))
757 		{
758 			photos = g_list_append(photos, filename);
759 			count++;
760 		}
761 	}
762 	sqlite3_finalize(stmt);
763 	library_sqlite_error(db, rc);
764 
765 	/* Empty filter */
766 	sqlite3_prepare_v2(db, "delete from filter;", -1, &stmt, NULL);
767 	rc = sqlite3_step(stmt);
768 	sqlite3_finalize(stmt);
769 	library_sqlite_error(db, rc);
770 
771 	/* Empty result */
772 	sqlite3_prepare_v2(db, "delete from result;", -1, &stmt, NULL);
773 	rc = sqlite3_step(stmt);
774 	sqlite3_finalize(stmt);
775 	library_sqlite_error(db, rc);
776 
777 	g_debug("Search in library took %.03f seconds", g_timer_elapsed(gt, NULL));
778 	g_timer_destroy(gt);
779 
780 	return photos;
781 }
782 
783 static void
library_photo_default_tags(RSLibrary * library,const gint photo_id,RSMetadata * metadata)784 library_photo_default_tags(RSLibrary *library, const gint photo_id, RSMetadata *metadata)
785 {
786 	g_assert(RS_IS_LIBRARY(library));
787 
788 	GList *tags = NULL;
789 
790 	if (metadata->make_ascii)
791 	{
792 		GList *temp = rs_split_string(metadata->make_ascii, " ");
793 		tags = g_list_concat(tags, temp);
794 	}
795 	if (metadata->model_ascii)
796 	{
797 		GList *temp = rs_split_string(metadata->model_ascii, " ");
798 		tags = g_list_concat(tags, temp);
799 	}
800 	if (metadata->lens_min_focal != -1 && metadata->lens_max_focal != -1)
801 	{
802 		gchar *lens = NULL;
803 		if (metadata->lens_min_focal == metadata->lens_max_focal)
804 			lens = g_strdup_printf("%dmm",(gint) metadata->lens_min_focal);
805 		else
806 			lens = g_strdup_printf("%d-%dmm",(gint) metadata->lens_min_focal, (gint) metadata->lens_max_focal);
807 		tags = g_list_append(tags, g_strdup(lens));
808 		g_free(lens);
809 	}
810 	if (metadata->focallength > 0)
811 	{
812 		gchar *text = NULL;
813 		if (metadata->focallength < 50)
814 		  text = g_strdup(_("wideangle"));
815 		else
816 		  text = g_strdup(_("telephoto"));
817 		tags = g_list_append(tags, g_strdup(text));
818 		g_free(text);
819 	}
820 	if (metadata->timestamp != -1)
821 	{
822 		gchar *year = NULL;
823 		gchar *month = NULL;
824 		GDate *date = g_date_new();
825 		g_date_set_time_t(date, metadata->timestamp);
826 		year = g_strdup_printf("%d", g_date_get_year(date));
827 		gint m = g_date_get_month(date);
828 
829 		switch (m)
830 		{
831 		case 1:
832 			month = g_strdup(_("January")); /* FIXME: There may be a better way to do this */
833 			break;
834 		case 2:
835 			month = g_strdup(_("February"));
836 			break;
837 		case 3:
838 			month = g_strdup(_("March"));
839 			break;
840 		case 4:
841 			month = g_strdup(_("April"));
842 			break;
843 		case 5:
844 			month = g_strdup(_("May"));
845 			break;
846 		case 6:
847 			month = g_strdup(_("June"));
848 			break;
849 		case 7:
850 			month = g_strdup(_("July"));
851 			break;
852 		case 8:
853 			month = g_strdup(_("August"));
854 			break;
855 		case 9:
856 			month = g_strdup(_("September"));
857 			break;
858 		case 10:
859 			month = g_strdup(_("October"));
860 			break;
861 		case 11:
862 			month = g_strdup(_("November"));
863 			break;
864 		case 12:
865 			month = g_strdup(_("December"));
866 			break;
867 		}
868 
869 		tags = g_list_append(tags, g_strdup(year));
870 		tags = g_list_append(tags, g_strdup(month));
871 
872 		g_date_free(date);
873 		g_free(year);
874 		g_free(month);
875 	}
876 
877 	gint i, j;
878 	library_execute_sql(library->db, "BEGIN TRANSACTION;");
879 	gint *used_tags = g_malloc(g_list_length(tags) * sizeof(gint));
880 	for(i = 0; i < g_list_length(tags); i++)
881 	{
882 		gchar *tag = (gchar *) g_list_nth_data(tags, i);
883 		gint tag_id = rs_library_add_tag(library, tag);
884 
885 		/* Check if tag has already been added */
886 		gboolean used = FALSE;
887 		for (j = 0; j < i; j++)
888 			if (tag_id == used_tags[j])
889 				used = TRUE;
890 
891 		if (!used)
892 			library_photo_add_tag(library, photo_id, tag_id, TRUE);
893 		used_tags[i] = tag_id;
894 		g_free(tag);
895 	}
896 	g_free(used_tags);
897 	library_execute_sql(library->db, "COMMIT;");
898 	g_list_free(tags);
899 }
900 
901 GList *
rs_library_photo_tags(RSLibrary * library,const gchar * photo,const gboolean autotag)902 rs_library_photo_tags(RSLibrary *library, const gchar *photo, const gboolean autotag)
903 {
904 	g_assert(RS_IS_LIBRARY(library));
905 	if (!rs_library_has_database_connection(library)) return NULL;
906 
907 	sqlite3_stmt *stmt;
908 	gint rc;
909 	sqlite3 *db = library->db;
910 	GList *tags = NULL;
911 
912 	if (autotag)
913 	{
914 		sqlite3_prepare_v2(db, "select tags.tagname from library,phototags,tags WHERE library.id=phototags.photo and phototags.tag=tags.id and library.filename = ?1;", -1, &stmt, NULL);
915 		rc = sqlite3_bind_text(stmt, 1, photo, -1, NULL);
916 	}
917 	else
918 	{
919 		sqlite3_prepare_v2(db, "select tags.tagname from library,phototags,tags WHERE library.id=phototags.photo and phototags.tag=tags.id and library.filename = ?1 and phototags.autotag = 0;", -1, &stmt, NULL);
920 		rc = sqlite3_bind_text(stmt, 1, photo, -1, NULL);
921 	}
922 	while (sqlite3_step(stmt) == SQLITE_ROW)
923 		tags = g_list_append(tags, g_strdup((gchar *) sqlite3_column_text(stmt, 0)));
924 	sqlite3_finalize(stmt);
925 	library_sqlite_error(db, rc);
926 
927 	return tags;
928 }
929 
930 GList *
rs_library_find_tag(RSLibrary * library,const gchar * tag)931 rs_library_find_tag(RSLibrary *library, const gchar *tag)
932 {
933 	g_assert(RS_IS_LIBRARY(library));
934 	if (!rs_library_has_database_connection(library)) return NULL;
935 
936 	sqlite3_stmt *stmt;
937 	gint rc;
938 	sqlite3 *db = library->db;
939 	GList *tags = NULL;
940 
941 	rc = sqlite3_prepare_v2(db, "select tags.tagname from tags WHERE tags.tagname like ?1 order by tags.tagname;", -1, &stmt, NULL);
942 	gchar *like = g_strdup_printf("%%%s%%", tag);
943         rc = sqlite3_bind_text(stmt, 1, like, -1, NULL);
944 	library_sqlite_error(db, rc);
945 
946 	while (sqlite3_step(stmt) == SQLITE_ROW)
947 		tags = g_list_append(tags, g_strdup((gchar *) sqlite3_column_text(stmt, 0)));
948 	sqlite3_finalize(stmt);
949 	library_sqlite_error(db, rc);
950 
951 	g_free(like);
952 
953 	return tags;
954 }
955 
956 
957 gboolean
rs_library_set_tag_search(gchar * str)958 rs_library_set_tag_search(gchar *str)
959 {
960 	if (!str)
961 		return FALSE;
962 	gtk_entry_set_text(GTK_ENTRY(tag_search_entry), str);
963 	return TRUE;
964 }
965 
966 void
rs_library_add_photo_with_metadata(RSLibrary * library,const gchar * photo,RSMetadata * metadata)967 rs_library_add_photo_with_metadata(RSLibrary *library, const gchar *photo, RSMetadata *metadata)
968 {
969 	if (!rs_library_has_database_connection(library)) return;
970 
971 	/* Bail out if we already know the photo */
972 	if (library_find_photo_id(library, photo) > -1)
973 		return;
974 
975 	gint photo_id = library_add_photo(library, photo);
976 	library_photo_default_tags(library, photo_id, metadata);
977 }
978 
979 static GStaticMutex backup_lock = G_STATIC_MUTEX_INIT;
980 
981 void
rs_library_backup_tags(RSLibrary * library,const gchar * photo_filename)982 rs_library_backup_tags(RSLibrary *library, const gchar *photo_filename)
983 {
984 	if (!rs_library_has_database_connection(library)) return;
985 
986 	sqlite3 *db = library->db;
987 	sqlite3_stmt *stmt;
988 	gint rc;
989 	gchar *filename = NULL, *checksum, *tag, *t_filename;
990 	gint autotag;
991 	gchar *directory = g_path_get_dirname(photo_filename);
992 	gchar *dotdir = rs_dotdir_get(photo_filename);
993 
994 	g_static_mutex_lock (&backup_lock);
995 
996 	if (!dotdir)
997 		return;
998 	GString *gs = g_string_new(dotdir);
999 	g_string_append(gs, G_DIR_SEPARATOR_S);
1000 	g_string_append(gs, TAGS_XML_FILE);
1001 	gchar *xmlfile = gs->str;
1002 	g_string_free(gs, FALSE);
1003 
1004 	xmlTextWriterPtr writer;
1005 
1006 	writer = xmlNewTextWriterFilename(xmlfile, 0);
1007 	if (!writer)
1008 	{
1009 		g_free(directory);
1010 		g_free(dotdir);
1011 		g_free(xmlfile);
1012 		g_static_mutex_unlock (&backup_lock);
1013 		return;
1014 	}
1015 
1016 	xmlTextWriterSetIndent(writer, 1);
1017 	xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
1018 	xmlTextWriterStartElement(writer, BAD_CAST "rawstudio-tags");
1019 	xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "version", "%d", LIBRARY_VERSION);
1020 
1021 	const gchar *temp = g_strdup_printf("%s/%%", directory);
1022 	rc = sqlite3_prepare_v2(db, "select library.filename,library.identifier,tags.tagname,phototags.autotag from library,phototags,tags where library.filename like ?1 and phototags.photo = library.id and tags.id = phototags.tag order by library.filename;", -1, &stmt, NULL);
1023 	rc = sqlite3_bind_text(stmt, 1, temp, -1, SQLITE_TRANSIENT);
1024 	library_sqlite_error(db, rc);
1025 	while (sqlite3_step(stmt) == SQLITE_ROW)
1026 	{
1027 		t_filename = g_path_get_basename((gchar *) sqlite3_column_text(stmt, 0));
1028 		if (g_strcmp0(t_filename, filename) != 0 || filename == NULL)
1029 		{
1030 			if (filename != NULL)
1031 				xmlTextWriterEndElement(writer);
1032 			filename = t_filename;
1033 			xmlTextWriterStartElement(writer, BAD_CAST "file");
1034 			xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "name", "%s", filename);
1035 			checksum = (gchar *) sqlite3_column_text(stmt, 1);
1036 			xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "checksum", "%s", checksum);
1037 		}
1038 
1039 		tag = (gchar *) sqlite3_column_text(stmt, 2);
1040 		autotag = (gint) sqlite3_column_int(stmt, 3);
1041 		xmlTextWriterStartElement(writer, BAD_CAST "tag");
1042 		xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "name", "%s", tag);
1043 		xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "auto", "%d", autotag);
1044 		xmlTextWriterEndElement(writer);
1045 	}
1046 	xmlTextWriterEndElement(writer);
1047 
1048 	rc = sqlite3_finalize(stmt);
1049 
1050 	xmlTextWriterEndDocument(writer);
1051 	xmlFreeTextWriter(writer);
1052 	g_free(directory);
1053 	g_free(dotdir);
1054 	g_free(xmlfile);
1055 	g_static_mutex_unlock (&backup_lock);
1056 	return;
1057 }
1058 
1059 void
rs_library_restore_tags(const gchar * directory)1060 rs_library_restore_tags(const gchar *directory)
1061 {
1062 	RSLibrary *library = rs_library_get_singleton();
1063 
1064 	if (!rs_library_has_database_connection(library)) return;
1065 
1066 	gchar *dotdir = rs_dotdir_get(directory);
1067 
1068 	if (!dotdir)
1069 		return;
1070 	GString *gs = g_string_new(dotdir);
1071 	g_string_append(gs, G_DIR_SEPARATOR_S);
1072 	g_string_append(gs, TAGS_XML_FILE);
1073 	gchar *xmlfile = gs->str;
1074 	g_string_free(gs, FALSE);
1075 
1076 	if (!g_file_test(xmlfile, G_FILE_TEST_EXISTS))
1077 	{
1078 		g_free(dotdir);
1079 		g_free(xmlfile);
1080 		return;
1081 	}
1082 
1083 	xmlDocPtr doc;
1084 	xmlNodePtr cur, cur2;
1085 	xmlChar *val;
1086 	gint version = 0;
1087 
1088 	gchar *filename, *identifier, *tagname;
1089 	gint autotag, photoid, tagid;
1090 
1091 	doc = xmlParseFile(xmlfile);
1092 	if (!doc)
1093 		return;
1094 
1095 	cur = xmlDocGetRootElement(doc);
1096 
1097 	if ((!xmlStrcmp(cur->name, BAD_CAST "rawstudio-tags")))
1098 	{
1099 		val = xmlGetProp(cur, BAD_CAST "version");
1100 		if (val)
1101 			version = atoi((gchar *) val);
1102 		if (version > LIBRARY_VERSION)
1103 		{
1104 			xmlFree(val);
1105 			g_free(dotdir);
1106 			g_free(xmlfile);
1107 			xmlFreeDoc(doc);
1108 			return;
1109 		}
1110 	}
1111 
1112 	cur = cur->xmlChildrenNode;
1113 	while(cur)
1114 	{
1115 		if ((!xmlStrcmp(cur->name, BAD_CAST "file")))
1116 		{
1117 			val = xmlGetProp(cur, BAD_CAST "name");
1118 			filename = g_build_filename(directory, val, NULL);
1119 			xmlFree(val);
1120 
1121 			photoid = library_find_photo_id(library, filename);
1122 			if ( photoid == -1 && g_file_test(filename, G_FILE_TEST_EXISTS))
1123 			{
1124 				photoid = rs_library_add_photo(library, filename);
1125 
1126 				val = xmlGetProp(cur, BAD_CAST "checksum");
1127 				identifier = (gchar *) val;
1128 
1129 				cur2 = cur->xmlChildrenNode;
1130 				while(cur2)
1131 				{
1132 					if ((!xmlStrcmp(cur2->name, BAD_CAST "tag")))
1133 					{
1134 						val = xmlGetProp(cur2, BAD_CAST "name");
1135 						tagname =(gchar*) val;
1136 						tagid = library_find_tag_id(library, tagname);
1137 						if ( tagid == -1)
1138 							tagid = rs_library_add_tag(library, tagname);
1139 
1140 						val = xmlGetProp(cur2, BAD_CAST "auto");
1141 						autotag = atoi((gchar *) val);
1142 						xmlFree(val);
1143 
1144 						library_photo_add_tag(library, photoid, tagid, (autotag == 1));
1145 
1146 						xmlFree(tagname);
1147 					}
1148 					cur2 = cur2->next;
1149 				}
1150 				xmlFree(identifier);
1151 			}
1152 			g_free(filename);
1153 		}
1154 		cur = cur->next;
1155 	}
1156 
1157 	g_free(dotdir);
1158 	g_free(xmlfile);
1159 	xmlFreeDoc(doc);
1160 	return;
1161 }
1162