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