1 #include <config.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <glib.h>
7 #include <glib/gi18n.h>
8 #include <gtk/gtk.h>
9 #include <glade/glade.h>
10 
11 #include "elist.h"
12 #include "math_util.h"
13 #include "str_util.h"
14 #include "str_convert.h"
15 #include "file_util.h"
16 #include "gtk_util.h"
17 #include "file_list.h"
18 #include "progress_dlg.h"
19 #include "cursor.h"
20 #include "mru.h"
21 #include "prefs.h"
22 #include "audio_file.h"
23 #include "genre.h"
24 
25 #include "playlist_tab.h"
26 
27 
28 enum {
29 	APPLY_TO_ALL = 0,
30 	APPLY_TO_SELECTED = 1
31 };
32 
33 
34 /* data needed to sort files by their tag fields */
35 typedef struct {
36 	gchar *name;
37 	audio_file *af;
38 } file_info;
39 
40 /* widgets */
41 static GtkButton *b_playlist_go = NULL;
42 static GtkComboBox *combo_playlist_apply = NULL;
43 static GtkToggleButton *rb_create_dir = NULL;
44 static GtkToggleButton *rb_create_toplevel = NULL;
45 static GtkToggleButton *rb_create_both = NULL;
46 static GtkToggleButton *rb_name_dir = NULL;
47 static GtkToggleButton *rb_name_set = NULL;
48 static GtkToggleButton *rb_sort_name = NULL;
49 static GtkToggleButton *rb_sort_field = NULL;
50 static GtkCheckButton *cb_sort_across_dirs = NULL;
51 static GtkEntry *ent_pl_name = NULL;
52 static GtkEntry *ent_pl_extension = NULL;
53 static GtkComboBox *combo_field = NULL;
54 
55 /* preferences */
56 static long* create_in;
57 static gboolean* name_dir;
58 static char* name;
59 static char* extension;
60 static gboolean* sort_by_name;
61 static long* sort_field;
62 static gboolean* sort_across_dirs;
63 
64 
65 /*** private functions ******************************************************/
66 
compare_audio_file(file_info * a,file_info * b)67 static gint compare_audio_file(file_info *a, file_info *b)
68 {
69 	const char *field_a, *field_b;
70 	gboolean have_a, have_b;
71 	int result = 0;
72 
73 	if (!*sort_across_dirs) {
74 		/* if sort_across_dirs is FALSE the file's directory takes
75 		   precedence over the tag contents */
76 		if (!fu_compare_file_paths(a->name, b->name)) {
77 			char *dir_a = g_dirname(a->name);
78 			char *dir_b = g_dirname(b->name);
79 			result = strcoll(dir_a, dir_b);
80 			free(dir_a);
81 			free(dir_b);
82 			return result;
83 		}
84 	}
85 
86 	have_a = (audio_file_get_field(a->af, *sort_field, &field_a) == AF_OK);
87 	if (have_a) field_a = strdup(field_a);
88 	have_b = (audio_file_get_field(b->af, *sort_field, &field_b) == AF_OK);
89 
90 	if (have_a ^ have_b) {
91 		/* files with tags will come before files w/o */
92 		if (have_a)
93 			result = -1;
94 		else
95 			result = 1;
96 	}
97 	else if (have_a & have_b) {
98 		/* if both have tags, sort by the selected field */
99 		switch (*sort_field) {
100 			case AF_TITLE:
101 			case AF_ARTIST:
102 			case AF_ALBUM:
103 			case AF_COMMENT:
104 			case AF_GENRE:
105 				result = strcoll(field_a, field_b);
106 				break;
107 
108 			case AF_YEAR:
109 			case AF_TRACK:
110 				result = compare(atoi(field_a), atoi(field_b));
111 				break;
112 
113 			default: /* should never get here */
114 				g_warning("compare_file_info: unexpected value for *sort_field");
115 				result = 0;
116 				break;
117 		}
118 	}
119 
120 	/* if all else is equal, still sort by file name */
121 	if (result == 0)
122 		result = strcoll(a->name, b->name);
123 
124 	if (have_a)
125 		free((char*)field_a);
126 
127 	return result;
128 }
129 
sort_by_field(GEList * file_list)130 static void sort_by_field(GEList *file_list)
131 {
132 	file_info *info;
133 	GEList *file_info_list;
134 	audio_file *af;
135 	GList *i, *j;
136 	int res;
137 
138 	file_info_list = g_elist_new();
139 
140 	/* make a list with the file names and tags */
141 	for (i = g_elist_first(file_list); i; i = g_list_next(i)) {
142 		res = audio_file_new(&af, i->data, FALSE);
143 		if (res == AF_OK) {
144 			info = malloc(sizeof(file_info));
145 			info->name = i->data;
146 			info->af = af;
147 			g_elist_append(file_info_list, info);
148 		}
149 		else {
150 			char *temp_utf8 = str_filename_to_utf8(i->data, _("(UTF8 conversion error)"));
151 			pd_printf(PD_ICON_WARN, _("Skipping file \"%s\""), temp_utf8);
152 			free(temp_utf8);
153 			if (res == AF_ERR_FILE)
154 				pd_printf(PD_ICON_NONE, _("Couldn't open file for reading"));
155 			else if (res == AF_ERR_FORMAT)
156 				pd_printf(PD_ICON_NONE, _("Audio format not recognized"));
157 			else
158 				pd_printf(PD_ICON_NONE, _("Unknown error (%d)"), res);
159 		}
160 	}
161 
162 	/* sort the list */
163 	g_elist_sort(file_info_list, (GCompareFunc)compare_audio_file);
164 
165 	/* copy the new sort order to the original list */
166 	while (g_elist_length(file_list) > g_elist_length(file_info_list))
167 		g_elist_extract(file_list, g_elist_last(file_list));
168 
169 	i = g_elist_first(file_list);
170 	j = g_elist_first(file_info_list);
171 	while (i) {
172 		i->data = ((file_info *)j->data)->name;
173 
174 		audio_file_delete(((file_info *)j->data)->af);
175 		free(j->data);
176 
177 		i = g_list_next(i);
178 		j = g_list_next(j);
179 	}
180 
181 	g_elist_free(file_info_list);
182 }
183 
184 
playlist_name(gchar * base_dir)185 static gchar *playlist_name(gchar *base_dir)
186 {
187 	static GString *full_name = NULL;
188 	gchar *temp_name;
189 	gchar *temp_ext;
190 
191 	if (full_name == NULL)
192 		full_name = g_string_sized_new(100);
193 
194 	if (gtk_toggle_button_get_active(rb_name_set))
195 		temp_name = str_filename_from_utf8(name, "utf8_conversion_error");
196 	else if (*base_dir == 0 || strcmp(base_dir, ".") == 0)
197 		temp_name = fu_last_path_component(fl_get_working_dir());
198 	else
199 		temp_name = fu_last_path_component(base_dir);
200 
201 	temp_ext = str_filename_from_utf8(extension, "utf8_conversion_error");
202 
203 	if (*base_dir)
204 		g_string_sprintf(full_name, "%s/%s.%s", base_dir, temp_name, temp_ext);
205 	else
206 		g_string_sprintf(full_name, "%s.%s", temp_name, temp_ext);
207 
208 	free(temp_name);
209 	free(temp_ext);
210 
211 	return full_name->str;
212 }
213 
214 
write_playlist(gchar * base_dir,gchar * file_name,GEList * file_list)215 static gint write_playlist(gchar *base_dir, gchar *file_name, GEList *file_list)
216 {
217 	FILE *f;
218 	GList *iter;
219 	gchar *entry;
220 	gchar *aux;
221 	gint len = strlen(base_dir);
222 	gint count = 0;
223 
224 	f = fopen(file_name, "w");
225 	if (f == NULL)
226 		return count;
227 
228 	/* the list comes sorted by file name... */
229 	if (gtk_toggle_button_get_active(rb_sort_field)) {
230 		/* ...sort it by tag field if requested */
231 		sort_by_field(file_list);
232 	}
233 
234 	for (iter = g_elist_first(file_list); iter; iter = g_list_next(iter)) {
235 		aux = iter->data;
236 		if (*base_dir && strstr(aux, base_dir) == aux) {
237 			aux += len;
238 			while (*aux == '/') aux++;
239 		}
240 
241 		entry = strdup(aux);
242 		str_ascii_replace_char(entry, '/', '\\');
243 		fprintf(f, "%s\n", entry);
244 		free(entry);
245 
246 		count++;
247 	}
248 
249 	fclose(f);
250 
251 	return count;
252 }
253 
254 
create_playlist_in_dir(gchar * base_dir,GEList * file_list)255 static gboolean create_playlist_in_dir(gchar *base_dir, GEList *file_list)
256 {
257 	gchar *pl_name;
258 	gchar *pl_name_display;
259 	gint ret, save_errno;
260 
261 	pl_name = playlist_name(base_dir);
262 	pl_name_display = str_filename_to_utf8(g_basename(pl_name), _("(UTF8 conversion error)"));
263 
264 	ret = write_playlist(base_dir, pl_name, file_list);
265 	if (ret == 0) {
266 		save_errno = errno;
267 		pd_printf(PD_ICON_FAIL, _("Error creating playlist \"%s\""), pl_name_display);
268 		pd_printf(PD_ICON_NONE, "%s (%d)", strerror(save_errno), save_errno);
269 		free(pl_name_display);
270 		return FALSE;
271 	}
272 	else {
273 		pd_printf(PD_ICON_OK, _("Wrote \"%s\" (%d entries)"), pl_name_display, ret);
274 		free(pl_name_display);
275 		return TRUE;
276 	}
277 }
278 
279 
create_playlists(GEList * file_list)280 static void create_playlists(GEList *file_list)
281 {
282 	GEList *aux_list;
283 	GList *iter;
284 	GString *path;
285 	gchar *full_name;
286 	gchar *last_full_name = "";
287 	gchar *p;
288 	gchar *temp_utf8;
289 	gboolean create_dir;
290 	gboolean create_toplevel;
291 	gint count_total, count_written;
292 	gboolean ret;
293 
294 	create_dir = gtk_toggle_button_get_active(rb_create_dir) ||
295 		     gtk_toggle_button_get_active(rb_create_both);
296 	create_toplevel = gtk_toggle_button_get_active(rb_create_toplevel) ||
297 			  gtk_toggle_button_get_active(rb_create_both);
298 
299 	pd_start(_("Writing Playlists"));
300 	pd_printf(PD_ICON_INFO, _("Starting in directory \"%s\""), fl_get_working_dir_utf8());
301 
302 	count_total = 0;
303 	count_written = 0;
304 
305 	if (create_toplevel) {
306 		/* make a copy of the list because it may need to be sorted,
307 		   and we don't want to change the original */
308 		aux_list = g_elist_copy(file_list);
309 
310 		count_total++;
311 		ret = create_playlist_in_dir("", aux_list);
312 		if (ret)
313 			count_written++;
314 
315 		g_elist_free(aux_list);
316 	}
317 
318 	if (create_dir) {
319 		aux_list = g_elist_new();
320 		path = g_string_sized_new(200);
321 		g_string_assign(path, "");
322 
323 		iter = g_elist_first(file_list);
324 		while (iter) {
325 			/* flush pending gtk operations so the UI doesn't freeze */
326 			pd_scroll_to_bottom();
327 			while (gtk_events_pending()) gtk_main_iteration();
328 			if (pd_stop_requested()) {
329 				pd_printf(PD_ICON_WARN, _("Operation stopped at user's request"));
330 				break;
331 			}
332 
333 			full_name = (gchar*)iter->data;
334 			if (!fu_compare_file_paths(last_full_name, full_name)) {
335 				if (!create_toplevel || *(path->str)) {
336 					count_total++;
337 					ret = create_playlist_in_dir(path->str, aux_list);
338 					if (ret)
339 						count_written++;
340 				}
341 
342 				p = strrchr(full_name, '/');
343 				g_string_sprintf(path, "%.*s", (gint)(p-full_name), full_name);
344 				g_elist_clear(aux_list);
345 
346 				temp_utf8 = str_filename_to_utf8(path->str, _("(UTF8 conversion error)"));
347 				pd_printf(PD_ICON_INFO, _("Entering directory \"%s\""), temp_utf8);
348 				free(temp_utf8);
349 			}
350 			g_elist_append(aux_list, full_name);
351 			last_full_name = full_name;
352 
353 			iter = g_list_next(iter);
354 		}
355 		if (g_elist_length(aux_list) > 0) {
356 			/* write the last one */
357 			count_total++;
358 			ret = create_playlist_in_dir(path->str, aux_list);
359 			if (ret)
360 				count_written++;
361 		}
362 
363 		g_elist_free(aux_list);
364 		g_string_free(path, TRUE);
365 	}
366 
367 	pd_printf(PD_ICON_INFO, _("Done (wrote %d of %d playlists)"), count_written, count_total);
368 	pd_end();
369 }
370 
371 
372 /* sets the interface state according to the preferences */
from_prefs()373 static void from_prefs()
374 {
375 	switch (*create_in) {
376 		case 0: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_create_dir), TRUE);
377 			break;
378 		case 1: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_create_toplevel), TRUE);
379 			break;
380 		case 2: gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_create_both), TRUE);
381 			break;
382 	}
383 
384 	if (*name_dir) {
385 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_name_dir), TRUE);
386 		gtk_widget_set_sensitive(GTK_WIDGET(ent_pl_name), FALSE);
387 	}
388 	else {
389 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_name_set), TRUE);
390 		gtk_widget_set_sensitive(GTK_WIDGET(ent_pl_name), TRUE);
391 	}
392 	gtk_entry_set_text(ent_pl_name, name);
393 	gtk_entry_set_text(ent_pl_extension, extension);
394 
395 	if (*sort_by_name) {
396 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_sort_name), TRUE);
397 		gtk_widget_set_sensitive(GTK_WIDGET(combo_field), FALSE);
398 		gtk_widget_set_sensitive(GTK_WIDGET(cb_sort_across_dirs), FALSE);
399 	}
400 	else {
401 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb_sort_field), TRUE);
402 		gtk_widget_set_sensitive(GTK_WIDGET(combo_field), TRUE);
403 		gtk_widget_set_sensitive(GTK_WIDGET(cb_sort_across_dirs), TRUE);
404 	}
405 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_sort_across_dirs), *sort_across_dirs);
406 	gtk_combo_box_set_active(combo_field, *sort_field);
407 }
408 
409 /* sets the preferences according to the curent interface state */
to_prefs()410 static void to_prefs()
411 {
412 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_create_dir)))
413 		*create_in = 0;
414 	else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_create_toplevel)))
415 		*create_in = 1;
416 	else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_create_both)))
417 		*create_in = 2;
418 
419 	*name_dir = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_name_dir));
420 	name = pref_set("pt:name", PREF_STRING, (void*)gtk_entry_get_text(ent_pl_name));
421 	extension = pref_set("pt:extension", PREF_STRING, (void*)gtk_entry_get_text(ent_pl_extension));
422 
423 	*sort_by_name = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rb_sort_name));
424 	*sort_across_dirs = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_sort_across_dirs));
425 	*sort_field = gtk_combo_box_get_active(combo_field);
426 }
427 
428 
429 /* orchestrates the call to create_playlists() */
start_operation()430 static void start_operation()
431 {
432 	GEList *file_list;
433 
434 	if (gtk_combo_box_get_active(combo_playlist_apply) == APPLY_TO_ALL)
435 		file_list = fl_get_all_files();
436 	else
437 		file_list = fl_get_selected_files();
438 
439 	if (g_elist_length(file_list) == 0) {
440 		pd_start(_("Writing Playlists"));
441 		pd_printf(PD_ICON_FAIL, _("No files selected"));
442 		pd_end();
443 
444 		g_elist_free(file_list);
445 		return;
446 	}
447 
448 	to_prefs();
449 
450 	cursor_set_wait();
451 	gtk_widget_set_sensitive(GTK_WIDGET(b_playlist_go), FALSE);
452 	create_playlists(file_list);
453 	gtk_widget_set_sensitive(GTK_WIDGET(b_playlist_go), TRUE);
454 	cursor_set_normal();
455 
456 	g_elist_free(file_list);
457 }
458 
459 
460 /*** UI callbacks ***********************************************************/
461 
cb_playlist_go(GtkButton * button,gpointer user_data)462 void cb_playlist_go(GtkButton *button, gpointer user_data)
463 {
464 	start_operation();
465 }
466 
cb_name_set(GtkToggleButton * button,gpointer user_data)467 void cb_name_set(GtkToggleButton *button, gpointer user_data)
468 {
469 	gtk_widget_set_sensitive(GTK_WIDGET(ent_pl_name),
470 				 gtk_toggle_button_get_active(button));
471 }
472 
cb_sort_field(GtkToggleButton * button,gpointer user_data)473 void cb_sort_field(GtkToggleButton *button, gpointer user_data)
474 {
475 	gboolean active = gtk_toggle_button_get_active(button);
476 
477 	gtk_widget_set_sensitive(GTK_WIDGET(combo_field), active);
478 	gtk_widget_set_sensitive(GTK_WIDGET(cb_sort_across_dirs), active);
479 }
480 
cb_file_selection_changed(GtkTreeSelection * selection,gpointer data)481 static void cb_file_selection_changed(GtkTreeSelection *selection, gpointer data)
482 {
483 	if (fl_count_selected() > 0)
484 		gtk_combo_box_set_active(combo_playlist_apply, APPLY_TO_SELECTED);
485 	else
486 		gtk_combo_box_set_active(combo_playlist_apply, APPLY_TO_ALL);
487 }
488 
489 
490 /*** public functions *******************************************************/
491 
pt_init(GladeXML * xml)492 void pt_init(GladeXML *xml)
493 {
494 	GtkStyle *style;
495 	GtkWidget *w;
496 
497 	/*
498 	 * get the widgets from glade
499 	 */
500 
501 	b_playlist_go = GTK_BUTTON(glade_xml_get_widget(xml, "b_playlist_go"));
502 	combo_playlist_apply = GTK_COMBO_BOX(glade_xml_get_widget(xml, "combo_playlist_apply"));
503 
504 	rb_create_dir = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_create_dir"));
505 	rb_create_toplevel = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_create_toplevel"));
506 	rb_create_both = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_create_both"));
507 
508 	rb_name_dir = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_name_dir"));
509 	rb_name_set = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_name_set"));
510 	ent_pl_name = GTK_ENTRY(glade_xml_get_widget(xml, "ent_pl_name"));
511 	ent_pl_extension = GTK_ENTRY(glade_xml_get_widget(xml, "ent_pl_extension"));
512 
513 	rb_sort_name = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_sort_name"));
514 	rb_sort_field = GTK_TOGGLE_BUTTON(glade_xml_get_widget(xml, "rb_sort_field"));
515 	cb_sort_across_dirs = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_sort_across_dirs"));
516 	combo_field = GTK_COMBO_BOX(glade_xml_get_widget(xml, "combo_field"));
517 
518 	/* initialize some widgets' state */
519 	gtk_combo_box_set_active(combo_playlist_apply, APPLY_TO_ALL);
520 
521 	/* connect signals */
522 	g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(glade_xml_get_widget(xml, "tv_files"))),
523 			 "changed", G_CALLBACK(cb_file_selection_changed), NULL);
524 
525 
526 	/*
527 	 * set the title colors
528 	 */
529 
530 	w = glade_xml_get_widget(xml, "lab_playlist_title");
531 	gtk_widget_ensure_style(w);
532 	style = gtk_widget_get_style(w);
533 
534 	gtk_widget_modify_fg(w, GTK_STATE_NORMAL, &style->text[GTK_STATE_SELECTED]);
535 
536 	w = glade_xml_get_widget(xml, "box_playlist_title");
537 	gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &style->base[GTK_STATE_SELECTED]);
538 
539 
540 	/*
541 	 * get the preference values, or set them to defaults
542 	 */
543 
544 	/* create_in */
545 	create_in = pref_get_ref("pt:create_in");
546 	if (!create_in) {
547 		long def = 0;
548 		create_in = pref_set("pt:create_in", PREF_INT, &def);
549 	}
550 
551 	/* name_dir */
552 	name_dir = pref_get_ref("pt:name_dir");
553 	if (!name_dir) {
554 		gboolean def = TRUE;
555 		name_dir = pref_set("pt:name_dir", PREF_BOOLEAN, &def);
556 	}
557 
558 	/* name */
559 	name = pref_get_ref("pt:name");
560 	if (!name) {
561 		char *def = "playlist";
562 		name = pref_set("pt:name", PREF_STRING, def);
563 	}
564 
565 	/* extension */
566 	extension = pref_get_ref("pt:extension");
567 	if (!extension) {
568 		char *def = "m3u";
569 		extension = pref_set("pt:extension", PREF_STRING, def);
570 	}
571 
572 	/* sort_by_name */
573 	sort_by_name = pref_get_ref("pt:sort_by_name");
574 	if (!sort_by_name) {
575 		gboolean def = TRUE;
576 		sort_by_name = pref_set("pt:sort_by_name", PREF_BOOLEAN, &def);
577 	}
578 
579 	/* sort_field */
580 	sort_field = pref_get_ref("pt:sort_field");
581 	if (!sort_field) {
582 		long def = 0;
583 		sort_field = pref_set("pt:sort_field", PREF_INT, &def);
584 	}
585 
586 	/* sort_across_dirs */
587 	sort_across_dirs = pref_get_ref("pt:sort_across_dirs");
588 	if (!sort_across_dirs) {
589 		gboolean def = FALSE;
590 		sort_across_dirs = pref_set("pt:sort_across_dirs", PREF_BOOLEAN, &def);
591 	}
592 
593 
594 	from_prefs();
595 }
596 
597