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