1 /* EasyTAG - Tag editor for audio files
2  * Copyright (C) 2014-2015  David King <amigadave@amigadave.com>
3  * Copyright (C) 2000-2003  Jerome Couderc <easytag@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, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 
22 #include "easytag.h"
23 
24 #include <glib/gi18n.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 
28 #include "application_window.h"
29 #include "browser.h"
30 #include "file_description.h"
31 #include "file_list.h"
32 #include "id3_tag.h"
33 #include "log.h"
34 #include "misc.h"
35 #include "cddb_dialog.h"
36 #include "setting.h"
37 #include "scan_dialog.h"
38 #include "et_core.h"
39 #include "charset.h"
40 
41 #include "win32/win32dep.h"
42 
43 static GtkWidget *QuitRecursionWindow = NULL;
44 
45 /* Referenced in the header. */
46 gboolean Main_Stop_Button_Pressed;
47 GtkWidget *MainWindow;
48 gboolean ReadingDirectory;
49 
50 /* Used to force to hide the msgbox when saving tag */
51 static gboolean SF_HideMsgbox_Write_Tag;
52 /* To remember which button was pressed when saving tag */
53 static gint SF_ButtonPressed_Write_Tag;
54 /* Used to force to hide the msgbox when renaming file */
55 static gboolean SF_HideMsgbox_Rename_File;
56 /* To remember which button was pressed when renaming file */
57 static gint SF_ButtonPressed_Rename_File;
58 
59 static gboolean Write_File_Tag (ET_File *ETFile, gboolean hide_msgbox);
60 static gint Save_File (ET_File *ETFile, gboolean multiple_files,
61                        gboolean force_saving_files);
62 static gint Save_Selected_Files_With_Answer (gboolean force_saving_files);
63 static gint Save_List_Of_Files (GList *etfilelist,
64                                 gboolean force_saving_files);
65 
66 static GList *read_directory_recursively (GList *file_list,
67                                           GFileEnumerator *dir_enumerator,
68                                           gboolean recurse);
69 static void Open_Quit_Recursion_Function_Window (void);
70 static void Destroy_Quit_Recursion_Function_Window (void);
71 static void et_on_quit_recursion_response (GtkDialog *dialog, gint response_id,
72                                            gpointer user_data);
73 
74 /*
75  * Action when Save button is pressed
76  */
Action_Save_Selected_Files(void)77 void Action_Save_Selected_Files (void)
78 {
79     Save_Selected_Files_With_Answer(FALSE);
80 }
81 
Action_Force_Saving_Selected_Files(void)82 void Action_Force_Saving_Selected_Files (void)
83 {
84     Save_Selected_Files_With_Answer(TRUE);
85 }
86 
87 
88 /*
89  * Will save the full list of file (not only the selected files in list)
90  * and check if we must save also only the changed files or all files
91  * (force_saving_files==TRUE)
92  */
Save_All_Files_With_Answer(gboolean force_saving_files)93 gint Save_All_Files_With_Answer (gboolean force_saving_files)
94 {
95     g_return_val_if_fail (ETCore != NULL && ETCore->ETFileList != NULL, FALSE);
96 
97     return Save_List_Of_Files (ETCore->ETFileList, force_saving_files);
98 }
99 
100 /*
101  * Will save only the selected files in the file list
102  */
103 static gint
Save_Selected_Files_With_Answer(gboolean force_saving_files)104 Save_Selected_Files_With_Answer (gboolean force_saving_files)
105 {
106     gint toreturn;
107     GList *etfilelist = NULL;
108     GList *selfilelist = NULL;
109     GList *l;
110     ET_File *etfile;
111     GtkTreeSelection *selection;
112 
113     selection = et_application_window_browser_get_selection (ET_APPLICATION_WINDOW (MainWindow));
114     selfilelist = gtk_tree_selection_get_selected_rows(selection, NULL);
115 
116     for (l = selfilelist; l != NULL; l = g_list_next (l))
117     {
118         etfile = et_application_window_browser_get_et_file_from_path (ET_APPLICATION_WINDOW (MainWindow),
119                                                                       l->data);
120         etfilelist = g_list_prepend (etfilelist, etfile);
121     }
122 
123     g_list_free_full (selfilelist, (GDestroyNotify)gtk_tree_path_free);
124 
125     etfilelist = g_list_reverse (etfilelist);
126     toreturn = Save_List_Of_Files(etfilelist, force_saving_files);
127     g_list_free(etfilelist);
128     return toreturn;
129 }
130 
131 /*
132  * Save_List_Of_Files: Function to save a list of files.
133  *  - force_saving_files = TRUE => force saving the file even if it wasn't changed
134  *  - force_saving_files = FALSE => force saving only the changed files
135  */
136 static gint
Save_List_Of_Files(GList * etfilelist,gboolean force_saving_files)137 Save_List_Of_Files (GList *etfilelist, gboolean force_saving_files)
138 {
139     EtApplicationWindow *window;
140     gint       progress_bar_index;
141     gint       saving_answer;
142     gint       nb_files_to_save;
143     gint       nb_files_changed_by_ext_program;
144     gchar     *msg;
145     gchar      progress_bar_text[30];
146     GList *l;
147     ET_File   *etfile_save_position = NULL;
148     File_Tag  *FileTag;
149     File_Name *FileNameNew;
150     double     fraction;
151     GAction *action;
152     GVariant *variant;
153     GtkWidget *widget_focused;
154     GtkTreePath *currentPath = NULL;
155 
156     g_return_val_if_fail (ETCore != NULL, FALSE);
157 
158     window = ET_APPLICATION_WINDOW (MainWindow);
159 
160     /* Save the current position in the list */
161     etfile_save_position = ETCore->ETFileDisplayed;
162 
163     et_application_window_update_et_file_from_ui (window);
164 
165     /* Save widget that has current focus, to give it again the focus after saving */
166     widget_focused = gtk_window_get_focus(GTK_WINDOW(MainWindow));
167 
168     /* Count the number of files to save */
169     /* Count the number of files changed by an external program */
170     nb_files_to_save = 0;
171     nb_files_changed_by_ext_program = 0;
172 
173     for (l = etfilelist; l != NULL; l = g_list_next (l))
174     {
175         GFile *file;
176         GFileInfo *fileinfo;
177 
178         const ET_File *ETFile = (ET_File *)l->data;
179         const File_Tag *file_tag  = (File_Tag *)ETFile->FileTag->data;
180         const File_Name *FileName = (File_Name *)ETFile->FileNameNew->data;
181         const gchar *filename_cur = ((File_Name *)ETFile->FileNameCur->data)->value;
182         const gchar *filename_cur_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
183         gchar *basename_cur_utf8 = g_path_get_basename(filename_cur_utf8);
184 
185         // Count only the changed files or all files if force_saving_files==TRUE
186         if (force_saving_files
187             || (FileName && FileName->saved == FALSE)
188             || (file_tag && file_tag->saved == FALSE))
189             nb_files_to_save++;
190 
191         file = g_file_new_for_path (filename_cur);
192         fileinfo = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
193                                       G_FILE_QUERY_INFO_NONE, NULL, NULL);
194         g_object_unref (file);
195 
196         if (fileinfo)
197         {
198             if (ETFile->FileModificationTime
199                 != g_file_info_get_attribute_uint64 (fileinfo,
200                                                      G_FILE_ATTRIBUTE_TIME_MODIFIED))
201             {
202                 nb_files_changed_by_ext_program++;
203             }
204 
205             g_object_unref (fileinfo);
206         }
207         g_free(basename_cur_utf8);
208     }
209 
210     /* Initialize status bar */
211     et_application_window_progress_set_fraction (window, 0.0);
212     progress_bar_index = 0;
213     g_snprintf(progress_bar_text, 30, "%d/%d", progress_bar_index, nb_files_to_save);
214     et_application_window_progress_set_text (window, progress_bar_text);
215 
216     /* Set to unsensitive all command buttons (except Quit button) */
217     et_application_window_disable_command_actions (window);
218     et_application_window_browser_set_sensitive (window, FALSE);
219     et_application_window_tag_area_set_sensitive (window, FALSE);
220     et_application_window_file_area_set_sensitive (window, FALSE);
221 
222     /* Show msgbox (if needed) to ask confirmation ('SF' for Save File) */
223     SF_HideMsgbox_Write_Tag = FALSE;
224     SF_HideMsgbox_Rename_File = FALSE;
225 
226     Main_Stop_Button_Pressed = FALSE;
227     /* Activate the stop button. */
228     action = g_action_map_lookup_action (G_ACTION_MAP (MainWindow), "stop");
229     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
230 
231     /*
232      * Check if file was changed by an external program
233      */
234     if (nb_files_changed_by_ext_program > 0)
235     {
236         // Some files were changed by other program than EasyTAG
237         GtkWidget *msgdialog = NULL;
238         gint response;
239 
240         msgdialog = gtk_message_dialog_new(GTK_WINDOW(MainWindow),
241                                            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
242                                            GTK_MESSAGE_WARNING,
243                                            GTK_BUTTONS_NONE,
244                                            ngettext ("A file was changed by an external program",
245                                                      "%d files were changed by an external program",
246                                                      nb_files_changed_by_ext_program),
247                                            nb_files_changed_by_ext_program);
248         gtk_dialog_add_buttons (GTK_DIALOG (msgdialog), _("_Discard"),
249                                 GTK_RESPONSE_NO, _("_Save"), GTK_RESPONSE_YES,
250                                 NULL);
251         gtk_dialog_set_default_response (GTK_DIALOG (msgdialog),
252                                          GTK_RESPONSE_YES);
253         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msgdialog),"%s",_("Do you want to continue saving the file?"));
254         gtk_window_set_title(GTK_WINDOW(msgdialog),_("Quit"));
255 
256         response = gtk_dialog_run(GTK_DIALOG(msgdialog));
257         gtk_widget_destroy(msgdialog);
258 
259         switch (response)
260         {
261             case GTK_RESPONSE_YES:
262                 break;
263             case GTK_RESPONSE_NO:
264             case GTK_RESPONSE_DELETE_EVENT:
265                 /* Skip the following loop. */
266                 Main_Stop_Button_Pressed = TRUE;
267                 break;
268             default:
269                 g_assert_not_reached ();
270                 break;
271         }
272     }
273 
274     for (l = etfilelist; l != NULL && !Main_Stop_Button_Pressed;
275          l = g_list_next (l))
276     {
277         FileTag = ((ET_File *)l->data)->FileTag->data;
278         FileNameNew = ((ET_File *)l->data)->FileNameNew->data;
279 
280         /* We process only the files changed and not saved, or we force to save all
281          * files if force_saving_files==TRUE */
282         if ( force_saving_files
283         || FileTag->saved == FALSE || FileNameNew->saved == FALSE )
284         {
285             /* ET_Display_File_Data_To_UI ((ET_File *)l->data);
286              * Use of 'currentPath' to try to increase speed. Indeed, in many
287              * cases, the next file to select, is the next in the list. */
288             currentPath = et_application_window_browser_select_file_by_et_file2 (window,
289                                                                                 (ET_File *)l->data,
290                                                                                 FALSE,
291                                                                                 currentPath);
292 
293             fraction = (++progress_bar_index) / (double) nb_files_to_save;
294             et_application_window_progress_set_fraction (window, fraction);
295             g_snprintf(progress_bar_text, 30, "%d/%d", progress_bar_index, nb_files_to_save);
296             et_application_window_progress_set_text (window,
297                                                      progress_bar_text);
298 
299             /* Needed to refresh status bar */
300             while (gtk_events_pending())
301                 gtk_main_iteration();
302 
303             // Save tag and rename file
304             saving_answer = Save_File ((ET_File *)l->data,
305                                        nb_files_to_save > 1 ? TRUE : FALSE,
306                                        force_saving_files);
307 
308             if (saving_answer == -1)
309             {
310                 /* Stop saving files + reinit progress bar */
311                 et_application_window_progress_set_text (window, "");
312                 et_application_window_progress_set_fraction (window, 0.0);
313                 et_application_window_status_bar_message (window,
314                                                           _("Saving files was stopped"),
315                                                           TRUE);
316                 /* To update state of command buttons */
317                 et_application_window_update_actions (window);
318                 et_application_window_browser_set_sensitive (window, TRUE);
319                 et_application_window_tag_area_set_sensitive (window, TRUE);
320                 et_application_window_file_area_set_sensitive (window, TRUE);
321 
322                 if (currentPath)
323                 {
324                     gtk_tree_path_free (currentPath);
325                 }
326                 return -1; /* We stop all actions */
327             }
328         }
329     }
330 
331     if (currentPath)
332         gtk_tree_path_free(currentPath);
333 
334     if (Main_Stop_Button_Pressed)
335         msg = g_strdup (_("Saving files was stopped"));
336     else
337         msg = g_strdup (_("All files have been saved"));
338 
339     Main_Stop_Button_Pressed = FALSE;
340     action = g_action_map_lookup_action (G_ACTION_MAP (MainWindow), "stop");
341     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
342 
343     /* Return to the saved position in the list */
344     et_application_window_display_et_file (ET_APPLICATION_WINDOW (MainWindow),
345                                            etfile_save_position);
346     et_application_window_browser_select_file_by_et_file (ET_APPLICATION_WINDOW (MainWindow),
347                                                           etfile_save_position,
348                                                           TRUE);
349 
350     /* FIXME: Find out why this is a special case for the artist/album mode. */
351     action = g_action_map_lookup_action (G_ACTION_MAP (MainWindow),
352                                          "file-artist-view");
353     variant = g_action_get_state (action);
354 
355     if (strcmp (g_variant_get_string (variant, NULL), "artist") == 0)
356     {
357         et_application_window_browser_toggle_display_mode (window);
358     }
359 
360     g_variant_unref (variant);
361 
362     /* To update state of command buttons */
363     et_application_window_update_actions (ET_APPLICATION_WINDOW (MainWindow));
364     et_application_window_browser_set_sensitive (window, TRUE);
365     et_application_window_tag_area_set_sensitive (window, TRUE);
366     et_application_window_file_area_set_sensitive (window, TRUE);
367 
368     /* Give again focus to the first entry, else the focus is passed to another */
369     gtk_widget_grab_focus(GTK_WIDGET(widget_focused));
370 
371     et_application_window_progress_set_text (window, "");
372     et_application_window_progress_set_fraction (window, 0.0);
373     et_application_window_status_bar_message (window, msg, TRUE);
374     g_free(msg);
375     et_application_window_browser_refresh_list (window);
376     return TRUE;
377 }
378 
379 
380 
381 /*
382  * Save changes of the ETFile (write tag and rename file)
383  *  - multiple_files = TRUE  : when saving files, a msgbox appears with ability
384  *                             to do the same action for all files.
385  *  - multiple_files = FALSE : appears only a msgbox to ask confirmation.
386  */
387 static gint
Save_File(ET_File * ETFile,gboolean multiple_files,gboolean force_saving_files)388 Save_File (ET_File *ETFile, gboolean multiple_files,
389            gboolean force_saving_files)
390 {
391     const File_Tag *FileTag;
392     const File_Name *FileNameNew;
393     gint stop_loop = 0;
394     const gchar *filename_cur_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
395     const gchar *filename_new_utf8 = ((File_Name *)ETFile->FileNameNew->data)->value_utf8;
396     gchar *basename_cur_utf8, *basename_new_utf8;
397     gchar *dirname_cur_utf8, *dirname_new_utf8;
398 
399     g_return_val_if_fail (ETFile != NULL, 0);
400 
401     basename_cur_utf8 = g_path_get_basename(filename_cur_utf8);
402     basename_new_utf8 = g_path_get_basename(filename_new_utf8);
403 
404     /* Save the current displayed data */
405     //ET_Save_File_Data_From_UI((ET_File *)ETFileList->data); // Not needed, because it was done before
406     FileTag     = ETFile->FileTag->data;
407     FileNameNew = ETFile->FileNameNew->data;
408 
409     /*
410      * Check if file was changed by an external program
411      */
412     /*stat(filename_cur,&statbuf);
413     if (ETFile->FileModificationTime != statbuf.st_mtime)
414     {
415         // File was changed
416         GtkWidget *msgbox = NULL;
417         gint response;
418 
419         msg = g_strdup_printf(_("The file '%s' was changed by an external program.\nDo you want to continue?"),basename_cur_utf8);
420         msgbox = msg_box_new(_("Write File"),
421                              GTK_WINDOW(MainWindow),
422                              NULL,
423                              GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
424                              msg,
425                              GTK_STOCK_DIALOG_WARNING,
426                              GTK_STOCK_NO,  GTK_RESPONSE_NO,
427                              GTK_STOCK_YES, GTK_RESPONSE_YES,
428                              NULL);
429         g_free(msg);
430 
431         response = gtk_dialog_run(GTK_DIALOG(msgbox));
432         gtk_widget_destroy(msgbox);
433 
434         switch (response)
435         {
436             case GTK_RESPONSE_YES:
437                 break;
438             case GTK_RESPONSE_NO:
439             case GTK_RESPONSE_NONE:
440                 stop_loop = -1;
441                 return stop_loop;
442                 break;
443         }
444     }*/
445 
446 
447     /*
448      * First part: write tag information (artist, title,...)
449      */
450     // Note : the option 'force_saving_files' is only used to save tags
451     if ( force_saving_files
452     || FileTag->saved == FALSE ) // This tag had been already saved ?
453     {
454         GtkWidget *msgdialog = NULL;
455         GtkWidget *msgdialog_check_button = NULL;
456         gint response;
457 
458         if (g_settings_get_boolean (MainSettings, "confirm-rename-file")
459             && !SF_HideMsgbox_Write_Tag)
460         {
461             // ET_Display_File_Data_To_UI(ETFile);
462 
463             msgdialog = gtk_message_dialog_new(GTK_WINDOW(MainWindow),
464                                                GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
465                                                GTK_MESSAGE_QUESTION,
466                                                GTK_BUTTONS_NONE,
467                                                _("Do you want to write the tag of file ‘%s’?"),
468                                                basename_cur_utf8);
469             gtk_window_set_title(GTK_WINDOW(msgdialog),_("Confirm Tag Writing"));
470             if (multiple_files)
471             {
472                 GtkWidget *message_area;
473                 message_area = gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(msgdialog));
474                 msgdialog_check_button = gtk_check_button_new_with_label(_("Repeat action for the remaining files"));
475                 gtk_container_add(GTK_CONTAINER(message_area),msgdialog_check_button);
476                 gtk_widget_show (msgdialog_check_button);
477                 gtk_dialog_add_buttons (GTK_DIALOG (msgdialog),
478                                         _("_Discard"), GTK_RESPONSE_NO,
479                                         _("_Cancel"), GTK_RESPONSE_CANCEL,
480                                         _("_Save"), GTK_RESPONSE_YES, NULL);
481                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(msgdialog_check_button), TRUE); // Checked by default
482             }
483             else
484             {
485                 gtk_dialog_add_buttons (GTK_DIALOG (msgdialog),
486                                         _("_Cancel"), GTK_RESPONSE_NO,
487                                         _("_Save"), GTK_RESPONSE_YES, NULL);
488             }
489 
490             gtk_dialog_set_default_response (GTK_DIALOG (msgdialog),
491                                              GTK_RESPONSE_YES);
492             SF_ButtonPressed_Write_Tag = response = gtk_dialog_run(GTK_DIALOG(msgdialog));
493             // When check button in msgbox was activated : do not display the message again
494             if (msgdialog_check_button && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(msgdialog_check_button)))
495                 SF_HideMsgbox_Write_Tag = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(msgdialog_check_button));
496             gtk_widget_destroy(msgdialog);
497         }else
498         {
499             if (SF_HideMsgbox_Write_Tag)
500                 response = SF_ButtonPressed_Write_Tag;
501             else
502                 response = GTK_RESPONSE_YES;
503         }
504 
505         switch (response)
506         {
507             case GTK_RESPONSE_YES:
508             {
509                 gboolean rc;
510 
511                 // if 'SF_HideMsgbox_Write_Tag is TRUE', then errors are displayed only in log
512                 rc = Write_File_Tag(ETFile,SF_HideMsgbox_Write_Tag);
513                 // if an error occurs when 'SF_HideMsgbox_Write_Tag is TRUE', we don't stop saving...
514                 if (rc != TRUE && !SF_HideMsgbox_Write_Tag)
515                 {
516                     stop_loop = -1;
517 
518                     g_free (basename_cur_utf8);
519                     g_free (basename_new_utf8);
520 
521                     return stop_loop;
522                 }
523                 break;
524             }
525             case GTK_RESPONSE_NO:
526                 break;
527             case GTK_RESPONSE_CANCEL:
528             case GTK_RESPONSE_DELETE_EVENT:
529                 stop_loop = -1;
530 
531                 g_free (basename_cur_utf8);
532                 g_free (basename_new_utf8);
533 
534                 return stop_loop;
535                 break;
536             default:
537                 g_assert_not_reached ();
538                 break;
539         }
540     }
541 
542 
543     /*
544      * Second part: rename the file
545      */
546     // Do only if changed! (don't take force_saving_files into account)
547     if ( FileNameNew->saved == FALSE ) // This filename had been already saved ?
548     {
549         GtkWidget *msgdialog = NULL;
550         GtkWidget *msgdialog_check_button = NULL;
551         gint response;
552 
553         if (g_settings_get_boolean (MainSettings, "confirm-rename-file")
554             && !SF_HideMsgbox_Rename_File)
555         {
556             gchar *msgdialog_title = NULL;
557             gchar *msg = NULL;
558             gchar *msg1 = NULL;
559             // ET_Display_File_Data_To_UI(ETFile);
560 
561             dirname_cur_utf8 = g_path_get_dirname(filename_cur_utf8);
562             dirname_new_utf8 = g_path_get_dirname(filename_new_utf8);
563 
564             // Directories were renamed? or only filename?
565             if (g_utf8_collate(dirname_cur_utf8,dirname_new_utf8) != 0)
566             {
567                 if (g_utf8_collate(basename_cur_utf8,basename_new_utf8) != 0)
568                 {
569                     // Directories and filename changed
570                     msgdialog_title = g_strdup (_("Rename File and Directory"));
571                     msg = g_strdup(_("File and directory rename confirmation required"));
572                     msg1 = g_strdup_printf (_("Do you want to rename the file and directory ‘%s’ to ‘%s’?"),
573                                            filename_cur_utf8, filename_new_utf8);
574                 }else
575                 {
576                     // Only directories changed
577                     msgdialog_title = g_strdup (_("Rename Directory"));
578                     msg = g_strdup(_("Directory rename confirmation required"));
579                     msg1 = g_strdup_printf (_("Do you want to rename the directory ‘%s’ to ‘%s’?"),
580                                             dirname_cur_utf8,
581                                             dirname_new_utf8);
582                 }
583             }else
584             {
585                 // Only filename changed
586                 msgdialog_title = g_strdup (_("Rename File"));
587                 msg = g_strdup(_("File rename confirmation required"));
588                 msg1 = g_strdup_printf (_("Do you want to rename the file ‘%s’ to ‘%s’?"),
589                                        basename_cur_utf8, basename_new_utf8);
590             }
591 
592             g_free(dirname_cur_utf8);
593             g_free(dirname_new_utf8);
594 
595             msgdialog = gtk_message_dialog_new(GTK_WINDOW(MainWindow),
596                                                GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
597                                                GTK_MESSAGE_QUESTION,
598                                                GTK_BUTTONS_NONE,
599                                                "%s",
600                                                msg);
601             gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msgdialog),"%s",msg1);
602             gtk_window_set_title(GTK_WINDOW(msgdialog),msgdialog_title);
603             if (multiple_files)
604             {
605                 GtkWidget *message_area;
606                 message_area = gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(msgdialog));
607                 msgdialog_check_button = gtk_check_button_new_with_label(_("Repeat action for the remaining files"));
608                 gtk_container_add(GTK_CONTAINER(message_area),msgdialog_check_button);
609                 gtk_widget_show (msgdialog_check_button);
610                 gtk_dialog_add_buttons (GTK_DIALOG (msgdialog), _("_Discard"),
611                                         GTK_RESPONSE_NO, _("_Cancel"),
612                                         GTK_RESPONSE_CANCEL, _("_Save"),
613                                         GTK_RESPONSE_YES, NULL);
614                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(msgdialog_check_button), TRUE); // Checked by default
615             }
616             else
617             {
618                 gtk_dialog_add_buttons (GTK_DIALOG (msgdialog), _("_Discard"),
619                                         GTK_RESPONSE_NO, _("_Save"),
620                                         GTK_RESPONSE_YES, NULL);
621             }
622             g_free(msg);
623             g_free(msg1);
624             g_free(msgdialog_title);
625             gtk_dialog_set_default_response (GTK_DIALOG (msgdialog),
626                                              GTK_RESPONSE_YES);
627             SF_ButtonPressed_Rename_File = response = gtk_dialog_run(GTK_DIALOG(msgdialog));
628             if (msgdialog_check_button && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(msgdialog_check_button)))
629                 SF_HideMsgbox_Rename_File = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(msgdialog_check_button));
630             gtk_widget_destroy(msgdialog);
631         }else
632         {
633             if (SF_HideMsgbox_Rename_File)
634                 response = SF_ButtonPressed_Rename_File;
635             else
636                 response = GTK_RESPONSE_YES;
637         }
638 
639         switch(response)
640         {
641             case GTK_RESPONSE_YES:
642             {
643                 gboolean rc;
644                 GError *error = NULL;
645                 const gchar *cur_filename = ((File_Name *)ETFile->FileNameCur->data)->value;
646                 const gchar *new_filename = ((File_Name *)ETFile->FileNameNew->data)->value;
647                 rc = et_rename_file (cur_filename, new_filename, &error);
648 
649                 // if 'SF_HideMsgbox_Rename_File is TRUE', then errors are displayed only in log
650                 if (!rc)
651                 {
652                     if (!SF_HideMsgbox_Rename_File)
653                     {
654                         msgdialog = gtk_message_dialog_new (GTK_WINDOW (MainWindow),
655                                                             GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
656                                                             GTK_MESSAGE_ERROR,
657                                                             GTK_BUTTONS_CLOSE,
658                                                             _("Cannot rename file ‘%s’ to ‘%s’"),
659                                                             filename_cur_utf8,
660                                                             filename_new_utf8);
661                         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (msgdialog),
662                                                                   "%s",
663                                                                   error->message);
664                         gtk_window_set_title (GTK_WINDOW (msgdialog),
665                                               _("Rename File Error"));
666 
667                         gtk_dialog_run (GTK_DIALOG (msgdialog));
668                         gtk_widget_destroy (msgdialog);
669                     }
670 
671                     Log_Print (LOG_ERROR,
672                                _("Cannot rename file ‘%s’ to ‘%s’: %s"),
673                                filename_cur_utf8, filename_new_utf8,
674                                error->message);
675 
676                     et_application_window_status_bar_message (ET_APPLICATION_WINDOW (MainWindow),
677                                                               _("File(s) not renamed"),
678                                                               TRUE);
679                     g_error_free (error);
680                 }
681 
682                 // if an error occurs when 'SF_HideMsgbox_Rename_File is TRUE', we don't stop saving...
683                 if (!rc && !SF_HideMsgbox_Rename_File)
684                 {
685                     stop_loop = -1;
686 
687                     g_free (basename_cur_utf8);
688                     g_free (basename_new_utf8);
689 
690                     return stop_loop;
691                 }
692 
693                 /* Mark after renaming files. */
694                 ETFile->FileNameCur = ETFile->FileNameNew;
695                 ET_Mark_File_Name_As_Saved (ETFile);
696                 break;
697             }
698             case GTK_RESPONSE_NO:
699                 break;
700             case GTK_RESPONSE_CANCEL:
701             case GTK_RESPONSE_DELETE_EVENT:
702                 stop_loop = -1;
703 
704                 g_free (basename_cur_utf8);
705                 g_free (basename_new_utf8);
706 
707                 return stop_loop;
708                 break;
709             default:
710                 g_assert_not_reached ();
711                 break;
712         }
713     }
714 
715     g_free(basename_cur_utf8);
716     g_free(basename_new_utf8);
717 
718     /* Refresh file into browser list */
719     // Browser_List_Refresh_File_In_List(ETFile);
720 
721     return 1;
722 }
723 
724 /*
725  * Write tag of the ETFile
726  * Return TRUE => OK
727  *        FALSE => error
728  */
729 static gboolean
Write_File_Tag(ET_File * ETFile,gboolean hide_msgbox)730 Write_File_Tag (ET_File *ETFile, gboolean hide_msgbox)
731 {
732     GError *error = NULL;
733     const gchar *cur_filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
734     gchar *msg = NULL;
735     gchar *basename_utf8;
736     GtkWidget *msgdialog;
737 
738     basename_utf8 = g_path_get_basename(cur_filename_utf8);
739     msg = g_strdup_printf (_("Writing tag of ‘%s’"),basename_utf8);
740     et_application_window_status_bar_message (ET_APPLICATION_WINDOW (MainWindow),
741                                               msg, TRUE);
742     g_free(msg);
743     msg = NULL;
744 
745     if (ET_Save_File_Tag_To_HD (ETFile, &error))
746     {
747         msg = g_strdup_printf (_("Wrote tag of ‘%s’"), basename_utf8);
748         et_application_window_status_bar_message (ET_APPLICATION_WINDOW (MainWindow),
749                                                   msg, TRUE);
750         g_free (msg);
751         g_free (basename_utf8);
752         return TRUE;
753     }
754 
755     Log_Print (LOG_ERROR, "%s", error->message);
756 
757     if (!hide_msgbox)
758     {
759 #ifdef ENABLE_ID3LIB
760         if (g_error_matches (error, ET_ID3_ERROR, ET_ID3_ERROR_BUGGY_ID3LIB))
761         {
762             msgdialog = gtk_message_dialog_new (GTK_WINDOW (MainWindow),
763                                                 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
764                                                 GTK_MESSAGE_ERROR,
765                                                 GTK_BUTTONS_CLOSE,
766                                                 "%s",
767                                                 _("You have tried to save "
768                                                 "this tag to Unicode but it "
769                                                 "was detected that your "
770                                                 "version of id3lib is buggy"));
771             gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (msgdialog),
772                                                       _("If you reload this "
773                                                       "file, some characters "
774                                                       "in the tag may not be "
775                                                       "displayed correctly. "
776                                                       "Please, apply the "
777                                                       "patch "
778                                                       "src/id3lib/patch_id3lib_3.8.3_UTF16_writing_bug.diff "
779                                                       "to id3lib, which is "
780                                                       "available in the "
781                                                       "EasyTAG package "
782                                                       "sources.\nNote that "
783                                                       "this message will "
784                                                       "appear only "
785                                                       "once.\n\nFile: %s"),
786                                                       basename_utf8);
787         }
788         else
789 #endif
790         {
791             msgdialog = gtk_message_dialog_new (GTK_WINDOW (MainWindow),
792                                                 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
793                                                 GTK_MESSAGE_ERROR,
794                                                 GTK_BUTTONS_CLOSE,
795                                                 _("Cannot write tag in file ‘%s’"),
796                                                 basename_utf8);
797             gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (msgdialog),
798                                                       "%s", error->message);
799             gtk_window_set_title (GTK_WINDOW (msgdialog),
800                                   _("Tag Write Error"));
801         }
802 
803         gtk_dialog_run(GTK_DIALOG(msgdialog));
804         gtk_widget_destroy(msgdialog);
805     }
806 
807     g_clear_error (&error);
808     g_free(basename_utf8);
809 
810     return FALSE;
811 }
812 
813 /*
814  * Scans the specified directory: and load files into a list.
815  * If the path doesn't exist, we free the previous loaded list of files.
816  */
817 #include <sys/types.h>
818 #include <sys/stat.h>
819 #include <unistd.h>
820 #include <string.h>
821 gboolean
Read_Directory(const gchar * path_real)822 Read_Directory (const gchar *path_real)
823 {
824     GFile *dir;
825     GFileEnumerator *dir_enumerator;
826     GError *error = NULL;
827     gchar *msg;
828     gchar  progress_bar_text[30];
829     guint  nbrfile = 0;
830     double fraction;
831     GList *FileList = NULL;
832     GList *l;
833     gint   progress_bar_index = 0;
834     GAction *action;
835     EtApplicationWindow *window;
836 
837     g_return_val_if_fail (path_real != NULL, FALSE);
838 
839     ReadingDirectory = TRUE;    /* A flag to avoid to start another reading */
840 
841     /* Initialize file list */
842     ET_Core_Free ();
843     ET_Core_Create ();
844     et_application_window_update_actions (ET_APPLICATION_WINDOW (MainWindow));
845 
846     window = ET_APPLICATION_WINDOW (MainWindow);
847 
848     /* Initialize browser list */
849     et_application_window_browser_clear (window);
850 
851     /* Clear entry boxes  */
852     et_application_window_file_area_clear (window);
853     et_application_window_tag_area_clear (window);
854 
855     // Set to unsensitive the Browser Area, to avoid to select another file while loading the first one
856     et_application_window_browser_set_sensitive (window, FALSE);
857 
858     /* Placed only here, to empty the previous list of files */
859     dir = g_file_new_for_path (path_real);
860     dir_enumerator = g_file_enumerate_children (dir,
861                                                 G_FILE_ATTRIBUTE_STANDARD_NAME ","
862                                                 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
863                                                 G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
864                                                 G_FILE_QUERY_INFO_NONE,
865                                                 NULL, &error);
866     if (!dir_enumerator)
867     {
868         // Message if the directory doesn't exist...
869         GtkWidget *msgdialog;
870         gchar *display_path = g_filename_display_name (path_real);
871 
872         msgdialog = gtk_message_dialog_new(GTK_WINDOW(MainWindow),
873                                            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
874                                            GTK_MESSAGE_ERROR,
875                                            GTK_BUTTONS_CLOSE,
876                                            _("Cannot read directory ‘%s’"),
877                                            display_path);
878         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (msgdialog),
879                                                   "%s", error->message);
880         gtk_window_set_title(GTK_WINDOW(msgdialog),_("Directory Read Error"));
881 
882         gtk_dialog_run(GTK_DIALOG(msgdialog));
883         gtk_widget_destroy(msgdialog);
884         g_free (display_path);
885 
886         ReadingDirectory = FALSE; //Allow a new reading
887         et_application_window_browser_set_sensitive (window, TRUE);
888         g_object_unref (dir);
889         g_error_free (error);
890         return FALSE;
891     }
892 
893     /* Open the window to quit recursion (since 27/04/2007 : not only into recursion mode) */
894     et_application_window_set_busy_cursor (window);
895     action = g_action_map_lookup_action (G_ACTION_MAP (MainWindow), "stop");
896     g_settings_bind (MainSettings, "browse-subdir", G_SIMPLE_ACTION (action),
897                      "enabled", G_SETTINGS_BIND_GET);
898     Open_Quit_Recursion_Function_Window();
899 
900     /* Read the directory recursively */
901     msg = g_strdup_printf(_("Search in progress…"));
902     et_application_window_status_bar_message (window, msg, FALSE);
903     g_free (msg);
904     /* Search the supported files. */
905     FileList = read_directory_recursively (FileList, dir_enumerator,
906                                            g_settings_get_boolean (MainSettings,
907                                                                    "browse-subdir"));
908     g_file_enumerator_close (dir_enumerator, NULL, &error);
909     g_object_unref (dir_enumerator);
910     g_object_unref (dir);
911 
912     nbrfile = g_list_length(FileList);
913 
914     et_application_window_progress_set_fraction (window, 0.0);
915     g_snprintf (progress_bar_text, 30, "%d/%u", 0, nbrfile);
916     et_application_window_progress_set_text (window, progress_bar_text);
917 
918     // Load the supported files (Extension recognized)
919     for (l = FileList; l != NULL && !Main_Stop_Button_Pressed;
920          l = g_list_next (l))
921     {
922         GFile *file = l->data;
923         gchar *filename_real = g_file_get_path (file);
924         gchar *display_path = g_filename_display_name (filename_real);
925 
926         msg = g_strdup_printf (_("File: ‘%s’"), display_path);
927         et_application_window_status_bar_message (window, msg, FALSE);
928         g_free(msg);
929         g_free (filename_real);
930         g_free (display_path);
931 
932         ETCore->ETFileList = et_file_list_add (ETCore->ETFileList, file);
933 
934         /* Update the progress bar. */
935         fraction = (++progress_bar_index) / (double) nbrfile;
936         et_application_window_progress_set_fraction (window, fraction);
937         g_snprintf (progress_bar_text, 30, "%d/%u", progress_bar_index,
938                     nbrfile);
939         et_application_window_progress_set_text (window, progress_bar_text);
940         while (gtk_events_pending())
941             gtk_main_iteration();
942     }
943 
944     g_list_free_full (FileList, g_object_unref);
945     et_application_window_progress_set_text (window, "");
946 
947     /* Close window to quit recursion */
948     Destroy_Quit_Recursion_Function_Window();
949     Main_Stop_Button_Pressed = FALSE;
950     action = g_action_map_lookup_action (G_ACTION_MAP (MainWindow), "stop");
951     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
952 
953     //ET_Debug_Print_File_List(ETCore->ETFileList,__FILE__,__LINE__,__FUNCTION__);
954 
955     if (ETCore->ETFileList)
956     {
957         //GList *etfilelist;
958         /* Load the list of file into the browser list widget */
959         et_application_window_browser_toggle_display_mode (window);
960 
961         /* Display the first file */
962         //No need to select first item, because Browser_Display_Tree_Or_Artist_Album_List() does this
963         //etfilelist = ET_Displayed_File_List_First();
964         //if (etfilelist)
965         //{
966         //    ET_Display_File_Data_To_UI((ET_File *)etfilelist->data);
967         //    Browser_List_Select_File_By_Etfile((ET_File *)etfilelist->data,FALSE);
968         //}
969 
970         /* Prepare message for the status bar */
971         if (g_settings_get_boolean (MainSettings, "browse-subdir"))
972         {
973             msg = g_strdup_printf (ngettext ("Found one file in this directory and subdirectories",
974                                              "Found %u files in this directory and subdirectories",
975                                              ETCore->ETFileDisplayedList_Length),
976                                    ETCore->ETFileDisplayedList_Length);
977         }
978         else
979         {
980             msg = g_strdup_printf (ngettext ("Found one file in this directory",
981                                              "Found %u files in this directory",
982                                              ETCore->ETFileDisplayedList_Length),
983                                    ETCore->ETFileDisplayedList_Length);
984         }
985     }else
986     {
987         /* Clear entry boxes */
988         et_application_window_file_area_clear (ET_APPLICATION_WINDOW (MainWindow));
989         et_application_window_tag_area_clear (ET_APPLICATION_WINDOW (MainWindow));
990 
991 	/* Translators: No files, as in "0 files". */
992         et_application_window_browser_label_set_text (ET_APPLICATION_WINDOW (MainWindow),
993                                                       _("No files")); /* See in ET_Display_Filename_To_UI */
994 
995         /* Prepare message for the status bar */
996         if (g_settings_get_boolean (MainSettings, "browse-subdir"))
997             msg = g_strdup(_("No file found in this directory and subdirectories"));
998         else
999             msg = g_strdup(_("No file found in this directory"));
1000     }
1001 
1002     /* Update sensitivity of buttons and menus */
1003     et_application_window_update_actions (window);
1004 
1005     et_application_window_browser_set_sensitive (window, TRUE);
1006 
1007     et_application_window_progress_set_fraction (window, 0.0);
1008     et_application_window_status_bar_message (window, msg, FALSE);
1009     g_free (msg);
1010     et_application_window_set_normal_cursor (window);
1011     ReadingDirectory = FALSE;
1012 
1013     return TRUE;
1014 }
1015 
1016 
1017 
1018 /*
1019  * Recurse the path to create a list of files. Return a GList of the files found.
1020  */
1021 static GList *
read_directory_recursively(GList * file_list,GFileEnumerator * dir_enumerator,gboolean recurse)1022 read_directory_recursively (GList *file_list, GFileEnumerator *dir_enumerator,
1023                             gboolean recurse)
1024 {
1025     GError *error = NULL;
1026     GFileInfo *info;
1027     const char *file_name;
1028     gboolean is_hidden;
1029     GFileType type;
1030 
1031     g_return_val_if_fail (dir_enumerator != NULL, file_list);
1032 
1033     while ((info = g_file_enumerator_next_file (dir_enumerator, NULL, &error))
1034            != NULL)
1035     {
1036         if (Main_Stop_Button_Pressed)
1037         {
1038             g_object_unref (info);
1039             return file_list;
1040         }
1041 
1042         file_name = g_file_info_get_name (info);
1043         is_hidden = g_file_info_get_is_hidden (info);
1044         type = g_file_info_get_file_type (info);
1045 
1046         /* Hidden directory like '.mydir' will also be browsed if allowed. */
1047         if (!is_hidden || (g_settings_get_boolean (MainSettings,
1048                                                    "browse-show-hidden")
1049                            && is_hidden))
1050         {
1051             if (type == G_FILE_TYPE_DIRECTORY)
1052             {
1053                 if (recurse)
1054                 {
1055                     /* Searching for files recursively. */
1056                     GFile *child_dir = g_file_enumerator_get_child (dir_enumerator,
1057                                                                     info);
1058                     GFileEnumerator *childdir_enumerator;
1059                     GError *child_error = NULL;
1060                     childdir_enumerator = g_file_enumerate_children (child_dir,
1061                                                                      G_FILE_ATTRIBUTE_STANDARD_NAME ","
1062                                                                      G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1063                                                                      G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
1064                                                                      G_FILE_QUERY_INFO_NONE,
1065                                                                      NULL, &child_error);
1066                     if (!childdir_enumerator)
1067                     {
1068                         gchar *child_path;
1069                         gchar *display_path;
1070 
1071                         child_path = g_file_get_path (child_dir);
1072                         display_path = g_filename_display_name (child_path);
1073 
1074                         Log_Print (LOG_ERROR,
1075                                    _("Error opening directory ‘%s’: %s"),
1076                                    display_path, child_error->message);
1077 
1078                         g_free (display_path);
1079                         g_free (child_path);
1080                         g_error_free (child_error);
1081                         g_object_unref (child_dir);
1082                         g_object_unref (info);
1083                         continue;
1084                     }
1085                     file_list = read_directory_recursively (file_list,
1086                                                             childdir_enumerator,
1087                                                             recurse);
1088                     g_object_unref (child_dir);
1089                     g_file_enumerator_close (childdir_enumerator, NULL,
1090                                              &error);
1091                     g_object_unref (childdir_enumerator);
1092                 }
1093             }
1094             else if (type == G_FILE_TYPE_REGULAR &&
1095                      et_file_is_supported (file_name))
1096             {
1097                 GFile *file = g_file_enumerator_get_child (dir_enumerator, info);
1098                 file_list = g_list_append (file_list, file);
1099             }
1100 
1101             // Just to not block X events
1102             while (gtk_events_pending())
1103                 gtk_main_iteration();
1104         }
1105         g_object_unref (info);
1106     }
1107 
1108     if (error)
1109     {
1110         Log_Print (LOG_ERROR, _("Cannot read directory ‘%s’"), error->message);
1111         g_error_free (error);
1112     }
1113 
1114     return file_list;
1115 }
1116 
1117 /*
1118  * Window with the 'STOP' button to stop recursion when reading directories
1119  */
1120 static void
Open_Quit_Recursion_Function_Window(void)1121 Open_Quit_Recursion_Function_Window (void)
1122 {
1123     if (QuitRecursionWindow != NULL)
1124         return;
1125     QuitRecursionWindow = gtk_message_dialog_new (GTK_WINDOW (MainWindow),
1126                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
1127                                                   GTK_MESSAGE_OTHER,
1128                                                   GTK_BUTTONS_NONE,
1129                                                   "%s",
1130                                                   _("Searching for audio files…"));
1131     gtk_window_set_title (GTK_WINDOW (QuitRecursionWindow), _("Searching"));
1132     gtk_dialog_add_button (GTK_DIALOG (QuitRecursionWindow), _("_Stop"),
1133                            GTK_RESPONSE_CANCEL);
1134 
1135     g_signal_connect (G_OBJECT (QuitRecursionWindow),"response",
1136                       G_CALLBACK (et_on_quit_recursion_response), NULL);
1137 
1138     gtk_widget_show_all(QuitRecursionWindow);
1139 }
1140 
1141 static void
Destroy_Quit_Recursion_Function_Window(void)1142 Destroy_Quit_Recursion_Function_Window (void)
1143 {
1144     if (QuitRecursionWindow)
1145     {
1146         gtk_widget_destroy(QuitRecursionWindow);
1147         QuitRecursionWindow = NULL;
1148         /*Statusbar_Message(_("Recursive file search interrupted."),FALSE);*/
1149     }
1150 }
1151 
1152 static void
et_on_quit_recursion_response(GtkDialog * dialog,gint response_id,gpointer user_data)1153 et_on_quit_recursion_response (GtkDialog *dialog, gint response_id,
1154                                gpointer user_data)
1155 {
1156     switch (response_id)
1157     {
1158         case GTK_RESPONSE_CANCEL:
1159             Action_Main_Stop_Button_Pressed ();
1160             Destroy_Quit_Recursion_Function_Window ();
1161             break;
1162         case GTK_RESPONSE_DELETE_EVENT:
1163             Destroy_Quit_Recursion_Function_Window ();
1164             break;
1165         default:
1166             g_assert_not_reached ();
1167             break;
1168     }
1169 }
1170 
1171 /*
1172  * To stop the recursive search within directories or saving files
1173  */
Action_Main_Stop_Button_Pressed(void)1174 void Action_Main_Stop_Button_Pressed (void)
1175 {
1176     GAction *action;
1177 
1178     action = g_action_map_lookup_action (G_ACTION_MAP (MainWindow), "stop");
1179     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
1180     Main_Stop_Button_Pressed = TRUE;
1181 }
1182