1 /* nautilus-tag-manager.c
2 *
3 * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com>
4 * Copyright (C) 2020 Sam Thursfield <sam@afuera.me.uk>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "nautilus-tag-manager.h"
21 #include "nautilus-file.h"
22 #include "nautilus-file-undo-operations.h"
23 #include "nautilus-file-undo-manager.h"
24 #include "nautilus-tracker-utilities.h"
25 #define DEBUG_FLAG NAUTILUS_DEBUG_TAG_MANAGER
26 #include "nautilus-debug.h"
27
28 #include <gio/gunixinputstream.h>
29 #include <tracker-sparql.h>
30
31 #include "config.h"
32
33 struct _NautilusTagManager
34 {
35 GObject object;
36
37 gboolean database_ok;
38 TrackerSparqlConnection *db;
39 TrackerNotifier *notifier;
40
41 TrackerSparqlStatement *query_starred_files;
42 TrackerSparqlStatement *query_file_is_starred;
43
44 GHashTable *starred_file_uris;
45 GFile *home;
46
47 GCancellable *cancellable;
48 };
49
50 G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
51
52 typedef struct
53 {
54 NautilusTagManager *tag_manager;
55 GTask *task;
56 GList *selection;
57 gboolean star;
58 } UpdateData;
59
60 enum
61 {
62 STARRED_CHANGED,
63 LAST_SIGNAL
64 };
65
66 #define QUERY_STARRED_FILES \
67 "SELECT ?file " \
68 "{ " \
69 " ?file a nautilus:File ; " \
70 " nautilus:starred true . " \
71 "}"
72
73 #define QUERY_FILE_IS_STARRED \
74 "ASK " \
75 "{ " \
76 " ~file a nautilus:File ; " \
77 " nautilus:starred true . " \
78 "}"
79
80 static guint signals[LAST_SIGNAL];
81
82 /* Limit to 10MB output from Tracker -- surely, nobody has over a million starred files. */
83 #define TRACKER2_MAX_IMPORT_BYTES 10 * 1024 * 1024
84
85 static gchar *
tracker2_migration_stamp(void)86 tracker2_migration_stamp (void)
87 {
88 return g_build_filename (g_get_user_data_dir (), "nautilus", "tracker2-migration-complete", NULL);
89 }
90
91 static void
start_query_or_update(TrackerSparqlConnection * db,GString * query,GAsyncReadyCallback callback,gpointer user_data,gboolean is_query,GCancellable * cancellable)92 start_query_or_update (TrackerSparqlConnection *db,
93 GString *query,
94 GAsyncReadyCallback callback,
95 gpointer user_data,
96 gboolean is_query,
97 GCancellable *cancellable)
98 {
99 g_autoptr (GError) error = NULL;
100
101 if (!db)
102 {
103 g_message ("nautilus-tag-manager: No Tracker connection");
104 return;
105 }
106
107 if (is_query)
108 {
109 tracker_sparql_connection_query_async (db,
110 query->str,
111 cancellable,
112 callback,
113 user_data);
114 }
115 else
116 {
117 tracker_sparql_connection_update_async (db,
118 query->str,
119 cancellable,
120 callback,
121 user_data);
122 }
123 }
124
125 static void
on_update_callback(GObject * object,GAsyncResult * result,gpointer user_data)126 on_update_callback (GObject *object,
127 GAsyncResult *result,
128 gpointer user_data)
129 {
130 TrackerSparqlConnection *db;
131 GError *error;
132 UpdateData *data;
133
134 data = user_data;
135
136 error = NULL;
137
138 db = TRACKER_SPARQL_CONNECTION (object);
139
140 tracker_sparql_connection_update_finish (db, result, &error);
141
142 if (error == NULL)
143 {
144 /* FIXME: make sure data->tag_manager->starred_file_uris is up to date */
145
146 if (!nautilus_file_undo_manager_is_operating ())
147 {
148 NautilusFileUndoInfo *undo_info;
149
150 undo_info = nautilus_file_undo_info_starred_new (data->selection, data->star);
151 nautilus_file_undo_manager_set_action (undo_info);
152
153 g_object_unref (undo_info);
154 }
155
156 g_signal_emit_by_name (data->tag_manager, "starred-changed", nautilus_file_list_copy (data->selection));
157
158 g_task_return_boolean (data->task, TRUE);
159 g_object_unref (data->task);
160 }
161 else if (error && error->code == G_IO_ERROR_CANCELLED)
162 {
163 g_error_free (error);
164 }
165 else
166 {
167 g_warning ("error updating tags: %s", error->message);
168 g_task_return_error (data->task, error);
169 g_object_unref (data->task);
170 }
171
172 nautilus_file_list_free (data->selection);
173 g_free (data);
174 }
175
176 /**
177 * nautilus_tag_manager_get_starred_files:
178 * @self: The tag manager singleton
179 *
180 * Returns: (element-type gchar*) (transfer container): A list of the starred urls.
181 */
182 GList *
nautilus_tag_manager_get_starred_files(NautilusTagManager * self)183 nautilus_tag_manager_get_starred_files (NautilusTagManager *self)
184 {
185 GHashTableIter starred_iter;
186 gchar *starred_uri;
187 GList *starred_file_uris = NULL;
188
189 g_hash_table_iter_init (&starred_iter, self->starred_file_uris);
190 while (g_hash_table_iter_next (&starred_iter, (gpointer *) &starred_uri, NULL))
191 {
192 g_autoptr (GFile) file = g_file_new_for_uri (starred_uri);
193
194 /* Skip files ouside $HOME, because we don't support starring these yet.
195 * See comment on nautilus_tag_manager_can_star_contents() */
196 if (g_file_has_prefix (file, self->home))
197 {
198 starred_file_uris = g_list_prepend (starred_file_uris, starred_uri);
199 }
200 }
201
202 return starred_file_uris;
203 }
204
205 static void
on_get_starred_files_cursor_callback(GObject * object,GAsyncResult * result,gpointer user_data)206 on_get_starred_files_cursor_callback (GObject *object,
207 GAsyncResult *result,
208 gpointer user_data)
209 {
210 TrackerSparqlCursor *cursor;
211 g_autoptr (GError) error = NULL;
212 const gchar *url;
213 gboolean success;
214 NautilusTagManager *self;
215 GList *changed_files;
216 NautilusFile *file;
217
218 cursor = TRACKER_SPARQL_CURSOR (object);
219
220 self = NAUTILUS_TAG_MANAGER (user_data);
221
222 success = tracker_sparql_cursor_next_finish (cursor, result, &error);
223
224 if (!success)
225 {
226 if (error != NULL)
227 {
228 g_warning ("Error on getting all tags cursor callback: %s", error->message);
229 }
230
231 g_clear_object (&cursor);
232 return;
233 }
234
235 url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
236
237 g_hash_table_add (self->starred_file_uris, g_strdup (url));
238
239 file = nautilus_file_get_by_uri (url);
240
241 if (file)
242 {
243 changed_files = g_list_prepend (NULL, file);
244
245 g_signal_emit_by_name (self, "starred-changed", changed_files);
246
247 nautilus_file_list_free (changed_files);
248 }
249 else
250 {
251 DEBUG ("File %s is starred but not found", url);
252 }
253
254 tracker_sparql_cursor_next_async (cursor,
255 self->cancellable,
256 on_get_starred_files_cursor_callback,
257 self);
258 }
259
260 static void
on_get_starred_files_query_callback(GObject * object,GAsyncResult * result,gpointer user_data)261 on_get_starred_files_query_callback (GObject *object,
262 GAsyncResult *result,
263 gpointer user_data)
264 {
265 TrackerSparqlCursor *cursor;
266 g_autoptr (GError) error = NULL;
267 TrackerSparqlStatement *statement;
268 NautilusTagManager *self;
269
270 self = NAUTILUS_TAG_MANAGER (user_data);
271 statement = TRACKER_SPARQL_STATEMENT (object);
272
273 cursor = tracker_sparql_statement_execute_finish (statement,
274 result,
275 &error);
276
277 if (error != NULL)
278 {
279 if (error->code != G_IO_ERROR_CANCELLED)
280 {
281 g_warning ("Error on getting starred files: %s", error->message);
282 }
283 }
284 else
285 {
286 tracker_sparql_cursor_next_async (cursor,
287 self->cancellable,
288 on_get_starred_files_cursor_callback,
289 user_data);
290 }
291 }
292
293 static void
nautilus_tag_manager_query_starred_files(NautilusTagManager * self,GCancellable * cancellable)294 nautilus_tag_manager_query_starred_files (NautilusTagManager *self,
295 GCancellable *cancellable)
296 {
297 if (!self->database_ok)
298 {
299 g_message ("nautilus-tag-manager: No Tracker connection");
300 return;
301 }
302
303 self->cancellable = cancellable;
304
305 tracker_sparql_statement_execute_async (self->query_starred_files,
306 cancellable,
307 on_get_starred_files_query_callback,
308 self);
309 }
310
311 static GString *
nautilus_tag_manager_delete_tag(NautilusTagManager * self,GList * selection)312 nautilus_tag_manager_delete_tag (NautilusTagManager *self,
313 GList *selection)
314 {
315 GString *query;
316 NautilusFile *file;
317 GList *l;
318
319 query = g_string_new ("DELETE DATA {");
320
321 for (l = selection; l != NULL; l = l->next)
322 {
323 g_autofree gchar *uri = NULL;
324
325 file = l->data;
326
327 uri = nautilus_file_get_uri (file);
328 g_string_append_printf (query,
329 " <%s> a nautilus:File ; "
330 " nautilus:starred true . ",
331 uri);
332 }
333
334 g_string_append (query, "}");
335
336 return query;
337 }
338
339 static GString *
nautilus_tag_manager_insert_tag(NautilusTagManager * self,GList * selection)340 nautilus_tag_manager_insert_tag (NautilusTagManager *self,
341 GList *selection)
342 {
343 GString *query;
344 NautilusFile *file;
345 GList *l;
346
347 query = g_string_new ("INSERT DATA {");
348
349 for (l = selection; l != NULL; l = l->next)
350 {
351 g_autofree gchar *uri = NULL;
352
353 file = l->data;
354
355 uri = nautilus_file_get_uri (file);
356 g_string_append_printf (query,
357 " <%s> a nautilus:File ; "
358 " nautilus:starred true . ",
359 uri);
360 }
361
362 g_string_append (query, "}");
363
364 return query;
365 }
366
367 gboolean
nautilus_tag_manager_file_is_starred(NautilusTagManager * self,const gchar * file_uri)368 nautilus_tag_manager_file_is_starred (NautilusTagManager *self,
369 const gchar *file_uri)
370 {
371 return g_hash_table_contains (self->starred_file_uris, file_uri);
372 }
373
374 void
nautilus_tag_manager_star_files(NautilusTagManager * self,GObject * object,GList * selection,GAsyncReadyCallback callback,GCancellable * cancellable)375 nautilus_tag_manager_star_files (NautilusTagManager *self,
376 GObject *object,
377 GList *selection,
378 GAsyncReadyCallback callback,
379 GCancellable *cancellable)
380 {
381 GString *query;
382 g_autoptr (GError) error = NULL;
383 GTask *task;
384 UpdateData *update_data;
385
386 DEBUG ("Starring %i files", g_list_length (selection));
387
388 task = g_task_new (object, cancellable, callback, NULL);
389
390 query = nautilus_tag_manager_insert_tag (self, selection);
391
392 update_data = g_new0 (UpdateData, 1);
393 update_data->task = task;
394 update_data->tag_manager = self;
395 update_data->selection = nautilus_file_list_copy (selection);
396 update_data->star = TRUE;
397
398 start_query_or_update (self->db,
399 query,
400 on_update_callback,
401 update_data,
402 FALSE,
403 cancellable);
404
405 g_string_free (query, TRUE);
406 }
407
408 void
nautilus_tag_manager_unstar_files(NautilusTagManager * self,GObject * object,GList * selection,GAsyncReadyCallback callback,GCancellable * cancellable)409 nautilus_tag_manager_unstar_files (NautilusTagManager *self,
410 GObject *object,
411 GList *selection,
412 GAsyncReadyCallback callback,
413 GCancellable *cancellable)
414 {
415 GString *query;
416 GTask *task;
417 UpdateData *update_data;
418
419 DEBUG ("Unstarring %i files", g_list_length (selection));
420
421 task = g_task_new (object, cancellable, callback, NULL);
422
423 query = nautilus_tag_manager_delete_tag (self, selection);
424
425 update_data = g_new0 (UpdateData, 1);
426 update_data->task = task;
427 update_data->tag_manager = self;
428 update_data->selection = nautilus_file_list_copy (selection);
429 update_data->star = FALSE;
430
431 start_query_or_update (self->db,
432 query,
433 on_update_callback,
434 update_data,
435 FALSE,
436 cancellable);
437
438 g_string_free (query, TRUE);
439 }
440
441 static void
on_tracker_notifier_events(TrackerNotifier * notifier,gchar * service,gchar * graph,GPtrArray * events,gpointer user_data)442 on_tracker_notifier_events (TrackerNotifier *notifier,
443 gchar *service,
444 gchar *graph,
445 GPtrArray *events,
446 gpointer user_data)
447 {
448 TrackerNotifierEvent *event;
449 NautilusTagManager *self;
450 int i;
451 const gchar *file_url;
452 GError *error = NULL;
453 TrackerSparqlCursor *cursor;
454 gboolean query_has_results = FALSE;
455 gboolean starred;
456 GList *changed_files;
457 NautilusFile *changed_file;
458
459 self = NAUTILUS_TAG_MANAGER (user_data);
460
461 for (i = 0; i < events->len; i++)
462 {
463 event = g_ptr_array_index (events, i);
464
465 file_url = tracker_notifier_event_get_urn (event);
466 changed_file = NULL;
467
468 DEBUG ("Got event for file %s", file_url);
469
470 tracker_sparql_statement_bind_string (self->query_file_is_starred, "file", file_url);
471 cursor = tracker_sparql_statement_execute (self->query_file_is_starred,
472 NULL,
473 &error);
474
475 if (cursor)
476 {
477 query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error);
478 }
479
480 if (error || !cursor || !query_has_results)
481 {
482 g_warning ("Couldn't query the starred files database: '%s'", error ? error->message : "(null error)");
483 g_clear_error (&error);
484 return;
485 }
486
487 starred = tracker_sparql_cursor_get_boolean (cursor, 0);
488 if (starred)
489 {
490 gboolean inserted = g_hash_table_add (self->starred_file_uris, g_strdup (file_url));
491
492 if (inserted)
493 {
494 DEBUG ("Added %s to starred files list", file_url);
495 changed_file = nautilus_file_get_by_uri (file_url);
496 }
497 }
498 else
499 {
500 gboolean removed = g_hash_table_remove (self->starred_file_uris, file_url);
501
502 if (removed)
503 {
504 DEBUG ("Removed %s from starred files list", file_url);
505 changed_file = nautilus_file_get_by_uri (file_url);
506 }
507 }
508
509 if (changed_file)
510 {
511 changed_files = g_list_prepend (NULL, changed_file);
512
513 g_signal_emit_by_name (self, "starred-changed", changed_files);
514
515 nautilus_file_list_free (changed_files);
516 }
517
518 g_object_unref (cursor);
519 }
520 }
521
522 static void
nautilus_tag_manager_finalize(GObject * object)523 nautilus_tag_manager_finalize (GObject *object)
524 {
525 NautilusTagManager *self;
526
527 self = NAUTILUS_TAG_MANAGER (object);
528
529 if (self->notifier != NULL)
530 {
531 g_signal_handlers_disconnect_by_func (self->notifier,
532 G_CALLBACK (on_tracker_notifier_events),
533 self);
534 }
535
536 g_clear_object (&self->notifier);
537 g_clear_object (&self->db);
538 g_clear_object (&self->query_file_is_starred);
539 g_clear_object (&self->query_starred_files);
540
541 g_hash_table_destroy (self->starred_file_uris);
542 g_clear_object (&self->home);
543
544 G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object);
545 }
546
547 static void
nautilus_tag_manager_class_init(NautilusTagManagerClass * klass)548 nautilus_tag_manager_class_init (NautilusTagManagerClass *klass)
549 {
550 GObjectClass *oclass;
551
552 oclass = G_OBJECT_CLASS (klass);
553
554 oclass->finalize = nautilus_tag_manager_finalize;
555
556 signals[STARRED_CHANGED] = g_signal_new ("starred-changed",
557 NAUTILUS_TYPE_TAG_MANAGER,
558 G_SIGNAL_RUN_LAST,
559 0,
560 NULL,
561 NULL,
562 g_cclosure_marshal_VOID__POINTER,
563 G_TYPE_NONE,
564 1,
565 G_TYPE_POINTER);
566 }
567
568 /**
569 * nautilus_tag_manager_get:
570 *
571 * Gets a reference to the tag manager.
572 *
573 * If used to initialize a struct field, make sure to release on finalization.
574 * If used to initialize a local variable, make sure to use g_autoptr().
575 *
576 * Returns: (transfer full): the #NautilusTagManager singleton object.
577 */
578 NautilusTagManager *
nautilus_tag_manager_get(void)579 nautilus_tag_manager_get (void)
580 {
581 static NautilusTagManager *tag_manager = NULL;
582
583 if (tag_manager != NULL)
584 {
585 return g_object_ref (tag_manager);
586 }
587
588 tag_manager = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
589 g_object_add_weak_pointer (G_OBJECT (tag_manager), (gpointer) & tag_manager);
590
591 return tag_manager;
592 }
593
594 static gboolean
setup_database(NautilusTagManager * self,GCancellable * cancellable,GError ** error)595 setup_database (NautilusTagManager *self,
596 GCancellable *cancellable,
597 GError **error)
598 {
599 const gchar *datadir;
600 g_autofree gchar *store_path = NULL;
601 g_autofree gchar *ontology_path = NULL;
602 g_autoptr (GFile) store = NULL;
603 g_autoptr (GFile) ontology = NULL;
604
605 /* Open private database to store nautilus:starred property. */
606
607 datadir = NAUTILUS_DATADIR;
608
609 store_path = g_build_filename (g_get_user_data_dir (), "nautilus", "tags", NULL);
610 ontology_path = g_build_filename (datadir, "ontology", NULL);
611
612 store = g_file_new_for_path (store_path);
613 ontology = g_file_new_for_path (ontology_path);
614
615 self->db = tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE,
616 store,
617 ontology,
618 cancellable,
619 error);
620
621 if (*error)
622 {
623 return FALSE;
624 }
625
626 /* Prepare reusable queries. */
627 self->query_file_is_starred = tracker_sparql_connection_query_statement (self->db,
628 QUERY_FILE_IS_STARRED,
629 cancellable,
630 error);
631
632 if (*error)
633 {
634 return FALSE;
635 }
636
637 self->query_starred_files = tracker_sparql_connection_query_statement (self->db,
638 QUERY_STARRED_FILES,
639 cancellable,
640 error);
641
642 if (*error)
643 {
644 return FALSE;
645 }
646
647 return TRUE;
648 }
649
650 /* Initialize the tag mananger. */
651 void
nautilus_tag_manager_set_cancellable(NautilusTagManager * self,GCancellable * cancellable)652 nautilus_tag_manager_set_cancellable (NautilusTagManager *self,
653 GCancellable *cancellable)
654 {
655 g_autoptr (GError) error = NULL;
656
657 self->database_ok = setup_database (self, cancellable, &error);
658
659 if (error)
660 {
661 g_warning ("Unable to initialize tag manager: %s", error->message);
662 return;
663 }
664
665 self->notifier = tracker_sparql_connection_create_notifier (self->db);
666
667 nautilus_tag_manager_query_starred_files (self, cancellable);
668
669 g_signal_connect (self->notifier,
670 "events",
671 G_CALLBACK (on_tracker_notifier_events),
672 self);
673 }
674
675 static void
nautilus_tag_manager_init(NautilusTagManager * self)676 nautilus_tag_manager_init (NautilusTagManager *self)
677 {
678 self->starred_file_uris = g_hash_table_new_full (g_str_hash,
679 g_str_equal,
680 (GDestroyNotify) g_free,
681 /* values are keys */
682 NULL);
683 self->home = g_file_new_for_path (g_get_home_dir ());
684 }
685
686 gboolean
nautilus_tag_manager_can_star_contents(NautilusTagManager * tag_manager,GFile * directory)687 nautilus_tag_manager_can_star_contents (NautilusTagManager *tag_manager,
688 GFile *directory)
689 {
690 /* We only allow files to be starred inside the home directory for now.
691 * This avoids the starred files database growing too big.
692 * See https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/553#note_903108
693 */
694 return g_file_has_prefix (directory, tag_manager->home) || g_file_equal (directory, tag_manager->home);
695 }
696
697 static void
update_moved_uris_callback(GObject * object,GAsyncResult * result,gpointer user_data)698 update_moved_uris_callback (GObject *object,
699 GAsyncResult *result,
700 gpointer user_data)
701 {
702 g_autoptr (GError) error = NULL;
703 g_autoptr (GPtrArray) new_uris = user_data;
704
705 tracker_sparql_connection_update_finish (TRACKER_SPARQL_CONNECTION (object),
706 result,
707 &error);
708
709 if (error != NULL && error->code != G_IO_ERROR_CANCELLED)
710 {
711 g_warning ("Error updating moved uris: %s", error->message);
712 }
713 else
714 {
715 g_autolist (NautilusFile) updated_files = NULL;
716 g_autoptr (NautilusTagManager) tag_manager = nautilus_tag_manager_get ();
717
718 for (guint i = 0; i < new_uris->len; i++)
719 {
720 gchar *new_uri = g_ptr_array_index (new_uris, i);
721
722 updated_files = g_list_prepend (updated_files, nautilus_file_get_by_uri (new_uri));
723 }
724
725 g_signal_emit_by_name (tag_manager, "starred-changed", updated_files);
726 }
727 }
728
729 /**
730 * nautilus_tag_manager_update_moved_uris:
731 * @self: The tag manager singleton
732 * @src: The original location as a #GFile
733 * @dest: The new location as a #GFile
734 *
735 * Checks whether the rename/move operation (@src to @dest) has modified
736 * the URIs of any starred files, and updates the database accordingly.
737 */
738 void
nautilus_tag_manager_update_moved_uris(NautilusTagManager * self,GFile * src,GFile * dest)739 nautilus_tag_manager_update_moved_uris (NautilusTagManager *self,
740 GFile *src,
741 GFile *dest)
742 {
743 GHashTableIter starred_iter;
744 gchar *starred_uri;
745 g_autoptr (GPtrArray) old_uris = NULL;
746 g_autoptr (GPtrArray) new_uris = NULL;
747 g_autoptr (GString) query = NULL;
748
749 if (!self->database_ok)
750 {
751 g_message ("nautilus-tag-manager: No Tracker connection");
752 return;
753 }
754
755 old_uris = g_ptr_array_new ();
756 new_uris = g_ptr_array_new_with_free_func (g_free);
757
758 g_hash_table_iter_init (&starred_iter, self->starred_file_uris);
759 while (g_hash_table_iter_next (&starred_iter, (gpointer *) &starred_uri, NULL))
760 {
761 g_autoptr (GFile) starred_location = NULL;
762 g_autofree gchar *relative_path = NULL;
763
764 starred_location = g_file_new_for_uri (starred_uri);
765
766 if (g_file_equal (starred_location, src))
767 {
768 /* The moved file/folder is starred */
769 g_ptr_array_add (old_uris, starred_uri);
770 g_ptr_array_add (new_uris, g_file_get_uri (dest));
771 continue;
772 }
773
774 relative_path = g_file_get_relative_path (src, starred_location);
775 if (relative_path != NULL)
776 {
777 /* The starred file/folder is descendant of the moved/renamed directory */
778 g_autoptr (GFile) new_location = NULL;
779
780 new_location = g_file_resolve_relative_path (dest, relative_path);
781
782 g_ptr_array_add (old_uris, starred_uri);
783 g_ptr_array_add (new_uris, g_file_get_uri (new_location));
784 }
785 }
786
787 if (new_uris->len == 0)
788 {
789 /* No starred files are affected by this move/rename */
790 return;
791 }
792
793 DEBUG ("Updating moved URI for %i starred files", new_uris->len);
794
795 query = g_string_new ("DELETE DATA {");
796
797 for (guint i = 0; i < old_uris->len; i++)
798 {
799 gchar *old_uri = g_ptr_array_index (old_uris, i);
800 g_string_append_printf (query,
801 " <%s> a nautilus:File ; "
802 " nautilus:starred true . ",
803 old_uri);
804 }
805
806 g_string_append (query, "} ; INSERT DATA {");
807
808 for (guint i = 0; i < new_uris->len; i++)
809 {
810 gchar *new_uri = g_ptr_array_index (new_uris, i);
811 g_string_append_printf (query,
812 " <%s> a nautilus:File ; "
813 " nautilus:starred true . ",
814 new_uri);
815 }
816
817 g_string_append (query, "}");
818
819 /* Forward the new_uris list to later pass in the ::files-changed signal.
820 * There is no need to pass the old_uris because the file model is updated
821 * independently; we need only inform the view where to display stars now.
822 */
823 tracker_sparql_connection_update_async (self->db,
824 query->str,
825 self->cancellable,
826 update_moved_uris_callback,
827 g_steal_pointer (&new_uris));
828 }
829
830 static void
process_tracker2_data_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)831 process_tracker2_data_cb (GObject *source_object,
832 GAsyncResult *res,
833 gpointer user_data)
834 {
835 NautilusTagManager *self = NAUTILUS_TAG_MANAGER (source_object);
836 g_autofree gchar *path = tracker2_migration_stamp ();
837 g_autoptr (GError) error = NULL;
838
839 tracker_sparql_connection_update_finish (self->db, res, &error);
840
841 if (!error)
842 {
843 DEBUG ("Data migration was successful. Creating stamp %s", path);
844
845 g_file_set_contents (path, "", -1, &error);
846 if (error)
847 {
848 g_warning ("Failed to create %s after migration: %s", path, error->message);
849 }
850 }
851 else
852 {
853 g_warning ("Error during data migration: %s", error->message);
854 }
855 }
856
857 static void
process_tracker2_data(NautilusTagManager * self,GBytes * key_file_data)858 process_tracker2_data (NautilusTagManager *self,
859 GBytes *key_file_data)
860 {
861 g_autoptr (GKeyFile) key_file = NULL;
862 g_autoptr (GError) error = NULL;
863 gchar **groups, **group;
864 GList *selection = NULL;
865 NautilusFile *file;
866
867 key_file = g_key_file_new ();
868 g_key_file_load_from_bytes (key_file,
869 key_file_data,
870 G_KEY_FILE_NONE,
871 &error);
872 g_bytes_unref (key_file_data);
873
874 if (error)
875 {
876 g_warning ("Tracker 2 migration: Failed to parse key file data: %s", error->message);
877 return;
878 }
879
880 groups = g_key_file_get_groups (key_file, NULL);
881
882 for (group = groups; *group != NULL; group++)
883 {
884 file = nautilus_file_get_by_uri (*group);
885
886 if (file)
887 {
888 DEBUG ("Tracker 2 migration: starring %s", *group);
889 selection = g_list_prepend (selection, file);
890 }
891 else
892 {
893 DEBUG ("Tracker 2 migration: couldn't get NautilusFile for %s", *group);
894 }
895 }
896
897 nautilus_tag_manager_star_files (self,
898 G_OBJECT (self),
899 selection,
900 process_tracker2_data_cb,
901 self->cancellable);
902
903 g_free (groups);
904 }
905
906 static void
export_tracker2_data_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)907 export_tracker2_data_cb (GObject *source_object,
908 GAsyncResult *res,
909 gpointer user_data)
910 {
911 GInputStream *stream = G_INPUT_STREAM (source_object);
912 NautilusTagManager *self = NAUTILUS_TAG_MANAGER (user_data);
913 g_autoptr (GError) error = NULL;
914 GBytes *key_file_data;
915
916 key_file_data = g_input_stream_read_bytes_finish (stream, res, &error);
917
918 if (key_file_data)
919 {
920 process_tracker2_data (self, key_file_data);
921 }
922 else
923 {
924 g_warning ("Tracker2 migration: Failed to read data from pipe: %s", error->message);
925 }
926 }
927
928 static void
child_watch_cb(GPid pid,gint status,gpointer user_data)929 child_watch_cb (GPid pid,
930 gint status,
931 gpointer user_data)
932 {
933 DEBUG ("Child %" G_PID_FORMAT " exited %s", pid,
934 g_spawn_check_exit_status (status, NULL) ? "normally" : "abnormally");
935 g_spawn_close_pid (pid);
936 }
937
938 static void
export_tracker2_data(NautilusTagManager * self)939 export_tracker2_data (NautilusTagManager *self)
940 {
941 gchar *argv[] = {"tracker3", "export", "--2to3", "files-starred", "--keyfile", NULL};
942 gint stdout_fd;
943 GPid child_pid;
944 g_autoptr (GError) error = NULL;
945 gboolean success;
946 g_autoptr (GInputStream) stream = NULL;
947 GSpawnFlags flags;
948
949 flags = G_SPAWN_DO_NOT_REAP_CHILD |
950 G_SPAWN_STDERR_TO_DEV_NULL |
951 G_SPAWN_SEARCH_PATH;
952 success = g_spawn_async_with_pipes (NULL,
953 argv,
954 NULL,
955 flags,
956 NULL,
957 NULL,
958 &child_pid,
959 NULL,
960 &stdout_fd,
961 NULL,
962 &error);
963 if (!success)
964 {
965 g_warning ("Tracker 2 migration: Couldn't run `tracker3`: %s", error->message);
966 return;
967 }
968
969 g_child_watch_add (child_pid, child_watch_cb, NULL);
970
971 stream = g_unix_input_stream_new (stdout_fd, TRUE);
972 g_input_stream_read_bytes_async (stream,
973 TRACKER2_MAX_IMPORT_BYTES,
974 G_PRIORITY_LOW,
975 self->cancellable,
976 export_tracker2_data_cb,
977 self);
978 }
979
980 void
nautilus_tag_manager_maybe_migrate_tracker2_data(NautilusTagManager * self)981 nautilus_tag_manager_maybe_migrate_tracker2_data (NautilusTagManager *self)
982 {
983 g_autofree gchar *path = tracker2_migration_stamp ();
984
985 if (g_file_test (path, G_FILE_TEST_EXISTS))
986 {
987 DEBUG ("Tracker 2 migration: already completed.");
988 }
989 else
990 {
991 DEBUG ("Tracker 2 migration: starting.");
992 export_tracker2_data (self);
993 }
994 }
995