1 /* nautilus-batch-rename-utilities.c
2  *
3  * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com>
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  * 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, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "nautilus-batch-rename-dialog.h"
20 #include "nautilus-batch-rename-utilities.h"
21 #include "nautilus-file.h"
22 #include "nautilus-tracker-utilities.h"
23 
24 #include <glib.h>
25 #include <gtk/gtk.h>
26 #include <string.h>
27 #include <stdarg.h>
28 #include <eel/eel-vfs-extensions.h>
29 
30 typedef struct
31 {
32     NautilusFile *file;
33     gint position;
34 } CreateDateElem;
35 
36 typedef struct
37 {
38     NautilusBatchRenameDialog *dialog;
39     GHashTable *date_order_hash_table;
40 
41     GList *selection_metadata;
42 
43     gboolean has_metadata[G_N_ELEMENTS (metadata_tags_constants)];
44 
45     GCancellable *cancellable;
46 } QueryData;
47 
48 enum
49 {
50     FILE_NAME_INDEX,
51     CREATION_DATE_INDEX,
52     YEAR_INDEX,
53     MONTH_INDEX,
54     DAY_INDEX,
55     HOURS_INDEX,
56     MINUTES_INDEX,
57     SECONDS_INDEX,
58     CAMERA_MODEL_INDEX,
59     SEASON_INDEX,
60     EPISODE_NUMBER_INDEX,
61     TRACK_NUMBER_INDEX,
62     ARTIST_NAME_INDEX,
63     TITLE_INDEX,
64     ALBUM_NAME_INDEX,
65 } QueryMetadata;
66 
67 static void on_cursor_callback (GObject      *object,
68                                 GAsyncResult *result,
69                                 gpointer      user_data);
70 
71 void
string_free(gpointer mem)72 string_free (gpointer mem)
73 {
74     if (mem != NULL)
75     {
76         g_string_free (mem, TRUE);
77     }
78 }
79 
80 void
conflict_data_free(gpointer mem)81 conflict_data_free (gpointer mem)
82 {
83     ConflictData *conflict_data = mem;
84 
85     g_free (conflict_data->name);
86     g_free (conflict_data);
87 }
88 
89 gchar *
batch_rename_get_tag_text_representation(TagConstants tag_constants)90 batch_rename_get_tag_text_representation (TagConstants tag_constants)
91 {
92     return g_strdup_printf ("[%s]", gettext (tag_constants.label));
93 }
94 
95 static GString *
batch_rename_replace(gchar * string,gchar * substring,gchar * replacement)96 batch_rename_replace (gchar *string,
97                       gchar *substring,
98                       gchar *replacement)
99 {
100     GString *new_string;
101     gchar **splitted_string;
102     gint i, n_splits;
103 
104     new_string = g_string_new ("");
105 
106     if (substring == NULL || replacement == NULL)
107     {
108         g_string_append (new_string, string);
109 
110         return new_string;
111     }
112 
113     if (g_utf8_strlen (substring, -1) == 0)
114     {
115         g_string_append (new_string, string);
116 
117         return new_string;
118     }
119 
120     splitted_string = g_strsplit (string, substring, -1);
121     if (splitted_string == NULL)
122     {
123         g_string_append (new_string, string);
124 
125         return new_string;
126     }
127 
128     n_splits = g_strv_length (splitted_string);
129 
130     for (i = 0; i < n_splits; i++)
131     {
132         g_string_append (new_string, splitted_string[i]);
133 
134         if (i != n_splits - 1)
135         {
136             g_string_append (new_string, replacement);
137         }
138     }
139 
140     g_strfreev (splitted_string);
141 
142     return new_string;
143 }
144 
145 void
batch_rename_sort_lists_for_rename(GList ** selection,GList ** new_names,GList ** old_names,GList ** new_files,GList ** old_files,gboolean is_undo_redo)146 batch_rename_sort_lists_for_rename (GList    **selection,
147                                     GList    **new_names,
148                                     GList    **old_names,
149                                     GList    **new_files,
150                                     GList    **old_files,
151                                     gboolean   is_undo_redo)
152 {
153     GList *new_names_list;
154     GList *new_names_list2;
155     GList *files;
156     GList *files2;
157     GList *old_names_list = NULL;
158     GList *new_files_list = NULL;
159     GList *old_files_list = NULL;
160     GList *old_names_list2 = NULL;
161     GList *new_files_list2 = NULL;
162     GList *old_files_list2 = NULL;
163     GString *new_file_name;
164     GString *new_name;
165     GString *old_name;
166     GFile *new_file;
167     GFile *old_file;
168     NautilusFile *file;
169     gboolean order_changed = TRUE;
170 
171     /* in the following case:
172      * file1 -> file2
173      * file2 -> file3
174      * file2 must be renamed first, so because of that, the list has to be reordered
175      */
176     while (order_changed)
177     {
178         order_changed = FALSE;
179 
180         if (is_undo_redo)
181         {
182             old_names_list = *old_names;
183             new_files_list = *new_files;
184             old_files_list = *old_files;
185         }
186 
187         for (new_names_list = *new_names, files = *selection;
188              new_names_list != NULL && files != NULL;
189              new_names_list = new_names_list->next, files = files->next)
190         {
191             g_autofree gchar *old_file_name = NULL;
192 
193             old_file_name = nautilus_file_get_name (NAUTILUS_FILE (files->data));
194             new_file_name = new_names_list->data;
195 
196             if (is_undo_redo)
197             {
198                 old_names_list2 = old_names_list;
199                 new_files_list2 = new_files_list;
200                 old_files_list2 = old_files_list;
201             }
202 
203             for (files2 = files, new_names_list2 = new_names_list;
204                  files2 != NULL && new_names_list2 != NULL;
205                  files2 = files2->next, new_names_list2 = new_names_list2->next)
206             {
207                 g_autofree gchar *file_name = NULL;
208 
209                 file_name = nautilus_file_get_name (NAUTILUS_FILE (files2->data));
210                 new_name = new_names_list2->data;
211 
212                 if (files2 != files && g_strcmp0 (file_name, new_file_name->str) == 0)
213                 {
214                     file = NAUTILUS_FILE (files2->data);
215 
216                     *selection = g_list_remove_link (*selection, files2);
217                     *new_names = g_list_remove_link (*new_names, new_names_list2);
218 
219                     *selection = g_list_prepend (*selection, file);
220                     *new_names = g_list_prepend (*new_names, new_name);
221 
222                     if (is_undo_redo)
223                     {
224                         old_name = old_names_list2->data;
225                         new_file = new_files_list2->data;
226                         old_file = old_files_list2->data;
227 
228                         *old_names = g_list_remove_link (*old_names, old_names_list2);
229                         *new_files = g_list_remove_link (*new_files, new_files_list2);
230                         *old_files = g_list_remove_link (*old_files, old_files_list2);
231 
232                         *old_names = g_list_prepend (*old_names, old_name);
233                         *new_files = g_list_prepend (*new_files, new_file);
234                         *old_files = g_list_prepend (*old_files, old_file);
235                     }
236 
237                     order_changed = TRUE;
238                     break;
239                 }
240 
241                 if (is_undo_redo)
242                 {
243                     old_names_list2 = old_names_list2->next;
244                     new_files_list2 = new_files_list2->next;
245                     old_files_list2 = old_files_list2->next;
246                 }
247             }
248 
249             if (is_undo_redo)
250             {
251                 old_names_list = old_names_list->next;
252                 new_files_list = new_files_list->next;
253                 old_files_list = old_files_list->next;
254             }
255         }
256     }
257 }
258 
259 /* This function changes the background color of the replaced part of the name */
260 GString *
batch_rename_replace_label_text(gchar * label,const gchar * substring)261 batch_rename_replace_label_text (gchar       *label,
262                                  const gchar *substring)
263 {
264     GString *new_label;
265     gchar **splitted_string;
266     gchar *token;
267     gint i, n_splits;
268 
269     new_label = g_string_new ("");
270 
271     if (substring == NULL || g_strcmp0 (substring, "") == 0)
272     {
273         token = g_markup_escape_text (label, -1);
274         new_label = g_string_append (new_label, token);
275         g_free (token);
276 
277         return new_label;
278     }
279 
280     splitted_string = g_strsplit (label, substring, -1);
281     if (splitted_string == NULL)
282     {
283         token = g_markup_escape_text (label, -1);
284         new_label = g_string_append (new_label, token);
285         g_free (token);
286 
287         return new_label;
288     }
289 
290     n_splits = g_strv_length (splitted_string);
291 
292     for (i = 0; i < n_splits; i++)
293     {
294         token = g_markup_escape_text (splitted_string[i], -1);
295         new_label = g_string_append (new_label, token);
296 
297         g_free (token);
298 
299         if (i != n_splits - 1)
300         {
301             token = g_markup_escape_text (substring, -1);
302             g_string_append_printf (new_label,
303                                     "<span background=\'#f57900\' color='white'>%s</span>",
304                                     token);
305 
306             g_free (token);
307         }
308     }
309 
310     g_strfreev (splitted_string);
311 
312     return new_label;
313 }
314 
315 static gchar *
get_metadata(GList * selection_metadata,gchar * file_name,MetadataType metadata_type)316 get_metadata (GList        *selection_metadata,
317               gchar        *file_name,
318               MetadataType  metadata_type)
319 {
320     GList *l;
321     FileMetadata *file_metadata;
322     gchar *metadata = NULL;
323 
324     for (l = selection_metadata; l != NULL; l = l->next)
325     {
326         file_metadata = l->data;
327         if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0)
328         {
329             if (file_metadata->metadata[metadata_type] &&
330                 file_metadata->metadata[metadata_type]->len > 0)
331             {
332                 metadata = file_metadata->metadata[metadata_type]->str;
333             }
334 
335             break;
336         }
337     }
338 
339     return metadata;
340 }
341 
342 static GString *
batch_rename_format(NautilusFile * file,GList * text_chunks,GList * selection_metadata,gint count)343 batch_rename_format (NautilusFile *file,
344                      GList        *text_chunks,
345                      GList        *selection_metadata,
346                      gint          count)
347 {
348     GList *l;
349     GString *tag_string;
350     GString *new_name;
351     gboolean added_tag;
352     MetadataType metadata_type;
353     g_autofree gchar *file_name = NULL;
354     g_autofree gchar *extension = NULL;
355     gint i;
356     gchar *metadata;
357 
358     file_name = nautilus_file_get_display_name (file);
359     if (!nautilus_file_is_directory (file))
360     {
361         extension = nautilus_file_get_extension (file);
362     }
363 
364     new_name = g_string_new ("");
365 
366     for (l = text_chunks; l != NULL; l = l->next)
367     {
368         added_tag = FALSE;
369         tag_string = l->data;
370 
371         for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
372         {
373             g_autofree gchar *tag_text_representation = NULL;
374 
375             tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]);
376             if (g_strcmp0 (tag_string->str, tag_text_representation) == 0)
377             {
378                 switch (numbering_tags_constants[i].numbering_type)
379                 {
380                     case NUMBERING_NO_ZERO_PAD:
381                     {
382                         g_string_append_printf (new_name, "%d", count);
383                     }
384                     break;
385 
386                     case NUMBERING_ONE_ZERO_PAD:
387                     {
388                         g_string_append_printf (new_name, "%02d", count);
389                     }
390                     break;
391 
392                     case NUMBERING_TWO_ZERO_PAD:
393                     {
394                         g_string_append_printf (new_name, "%03d", count);
395                     }
396                     break;
397 
398                     default:
399                     {
400                         g_warn_if_reached ();
401                     }
402                     break;
403                 }
404 
405                 added_tag = TRUE;
406                 break;
407             }
408         }
409 
410         if (added_tag)
411         {
412             continue;
413         }
414 
415         for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
416         {
417             g_autofree gchar *tag_text_representation = NULL;
418 
419             tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]);
420             if (g_strcmp0 (tag_string->str, tag_text_representation) == 0)
421             {
422                 metadata_type = metadata_tags_constants[i].metadata_type;
423                 metadata = get_metadata (selection_metadata, file_name, metadata_type);
424 
425                 /* TODO: This is a hack, we should provide a cancellable for checking
426                  * the metadata, and if that is happening don't enter here. We can
427                  * special case original file name upper in the call stack */
428                 if (!metadata && metadata_type != ORIGINAL_FILE_NAME)
429                 {
430                     g_warning ("Metadata not present in one file, it shouldn't have been added. File name: %s, Metadata: %s",
431                                file_name, metadata_tags_constants[i].label);
432                     continue;
433                 }
434 
435                 switch (metadata_type)
436                 {
437                     case ORIGINAL_FILE_NAME:
438                     {
439                         if (nautilus_file_is_directory (file))
440                         {
441                             new_name = g_string_append (new_name, file_name);
442                         }
443                         else
444                         {
445                             g_autofree gchar *base_name = NULL;
446                             base_name = eel_filename_strip_extension (file_name);
447                             new_name = g_string_append (new_name, base_name);
448                         }
449                     }
450                     break;
451 
452                     case TRACK_NUMBER:
453                     {
454                         g_string_append_printf (new_name, "%02d", atoi (metadata));
455                     }
456                     break;
457 
458                     default:
459                     {
460                         new_name = g_string_append (new_name, metadata);
461                     }
462                     break;
463                 }
464 
465                 added_tag = TRUE;
466                 break;
467             }
468         }
469 
470         if (!added_tag)
471         {
472             new_name = g_string_append (new_name, tag_string->str);
473         }
474     }
475 
476     if (g_strcmp0 (new_name->str, "") == 0)
477     {
478         new_name = g_string_append (new_name, file_name);
479     }
480     else
481     {
482         if (extension != NULL)
483         {
484             new_name = g_string_append (new_name, extension);
485         }
486     }
487 
488     return new_name;
489 }
490 
491 GList *
batch_rename_dialog_get_new_names_list(NautilusBatchRenameDialogMode mode,GList * selection,GList * text_chunks,GList * selection_metadata,gchar * entry_text,gchar * replace_text)492 batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode  mode,
493                                         GList                         *selection,
494                                         GList                         *text_chunks,
495                                         GList                         *selection_metadata,
496                                         gchar                         *entry_text,
497                                         gchar                         *replace_text)
498 {
499     GList *l;
500     GList *result;
501     GString *file_name;
502     GString *new_name;
503     NautilusFile *file;
504     gchar *name;
505     gint count;
506 
507     result = NULL;
508     count = 1;
509 
510     for (l = selection; l != NULL; l = l->next)
511     {
512         file = NAUTILUS_FILE (l->data);
513 
514         name = nautilus_file_get_name (file);
515         file_name = g_string_new (name);
516 
517         /* get the new name here and add it to the list*/
518         if (mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT)
519         {
520             new_name = batch_rename_format (file,
521                                             text_chunks,
522                                             selection_metadata,
523                                             count++);
524             result = g_list_prepend (result, new_name);
525         }
526 
527         if (mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
528         {
529             new_name = batch_rename_replace (file_name->str,
530                                              entry_text,
531                                              replace_text);
532             result = g_list_prepend (result, new_name);
533         }
534 
535         g_string_free (file_name, TRUE);
536         g_free (name);
537     }
538 
539     return result;
540 }
541 
542 /* There is a case that a new name for a file conflicts with an existing file name
543  * in the directory but it's not a problem because the file in the directory that
544  * conflicts is part of the batch renaming selection and it's going to change the name anyway. */
545 gboolean
file_name_conflicts_with_results(GList * selection,GList * new_names,GString * old_name,gchar * parent_uri)546 file_name_conflicts_with_results (GList   *selection,
547                                   GList   *new_names,
548                                   GString *old_name,
549                                   gchar   *parent_uri)
550 {
551     GList *l1;
552     GList *l2;
553     NautilusFile *selection_file;
554     GString *new_name;
555 
556     for (l1 = selection, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
557     {
558         g_autofree gchar *name1 = NULL;
559         g_autofree gchar *selection_parent_uri = NULL;
560 
561         selection_file = NAUTILUS_FILE (l1->data);
562         name1 = nautilus_file_get_name (selection_file);
563 
564         selection_parent_uri = nautilus_file_get_parent_uri (selection_file);
565 
566         if (g_strcmp0 (name1, old_name->str) == 0)
567         {
568             new_name = l2->data;
569 
570             /* if the name didn't change, then there's a conflict */
571             if (g_string_equal (old_name, new_name) &&
572                 (parent_uri == NULL || g_strcmp0 (parent_uri, selection_parent_uri) == 0))
573             {
574                 return FALSE;
575             }
576 
577 
578             /* if this file exists and it changed it's name, then there's no
579              * conflict */
580             return TRUE;
581         }
582     }
583 
584     /* the case this function searched for doesn't exist, so the file
585      * has a conlfict */
586     return FALSE;
587 }
588 
589 static gint
compare_files_by_name_ascending(gconstpointer a,gconstpointer b)590 compare_files_by_name_ascending (gconstpointer a,
591                                  gconstpointer b)
592 {
593     NautilusFile *file1;
594     NautilusFile *file2;
595 
596     file1 = NAUTILUS_FILE (a);
597     file2 = NAUTILUS_FILE (b);
598 
599     return nautilus_file_compare_for_sort (file1, file2,
600                                            NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
601                                            FALSE, FALSE);
602 }
603 
604 static gint
compare_files_by_name_descending(gconstpointer a,gconstpointer b)605 compare_files_by_name_descending (gconstpointer a,
606                                   gconstpointer b)
607 {
608     NautilusFile *file1;
609     NautilusFile *file2;
610 
611     file1 = NAUTILUS_FILE (a);
612     file2 = NAUTILUS_FILE (b);
613 
614     return nautilus_file_compare_for_sort (file1, file2,
615                                            NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
616                                            FALSE, TRUE);
617 }
618 
619 static gint
compare_files_by_first_modified(gconstpointer a,gconstpointer b)620 compare_files_by_first_modified (gconstpointer a,
621                                  gconstpointer b)
622 {
623     NautilusFile *file1;
624     NautilusFile *file2;
625 
626     file1 = NAUTILUS_FILE (a);
627     file2 = NAUTILUS_FILE (b);
628 
629     return nautilus_file_compare_for_sort (file1, file2,
630                                            NAUTILUS_FILE_SORT_BY_MTIME,
631                                            FALSE, FALSE);
632 }
633 
634 static gint
compare_files_by_last_modified(gconstpointer a,gconstpointer b)635 compare_files_by_last_modified (gconstpointer a,
636                                 gconstpointer b)
637 {
638     NautilusFile *file1;
639     NautilusFile *file2;
640 
641     file1 = NAUTILUS_FILE (a);
642     file2 = NAUTILUS_FILE (b);
643 
644     return nautilus_file_compare_for_sort (file1, file2,
645                                            NAUTILUS_FILE_SORT_BY_MTIME,
646                                            FALSE, TRUE);
647 }
648 
649 static gint
compare_files_by_first_created(gconstpointer a,gconstpointer b)650 compare_files_by_first_created (gconstpointer a,
651                                 gconstpointer b)
652 {
653     CreateDateElem *elem1;
654     CreateDateElem *elem2;
655 
656     elem1 = (CreateDateElem *) a;
657     elem2 = (CreateDateElem *) b;
658 
659     return elem1->position - elem2->position;
660 }
661 
662 static gint
compare_files_by_last_created(gconstpointer a,gconstpointer b)663 compare_files_by_last_created (gconstpointer a,
664                                gconstpointer b)
665 {
666     CreateDateElem *elem1;
667     CreateDateElem *elem2;
668 
669     elem1 = (CreateDateElem *) a;
670     elem2 = (CreateDateElem *) b;
671 
672     return elem2->position - elem1->position;
673 }
674 
675 GList *
nautilus_batch_rename_dialog_sort(GList * selection,SortMode mode,GHashTable * creation_date_table)676 nautilus_batch_rename_dialog_sort (GList      *selection,
677                                    SortMode    mode,
678                                    GHashTable *creation_date_table)
679 {
680     GList *l, *l2;
681     NautilusFile *file;
682     GList *create_date_list;
683     GList *create_date_list_sorted;
684     gchar *name;
685 
686     if (mode == ORIGINAL_ASCENDING)
687     {
688         return g_list_sort (selection, compare_files_by_name_ascending);
689     }
690 
691     if (mode == ORIGINAL_DESCENDING)
692     {
693         return g_list_sort (selection, compare_files_by_name_descending);
694     }
695 
696     if (mode == FIRST_MODIFIED)
697     {
698         return g_list_sort (selection, compare_files_by_first_modified);
699     }
700 
701     if (mode == LAST_MODIFIED)
702     {
703         return g_list_sort (selection, compare_files_by_last_modified);
704     }
705 
706     if (mode == FIRST_CREATED || mode == LAST_CREATED)
707     {
708         create_date_list = NULL;
709 
710         for (l = selection; l != NULL; l = l->next)
711         {
712             CreateDateElem *elem;
713             elem = g_new (CreateDateElem, 1);
714 
715             file = NAUTILUS_FILE (l->data);
716 
717             name = nautilus_file_get_name (file);
718             elem->file = file;
719             elem->position = GPOINTER_TO_INT (g_hash_table_lookup (creation_date_table, name));
720             g_free (name);
721 
722             create_date_list = g_list_prepend (create_date_list, elem);
723         }
724 
725         if (mode == FIRST_CREATED)
726         {
727             create_date_list_sorted = g_list_sort (create_date_list,
728                                                    compare_files_by_first_created);
729         }
730         else
731         {
732             create_date_list_sorted = g_list_sort (create_date_list,
733                                                    compare_files_by_last_created);
734         }
735 
736         for (l = selection, l2 = create_date_list_sorted; l2 != NULL; l = l->next, l2 = l2->next)
737         {
738             CreateDateElem *elem = l2->data;
739             l->data = elem->file;
740         }
741 
742         g_list_free_full (create_date_list, g_free);
743     }
744 
745     return selection;
746 }
747 
748 static void
cursor_next(QueryData * query_data,TrackerSparqlCursor * cursor)749 cursor_next (QueryData           *query_data,
750              TrackerSparqlCursor *cursor)
751 {
752     tracker_sparql_cursor_next_async (cursor,
753                                       query_data->cancellable,
754                                       on_cursor_callback,
755                                       query_data);
756 }
757 
758 static void
remove_metadata(QueryData * query_data,MetadataType metadata_type)759 remove_metadata (QueryData    *query_data,
760                  MetadataType  metadata_type)
761 {
762     GList *l;
763     FileMetadata *metadata_to_delete;
764 
765     for (l = query_data->selection_metadata; l != NULL; l = l->next)
766     {
767         metadata_to_delete = l->data;
768         if (metadata_to_delete->metadata[metadata_type])
769         {
770             g_string_free (metadata_to_delete->metadata[metadata_type], TRUE);
771             metadata_to_delete->metadata[metadata_type] = NULL;
772         }
773     }
774 
775     query_data->has_metadata[metadata_type] = FALSE;
776 }
777 
778 static GString *
format_date_time(GDateTime * date_time)779 format_date_time (GDateTime *date_time)
780 {
781     g_autofree gchar *date = NULL;
782     GString *formated_date;
783 
784     date = g_date_time_format (date_time, "%x");
785     if (strstr (date, "/") != NULL)
786     {
787         formated_date = batch_rename_replace (date, "/", "-");
788     }
789     else
790     {
791         formated_date = g_string_new (date);
792     }
793 
794     return formated_date;
795 }
796 
797 static void
on_cursor_callback(GObject * object,GAsyncResult * result,gpointer user_data)798 on_cursor_callback (GObject      *object,
799                     GAsyncResult *result,
800                     gpointer      user_data)
801 {
802     TrackerSparqlCursor *cursor;
803     gboolean success;
804     QueryData *query_data;
805     MetadataType metadata_type;
806     g_autoptr (GError) error = NULL;
807     GList *l;
808     FileMetadata *file_metadata;
809     GDateTime *date_time;
810     guint i;
811     const gchar *current_metadata;
812     const gchar *file_name;
813     const gchar *creation_date;
814     const gchar *year;
815     const gchar *month;
816     const gchar *day;
817     const gchar *hours;
818     const gchar *minutes;
819     const gchar *seconds;
820     const gchar *equipment;
821     const gchar *season_number;
822     const gchar *episode_number;
823     const gchar *track_number;
824     const gchar *artist_name;
825     const gchar *title;
826     const gchar *album_name;
827 
828     file_metadata = NULL;
829 
830     cursor = TRACKER_SPARQL_CURSOR (object);
831     query_data = user_data;
832 
833     success = tracker_sparql_cursor_next_finish (cursor, result, &error);
834     if (!success)
835     {
836         if (error != NULL)
837         {
838             g_warning ("Error on batch rename tracker query cursor: %s", error->message);
839         }
840 
841         g_clear_object (&cursor);
842 
843         /* The dialog is going away at the time of cancellation */
844         if (error == NULL ||
845             (error != NULL && error->code != G_IO_ERROR_CANCELLED))
846         {
847             nautilus_batch_rename_dialog_query_finished (query_data->dialog,
848                                                          query_data->date_order_hash_table,
849                                                          query_data->selection_metadata);
850         }
851 
852         g_free (query_data);
853 
854         return;
855     }
856 
857     creation_date = tracker_sparql_cursor_get_string (cursor, CREATION_DATE_INDEX, NULL);
858 
859     year = tracker_sparql_cursor_get_string (cursor, YEAR_INDEX, NULL);
860     month = tracker_sparql_cursor_get_string (cursor, MONTH_INDEX, NULL);
861     day = tracker_sparql_cursor_get_string (cursor, DAY_INDEX, NULL);
862     hours = tracker_sparql_cursor_get_string (cursor, HOURS_INDEX, NULL);
863     minutes = tracker_sparql_cursor_get_string (cursor, MINUTES_INDEX, NULL);
864     seconds = tracker_sparql_cursor_get_string (cursor, SECONDS_INDEX, NULL);
865     equipment = tracker_sparql_cursor_get_string (cursor, CAMERA_MODEL_INDEX, NULL);
866     season_number = tracker_sparql_cursor_get_string (cursor, SEASON_INDEX, NULL);
867     episode_number = tracker_sparql_cursor_get_string (cursor, EPISODE_NUMBER_INDEX, NULL);
868     track_number = tracker_sparql_cursor_get_string (cursor, TRACK_NUMBER_INDEX, NULL);
869     artist_name = tracker_sparql_cursor_get_string (cursor, ARTIST_NAME_INDEX, NULL);
870     title = tracker_sparql_cursor_get_string (cursor, TITLE_INDEX, NULL);
871     album_name = tracker_sparql_cursor_get_string (cursor, ALBUM_NAME_INDEX, NULL);
872 
873     /* Search for the metadata object corresponding to the file name */
874     file_name = tracker_sparql_cursor_get_string (cursor, FILE_NAME_INDEX, NULL);
875     for (l = query_data->selection_metadata; l != NULL; l = l->next)
876     {
877         file_metadata = l->data;
878 
879         if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0)
880         {
881             break;
882         }
883     }
884 
885     /* Set metadata when available, and delete for the whole selection when not */
886     for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
887     {
888         if (query_data->has_metadata[i])
889         {
890             metadata_type = metadata_tags_constants[i].metadata_type;
891             current_metadata = NULL;
892             switch (metadata_type)
893             {
894                 case ORIGINAL_FILE_NAME:
895                 {
896                     current_metadata = file_name;
897                 }
898                 break;
899 
900                 case CREATION_DATE:
901                 {
902                     current_metadata = creation_date;
903                 }
904                 break;
905 
906                 case EQUIPMENT:
907                 {
908                     current_metadata = equipment;
909                 }
910                 break;
911 
912                 case SEASON_NUMBER:
913                 {
914                     current_metadata = season_number;
915                 }
916                 break;
917 
918                 case EPISODE_NUMBER:
919                 {
920                     current_metadata = episode_number;
921                 }
922                 break;
923 
924                 case ARTIST_NAME:
925                 {
926                     current_metadata = artist_name;
927                 }
928                 break;
929 
930                 case ALBUM_NAME:
931                 {
932                     current_metadata = album_name;
933                 }
934                 break;
935 
936                 case TITLE:
937                 {
938                     current_metadata = title;
939                 }
940                 break;
941 
942                 case TRACK_NUMBER:
943                 {
944                     current_metadata = track_number;
945                 }
946                 break;
947 
948                 default:
949                 {
950                     g_warn_if_reached ();
951                 }
952                 break;
953             }
954 
955             /* TODO: Figure out how to inform the user of why the metadata is
956              * unavailable when one or more contains the unallowed character "/"
957              */
958             if (!current_metadata || g_strrstr (current_metadata, "/"))
959             {
960                 remove_metadata (query_data,
961                                  metadata_type);
962 
963                 if (metadata_type == CREATION_DATE &&
964                     query_data->date_order_hash_table)
965                 {
966                     g_hash_table_destroy (query_data->date_order_hash_table);
967                     query_data->date_order_hash_table = NULL;
968                 }
969             }
970             else
971             {
972                 if (metadata_type == CREATION_DATE)
973                 {
974                     /* Add the sort order to the order hash table */
975                     g_hash_table_insert (query_data->date_order_hash_table,
976                                          g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL)),
977                                          GINT_TO_POINTER (g_hash_table_size (query_data->date_order_hash_table)));
978 
979                     date_time = g_date_time_new_local (atoi (year),
980                                                        atoi (month),
981                                                        atoi (day),
982                                                        atoi (hours),
983                                                        atoi (minutes),
984                                                        atoi (seconds));
985 
986                     file_metadata->metadata[metadata_type] = format_date_time (date_time);
987                 }
988                 else
989                 {
990                     file_metadata->metadata[metadata_type] = g_string_new (current_metadata);
991                 }
992             }
993         }
994     }
995 
996     /* Get next */
997     cursor_next (query_data, cursor);
998 }
999 
1000 static void
batch_rename_dialog_query_callback(GObject * object,GAsyncResult * result,gpointer user_data)1001 batch_rename_dialog_query_callback (GObject      *object,
1002                                     GAsyncResult *result,
1003                                     gpointer      user_data)
1004 {
1005     TrackerSparqlConnection *connection;
1006     TrackerSparqlCursor *cursor;
1007     QueryData *query_data;
1008     g_autoptr (GError) error = NULL;
1009 
1010     connection = TRACKER_SPARQL_CONNECTION (object);
1011     query_data = user_data;
1012 
1013     cursor = tracker_sparql_connection_query_finish (connection,
1014                                                      result,
1015                                                      &error);
1016 
1017     if (error != NULL)
1018     {
1019         g_warning ("Error on batch rename query for metadata: %s", error->message);
1020 
1021         /* The dialog is being finalized at this point */
1022         if (error->code != G_IO_ERROR_CANCELLED)
1023         {
1024             nautilus_batch_rename_dialog_query_finished (query_data->dialog,
1025                                                          query_data->date_order_hash_table,
1026                                                          query_data->selection_metadata);
1027         }
1028 
1029         g_free (query_data);
1030     }
1031     else
1032     {
1033         cursor_next (query_data, cursor);
1034     }
1035 }
1036 
1037 void
check_metadata_for_selection(NautilusBatchRenameDialog * dialog,GList * selection,GCancellable * cancellable)1038 check_metadata_for_selection (NautilusBatchRenameDialog *dialog,
1039                               GList                     *selection,
1040                               GCancellable              *cancellable)
1041 {
1042     TrackerSparqlConnection *connection;
1043     GString *query;
1044     GList *l;
1045     NautilusFile *file;
1046     GError *error;
1047     QueryData *query_data;
1048     gchar *file_name;
1049     FileMetadata *file_metadata;
1050     GList *selection_metadata;
1051     guint i;
1052     g_autofree gchar *parent_uri = NULL;
1053     gchar *file_name_escaped;
1054 
1055     error = NULL;
1056     selection_metadata = NULL;
1057 
1058     query = g_string_new ("SELECT "
1059                           "nfo:fileName(?file) "
1060                           "nie:contentCreated(?content) "
1061                           "year(nie:contentCreated(?content)) "
1062                           "month(nie:contentCreated(?content)) "
1063                           "day(nie:contentCreated(?content)) "
1064                           "hours(nie:contentCreated(?content)) "
1065                           "minutes(nie:contentCreated(?content)) "
1066                           "seconds(nie:contentCreated(?content)) "
1067                           "nfo:model(nfo:equipment(?content)) "
1068                           "nmm:seasonNumber(?content) "
1069                           "nmm:episodeNumber(?content) "
1070                           "nmm:trackNumber(?content) "
1071                           "nmm:artistName(nmm:performer(?content)) "
1072                           "nie:title(?content) "
1073                           "nie:title(nmm:musicAlbum(?content)) "
1074                           "WHERE { ?file a nfo:FileDataObject. ?file nie:url ?url. ?content nie:isStoredAs ?file. ");
1075 
1076     parent_uri = nautilus_file_get_parent_uri (NAUTILUS_FILE (selection->data));
1077 
1078     g_string_append_printf (query,
1079                             "FILTER(tracker:uri-is-parent(\"%s\", ?url)) ",
1080                             parent_uri);
1081 
1082     for (l = selection; l != NULL; l = l->next)
1083     {
1084         file = NAUTILUS_FILE (l->data);
1085         file_name = nautilus_file_get_name (file);
1086         file_name_escaped = tracker_sparql_escape_string (file_name);
1087 
1088         if (l == selection)
1089         {
1090             g_string_append_printf (query,
1091                                     "FILTER (nfo:fileName(?file) IN (\"%s\", ",
1092                                     file_name_escaped);
1093         }
1094         else if (l->next == NULL)
1095         {
1096             g_string_append_printf (query,
1097                                     "\"%s\")) ",
1098                                     file_name_escaped);
1099         }
1100         else
1101         {
1102             g_string_append_printf (query,
1103                                     "\"%s\", ",
1104                                     file_name_escaped);
1105         }
1106 
1107         file_metadata = g_new0 (FileMetadata, 1);
1108         file_metadata->file_name = g_string_new (file_name);
1109         file_metadata->metadata[ORIGINAL_FILE_NAME] = g_string_new (file_name);
1110 
1111         selection_metadata = g_list_prepend (selection_metadata, file_metadata);
1112 
1113         g_free (file_name);
1114         g_free (file_name_escaped);
1115     }
1116 
1117     selection_metadata = g_list_reverse (selection_metadata);
1118 
1119     g_string_append (query, "} ORDER BY ASC(nie:contentCreated(?content))");
1120 
1121     connection = nautilus_tracker_get_miner_fs_connection (&error);
1122     if (!connection)
1123     {
1124         if (error)
1125         {
1126             g_warning ("Error on batch rename tracker connection: %s", error->message);
1127             g_error_free (error);
1128         }
1129 
1130         return;
1131     }
1132 
1133     query_data = g_new (QueryData, 1);
1134     query_data->date_order_hash_table = g_hash_table_new_full (g_str_hash,
1135                                                                g_str_equal,
1136                                                                (GDestroyNotify) g_free,
1137                                                                NULL);
1138     query_data->dialog = dialog;
1139     query_data->selection_metadata = selection_metadata;
1140     for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
1141     {
1142         query_data->has_metadata[i] = TRUE;
1143     }
1144     query_data->cancellable = cancellable;
1145 
1146     /* Make an asynchronous query to the store */
1147     tracker_sparql_connection_query_async (connection,
1148                                            query->str,
1149                                            cancellable,
1150                                            batch_rename_dialog_query_callback,
1151                                            query_data);
1152 
1153     g_string_free (query, TRUE);
1154 }
1155 
1156 GList *
batch_rename_files_get_distinct_parents(GList * selection)1157 batch_rename_files_get_distinct_parents (GList *selection)
1158 {
1159     GList *result;
1160     GList *l1;
1161     NautilusFile *file;
1162     NautilusDirectory *directory;
1163     NautilusFile *parent;
1164 
1165     result = NULL;
1166     for (l1 = selection; l1 != NULL; l1 = l1->next)
1167     {
1168         file = NAUTILUS_FILE (l1->data);
1169         parent = nautilus_file_get_parent (file);
1170         directory = nautilus_directory_get_for_file (parent);
1171         if (!g_list_find (result, directory))
1172         {
1173             result = g_list_prepend (result, directory);
1174         }
1175 
1176         nautilus_file_unref (parent);
1177     }
1178 
1179     return result;
1180 }
1181