1 #include <config.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <unistd.h>
6 #include <errno.h>
7 #include <string.h>
8 #include <glib.h>
9 #include <glib/gi18n.h>
10 #include <gtk/gtk.h>
11 #include <glade/glade.h>
12
13 #include "elist.h"
14 #include "str_util.h"
15 #include "str_convert.h"
16 #include "file_util.h"
17 #include "mru.h"
18 #include "prefs.h"
19 #include "audio_file.h"
20 #include "file_list.h"
21 #include "char_conv_dlg.h"
22 #include "progress_dlg.h"
23 #include "cursor.h"
24 #include "help.h"
25
26 #include "rename_tab.h"
27
28
29 enum {
30 APPLY_TO_ALL = 0,
31 APPLY_TO_SELECTED = 1
32 };
33
34
35 /* widgets */
36 static GtkCombo *combo_rename_format = NULL;
37 static GtkEntry *ent_rename_format = NULL;
38 static GtkButton *b_rename_edit_format = NULL;
39 static GtkButton *b_rename_go = NULL;
40 static GtkComboBox *combo_rename_apply = NULL;
41
42 /* preferences */
43 static MRUList *format_mru;
44
45
46 /*** private functions ******************************************************/
47
append_field(GString * gstr,audio_file * af,int field)48 static gboolean append_field(GString *gstr, audio_file *af, int field)
49 {
50 char *temp;
51 char *value;
52 chconv_rename_options options;
53
54 audio_file_get_field(af, field, (const char **)&temp);
55 if (temp == NULL || *temp == 0)
56 return FALSE;
57
58 /* apply character conversions */
59 options = chconv_get_rename_options();
60
61 if (options.invalid_conv != NULL && options.invalid_conv[0] != 0)
62 value = str_replace_char(temp, '/', options.invalid_conv[0]);
63 else
64 value = str_remove_char(temp, '/');
65
66 if (options.space_conv != NULL && options.space_conv[0] != 0) {
67 temp = value;
68 value = str_replace_char(value, ' ', options.space_conv[0]);
69 free(temp);
70 }
71
72 if (options.case_conv != CASE_CONV_NONE) {
73 temp = value;
74 value = str_convert_case(value, options.case_conv);
75 free(temp);
76 }
77
78 str_trim(value);
79
80 /* convert to filesystem encoding */
81 temp = value;
82 value = str_filename_from_utf8(value, "utf8_conversion_error");
83 free(temp);
84
85 /* append to name */
86 if (field == AF_TRACK) {
87 long track;
88 char *endptr;
89 track = strtol(value, &endptr, 10);
90 if (*endptr == 0)
91 g_string_sprintfa(gstr, "%02li", track);
92 else
93 g_string_append(gstr, value);
94 }
95 else {
96 g_string_append(gstr, value);
97 }
98
99 free(value);
100
101 return TRUE;
102 }
103
build_file_name(const gchar * format,audio_file * af,GString * new_name)104 static gboolean build_file_name(const gchar *format, audio_file *af, GString *new_name)
105 {
106 gchar *p;
107 gint i, span;
108
109 /* fill in the filename from the tag according to the given format */
110 i = 0;
111 while (TRUE) {
112 p = index(&format[i], '<');
113 if (p != NULL) {
114 span = (gint)(p - &format[i]);
115 if (span > 0) {
116 g_string_sprintfa(new_name, "%.*s", span, &format[i]);
117 i += span;
118 } else if (strncmp(&format[i], "<title>", 7) == 0) {
119 if (!append_field(new_name, af, AF_TITLE))
120 return FALSE;
121 i += 7;
122 } else if (strncmp(&format[i], "<artist>", 8) == 0) {
123 if (!append_field(new_name, af, AF_ARTIST))
124 return FALSE;
125 i += 8;
126 } else if (strncmp(&format[i], "<album>", 7) == 0) {
127 if (!append_field(new_name, af, AF_ALBUM))
128 return FALSE;
129 i += 7;
130 } else if (strncmp(&format[i], "<year>", 6) == 0) {
131 if (!append_field(new_name, af, AF_YEAR))
132 return FALSE;
133 i += 6;
134 } else if (strncmp(&format[i], "<comment>", 9) == 0) {
135 if (!append_field(new_name, af, AF_COMMENT))
136 return FALSE;
137 i += 9;
138 } else if (strncmp(&format[i], "<track>", 7) == 0) {
139 if (!append_field(new_name, af, AF_TRACK))
140 return FALSE;
141 i += 7;
142 } else if (strncmp(&format[i], "<genre>", 7) == 0) {
143 if (!append_field(new_name, af, AF_GENRE))
144 return FALSE;
145 i += 7;
146 } else {
147 g_string_append_c(new_name, format[i++]);
148 }
149 }
150 else {
151 g_string_append(new_name, &format[i]);
152 break;
153 }
154 }
155
156 /* add back the extension */
157 g_string_append(new_name, audio_file_get_extension(af));
158
159 return TRUE;
160 }
161
162
rename_files(GEList * file_list)163 static void rename_files(GEList *file_list)
164 {
165 GList *iter;
166 audio_file *af = NULL;
167 const gchar *format;
168 gchar *last_full_name = "";
169 gchar *orig_full_name, *orig_name, *orig_name_utf8;
170 GString *new_full_name, *new_path;
171 gchar *p;
172 gchar *temp_utf8;
173 gint new_dirs;
174 gint count_total, count_renamed;
175 gboolean moving;
176 int res, save_errno;
177
178 new_path = g_string_sized_new(256);
179 new_full_name = g_string_sized_new(256);
180
181 format = gtk_entry_get_text(ent_rename_format);
182 moving = (strchr(format, '/') ? TRUE : FALSE);
183
184 if (moving)
185 pd_start(_("Moving Files"));
186 else
187 pd_start(_("Renaming Files"));
188 pd_printf(PD_ICON_INFO, _("Starting in directory \"%s\""), fl_get_working_dir_utf8());
189
190 count_total = 0;
191 count_renamed = 0;
192 for (iter = g_elist_first(file_list); iter; iter = g_list_next(iter)) {
193 /* flush pending gtk operations so the UI doesn't freeze */
194 pd_scroll_to_bottom();
195 while (gtk_events_pending()) gtk_main_iteration();
196 if (pd_stop_requested()) {
197 pd_printf(PD_ICON_WARN, _("Operation stopped at user's request"));
198 break;
199 }
200
201 count_total++;
202
203 orig_full_name = (gchar *)iter->data;
204 if (!fu_compare_file_paths(last_full_name, orig_full_name)) {
205 temp_utf8 = str_filename_to_utf8(orig_full_name, _("(UTF8 conversion error)"));
206 p = g_utf8_strrchr(temp_utf8, -1, '/');
207 pd_printf(PD_ICON_INFO, _("Entering directory \"%.*s\""), (gint)(p-temp_utf8), temp_utf8);
208 free(temp_utf8);
209 }
210 last_full_name = orig_full_name;
211
212 /* read the file information */
213 orig_name = fu_last_n_path_components(orig_full_name, 1);
214 orig_name_utf8 = str_filename_to_utf8(orig_name, _("(UTF8 conversion error)"));
215
216 res = audio_file_new(&af, orig_full_name, FALSE);
217 if (res != AF_OK) {
218 pd_printf(PD_ICON_FAIL, _("Error renaming \"%s\""), orig_name_utf8);
219
220 if (res == AF_ERR_FILE)
221 pd_printf(PD_ICON_NONE, _("Couldn't open file for reading"));
222 else if (res == AF_ERR_FORMAT)
223 pd_printf(PD_ICON_NONE, _("Audio format not recognized"));
224 else
225 pd_printf(PD_ICON_NONE, _("Unknown error"));
226
227 goto _continue;
228 }
229
230 if (!audio_file_has_tag(af)) {
231 pd_printf(PD_ICON_FAIL, _("Error renaming \"%s\""), orig_name_utf8);
232 pd_printf(PD_ICON_NONE, _("File has no tag"));
233 goto _continue;
234 }
235
236 /* build the new file name (with path) */
237 g_string_truncate(new_full_name, 0);
238 if (format[0] != '/' && (p = strrchr(orig_full_name, '/')))
239 g_string_sprintfa(new_full_name, "%.*s/", (gint)(p-orig_full_name), orig_full_name);
240
241 if (!build_file_name(format, af, new_full_name)) {
242 pd_printf(PD_ICON_FAIL, _("Error renaming \"%s\""), orig_name_utf8);
243 pd_printf(PD_ICON_NONE, _("One of the tag fields is empty"));
244 goto _continue;
245 }
246 if (strcmp(orig_full_name, new_full_name->str) == 0) {
247 pd_printf(PD_ICON_OK, _("File name \"%s\" already in desired format"), orig_name_utf8);
248 goto _continue;
249 }
250
251 if (fu_exists(new_full_name->str)) {
252 if (moving)
253 pd_printf(PD_ICON_FAIL, _("Error moving \"%s\""), orig_name_utf8);
254 else
255 pd_printf(PD_ICON_FAIL, _("Error renaming \"%s\""), orig_name_utf8);
256 temp_utf8 = str_filename_to_utf8(new_full_name->str, _("(UTF8 conversion error)"));
257 pd_printf(PD_ICON_NONE, _("File \"%s\" already exists"), temp_utf8);
258 free(temp_utf8);
259 goto _continue;
260 }
261
262 /* create the destination dir if necessary */
263 p = strrchr(new_full_name->str, '/');
264 if (moving && p) {
265 new_dirs = 0;
266 g_string_sprintf(new_path, "%.*s/", (gint)(p-new_full_name->str), new_full_name->str);
267 res = fu_make_dir_tree(new_path->str, &new_dirs);
268 if (!res) {
269 save_errno = errno;
270 temp_utf8 = str_filename_to_utf8(new_path->str, _("(UTF8 conversion error)"));
271 pd_printf(PD_ICON_FAIL, _("Error creating directory \"%s\""), temp_utf8);
272 pd_printf(PD_ICON_NONE, "%s (%d)", strerror(save_errno), save_errno);
273 free(temp_utf8);
274 goto _continue;
275 }
276 if (new_dirs) {
277 pd_printf(PD_ICON_OK, _("Created directory \"%s\""), new_path->str);
278 }
279 }
280
281 /* rename the file */
282 res = rename(orig_full_name, new_full_name->str);
283 if (res != 0) {
284 save_errno = errno;
285 if (moving)
286 pd_printf(PD_ICON_FAIL, _("Error moving \"%s\""), orig_name_utf8);
287 else
288 pd_printf(PD_ICON_FAIL, _("Error renaming \"%s\""), orig_name_utf8);
289 pd_printf(PD_ICON_NONE, "%s (%d)", strerror(save_errno), save_errno);
290 }
291 else {
292 if (moving) {
293 temp_utf8 = str_filename_to_utf8(new_full_name->str, _("(UTF8 conversion error)"));
294 pd_printf(PD_ICON_OK, _("Moved \"%s\" to \"%s\""), orig_name_utf8, temp_utf8);
295 }
296 else {
297 temp_utf8 = str_filename_to_utf8(g_basename(new_full_name->str), _("(UTF8 conversion error)"));
298 pd_printf(PD_ICON_OK, _("Renamed \"%s\" to \"%s\""), orig_name_utf8, temp_utf8);
299 }
300 free(temp_utf8);
301 count_renamed++;
302 }
303
304 _continue:
305 if (af) {
306 audio_file_delete(af);
307 af = NULL;
308 }
309 free(orig_name_utf8);
310 orig_name_utf8 = NULL;
311 }
312
313 g_string_free(new_full_name, TRUE);
314 g_string_free(new_path, TRUE);
315
316 if (moving)
317 pd_printf(PD_ICON_INFO, _("Done (moved %d of %d files)"), count_renamed, count_total);
318 else
319 pd_printf(PD_ICON_INFO, _("Done (renamed %d of %d files)"), count_renamed, count_total);
320 pd_end();
321
322 if (count_renamed > 0)
323 fl_refresh(TRUE);
324 }
325
326
start_operation()327 static void start_operation()
328 {
329 GEList *file_list;
330
331 if (gtk_combo_box_get_active(combo_rename_apply) == APPLY_TO_ALL)
332 file_list = fl_get_all_files();
333 else
334 file_list = fl_get_selected_files();
335
336 if (g_elist_length(file_list) == 0) {
337 pd_start(NULL);
338 pd_printf(PD_ICON_FAIL, _("No files selected"));
339 pd_end();
340
341 g_elist_free(file_list);
342 return;
343 }
344
345 mru_add(format_mru, gtk_entry_get_text(ent_rename_format));
346 gtk_combo_set_popdown_strings(combo_rename_format, GLIST(format_mru->list));
347
348 cursor_set_wait();
349 gtk_widget_set_sensitive(GTK_WIDGET(b_rename_go), FALSE);
350 rename_files(file_list);
351 gtk_widget_set_sensitive(GTK_WIDGET(b_rename_go), TRUE);
352 cursor_set_normal();
353
354 g_elist_free(file_list);
355 }
356
357
358 /*** UI callbacks ***********************************************************/
359
cb_rename_go(GtkButton * button,gpointer user_data)360 void cb_rename_go(GtkButton *button, gpointer user_data)
361 {
362 start_operation();
363 }
364
cb_show_rename_chconv(GtkButton * button,gpointer user_data)365 void cb_show_rename_chconv(GtkButton *button, gpointer user_data)
366 {
367 chconv_display(CHCONV_RENAME);
368 }
369
cb_rename_help(GtkButton * button,gpointer user_data)370 void cb_rename_help(GtkButton *button, gpointer user_data)
371 {
372 help_display(HELP_RENAME_FORMAT);
373 }
374
cb_file_selection_changed(GtkTreeSelection * selection,gpointer data)375 static void cb_file_selection_changed(GtkTreeSelection *selection, gpointer data)
376 {
377 if (fl_count_selected() > 0)
378 gtk_combo_box_set_active(combo_rename_apply, APPLY_TO_SELECTED);
379 else
380 gtk_combo_box_set_active(combo_rename_apply, APPLY_TO_ALL);
381 }
382
383
384 /*** public functions *******************************************************/
385
rt_init(GladeXML * xml)386 void rt_init(GladeXML *xml)
387 {
388 GtkStyle *style;
389 GtkWidget *w;
390 GEList *format_list;
391
392 /*
393 * get the widgets from glade
394 */
395
396 combo_rename_format = GTK_COMBO(glade_xml_get_widget(xml, "combo_rename_format"));
397 ent_rename_format = GTK_ENTRY(glade_xml_get_widget(xml, "ent_rename_format"));
398 b_rename_edit_format = GTK_BUTTON(glade_xml_get_widget(xml, "b_rename_edit_format"));
399 b_rename_go = GTK_BUTTON(glade_xml_get_widget(xml, "b_rename_go"));
400 combo_rename_apply = GTK_COMBO_BOX(glade_xml_get_widget(xml, "combo_rename_apply"));
401
402 /* initialize some widgets' state */
403 gtk_combo_box_set_active(combo_rename_apply, APPLY_TO_ALL);
404
405 /* connect signals */
406 g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(glade_xml_get_widget(xml, "tv_files"))),
407 "changed", G_CALLBACK(cb_file_selection_changed), NULL);
408
409
410 /*
411 * set the title colors
412 */
413
414 w = glade_xml_get_widget(xml, "lab_rename_title");
415 gtk_widget_ensure_style(w);
416 style = gtk_widget_get_style(w);
417
418 gtk_widget_modify_fg(w, GTK_STATE_NORMAL, &style->text[GTK_STATE_SELECTED]);
419
420 w = glade_xml_get_widget(xml, "box_rename_title");
421 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &style->base[GTK_STATE_SELECTED]);
422
423
424 /*
425 * get the preference values, or set them to defaults
426 */
427
428 /* format_mru */
429 format_list = pref_get_ref("rt:format_mru");
430 if (!format_list) {
431 GEList *temp_list = g_elist_new();
432 g_elist_append(temp_list, "<track>. <title>");
433 g_elist_append(temp_list, "<artist> - <title>");
434 g_elist_append(temp_list, "<artist> - <album>/<track>. <title>");
435 g_elist_append(temp_list, "<artist>/<album>/<track>. <title>");
436 g_elist_append(temp_list, "<artist>/<album> (<year>)/<track>. <title>");
437 format_list = pref_set("rt:format_mru", PREF_STRING | PREF_LIST, temp_list);
438 g_elist_free(temp_list);
439 }
440 format_mru = mru_new_from_list(10, format_list);
441
442 gtk_combo_set_popdown_strings(combo_rename_format, GLIST(format_mru->list));
443 }
444
445