1 /*
2    Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera
3    Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org>
4 
5    The Gnome Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9 
10    The Gnome Library 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 GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with the Gnome Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301  USA.
19 
20    Author: Bastien Nocera <hadess@hadess.net>
21  */
22 
23 #include "config.h"
24 
25 #ifndef TOTEM_PL_PARSER_MINI
26 #include <string.h>
27 #include <glib.h>
28 #include <glib/gi18n-lib.h>
29 
30 #include "totem-pl-parser.h"
31 #endif /* !TOTEM_PL_PARSER_MINI */
32 
33 #include "totem-pl-parser-mini.h"
34 #include "totem-pl-parser-pls.h"
35 #include "totem-pl-parser-private.h"
36 
37 #ifndef TOTEM_PL_PARSER_MINI
38 gboolean
totem_pl_parser_save_pls(TotemPlParser * parser,TotemPlPlaylist * playlist,GFile * output,const gchar * title,GCancellable * cancellable,GError ** error)39 totem_pl_parser_save_pls (TotemPlParser    *parser,
40                           TotemPlPlaylist  *playlist,
41                           GFile            *output,
42                           const gchar      *title,
43                           GCancellable     *cancellable,
44                           GError          **error)
45 {
46         TotemPlPlaylistIter iter;
47 	GFileOutputStream *stream;
48 	int num_entries, i;
49 	gboolean valid, success;
50 	char *buf;
51 
52 	num_entries = totem_pl_parser_num_entries (parser, playlist);
53 
54 	stream = g_file_replace (output, NULL, FALSE, G_FILE_CREATE_NONE, cancellable, error);
55 	if (stream == NULL)
56 		return FALSE;
57 
58 	buf = g_strdup ("[playlist]\n");
59 	success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
60 	g_free (buf);
61 	if (success == FALSE)
62 		return FALSE;
63 
64 	if (title != NULL) {
65 		buf = g_strdup_printf ("X-GNOME-Title=%s\n", title);
66 		success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
67 		g_free (buf);
68 		if (success == FALSE)
69 			return FALSE;
70 	}
71 
72 	buf = g_strdup_printf ("NumberOfEntries=%d\n", num_entries);
73 	success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
74 	g_free (buf);
75 	if (success == FALSE)
76 		return FALSE;
77 
78         valid = totem_pl_playlist_iter_first (playlist, &iter);
79         i = 0;
80 
81         while (valid) {
82                 gchar *uri, *entry_title, *relative;
83                 GFile *file;
84 
85                 totem_pl_playlist_get (playlist, &iter,
86                                        TOTEM_PL_PARSER_FIELD_URI, &uri,
87                                        TOTEM_PL_PARSER_FIELD_TITLE, &entry_title,
88                                        NULL);
89 
90                 valid = totem_pl_playlist_iter_next (playlist, &iter);
91 
92                 if (!uri) {
93                         g_free (entry_title);
94                         continue;
95                 }
96 
97                 file = g_file_new_for_uri (uri);
98 
99                 if (totem_pl_parser_scheme_is_ignored (parser, file)) {
100                         g_object_unref (file);
101                         g_free (uri);
102                         g_free (entry_title);
103                         continue;
104                 }
105 
106                 g_object_unref (file);
107                 i++;
108 
109                 relative = totem_pl_parser_relative (output, uri);
110                 buf = g_strdup_printf ("File%d=%s\n", i, relative ? relative : uri);
111                 g_free (relative);
112                 g_free (uri);
113 
114                 success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
115                 g_free (buf);
116 
117                 if (success == FALSE) {
118                         g_free (entry_title);
119                         return FALSE;
120                 }
121 
122                 if (!entry_title) {
123                         continue;
124                 }
125 
126                 buf = g_strdup_printf ("Title%d=%s\n", i, entry_title);
127                 success = totem_pl_parser_write_string (G_OUTPUT_STREAM (stream), buf, cancellable, error);
128                 g_free (buf);
129                 g_free (entry_title);
130 
131                 if (success == FALSE) {
132                         return FALSE;
133                 }
134         }
135 
136 	g_object_unref (stream);
137 	return TRUE;
138 }
139 
140 static char *
ensure_utf8_valid(char * input)141 ensure_utf8_valid (char *input)
142 {
143 	char *utf8_valid;
144 
145 	utf8_valid = g_strdup (input);
146 
147 	if (!g_utf8_validate (utf8_valid, -1, NULL)) {
148 		gint i;
149 
150 		for (i = 0; i < g_utf8_strlen (utf8_valid, -1); i++) {
151 			gunichar c;
152 			c = g_utf8_get_char_validated (&utf8_valid[i], -1);
153 			if (c > 127) {
154 				utf8_valid[i] = '?';
155 			}
156 		}
157 	}
158 	return utf8_valid;
159 }
160 
161 TotemPlParserResult
totem_pl_parser_add_pls_with_contents(TotemPlParser * parser,GFile * file,GFile * _base_file,const char * contents,TotemPlParseData * parse_data)162 totem_pl_parser_add_pls_with_contents (TotemPlParser *parser,
163 				       GFile *file,
164 				       GFile *_base_file,
165 				       const char *contents,
166 				       TotemPlParseData *parse_data)
167 {
168 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED;
169 	GFile *base_file;
170 	char **lines;
171 	guint i, num_entries;
172 	char *playlist_title;
173 	gboolean fallback;
174 	GHashTable *entries;
175 	guint found_entries;
176 	char *uri;
177 
178 	lines = g_strsplit_set (contents, "\r\n", 0);
179 
180 	/* [playlist] */
181 	i = 0;
182 	num_entries = 0;
183 
184 	/* Ignore empty lines */
185 	while (lines[i] != NULL && totem_pl_parser_line_is_empty (lines[i]) != FALSE)
186 		i++;
187 
188 	if (lines[i] == NULL
189 	    || g_ascii_strncasecmp (lines[i], "[playlist]",
190 				    (gsize)strlen ("[playlist]")) != 0) {
191 		g_strfreev (lines);
192 		return retval;
193 	}
194 
195 	playlist_title = totem_pl_parser_read_ini_line_string (lines,
196 							       "X-GNOME-Title");
197 	totem_pl_parser_add_uri (parser,
198 				 TOTEM_PL_PARSER_FIELD_IS_PLAYLIST, TRUE,
199 				 TOTEM_PL_PARSER_FIELD_FILE, file,
200 				 TOTEM_PL_PARSER_FIELD_TITLE, playlist_title,
201 				 TOTEM_PL_PARSER_FIELD_CONTENT_TYPE, "audio/x-scpls",
202 				 NULL);
203 	g_free (playlist_title);
204 
205 	/* Load the file in hash table to speed up the later processing */
206 	entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
207 	for (i = 0; lines[i] != NULL; i++) {
208 		char **bits;
209 		char *value;
210 
211 		if (totem_pl_parser_line_is_empty (lines[i]))
212 			continue;
213 
214 		if (lines[i][0] == '#' || lines[i][0] == '[')
215 			continue;
216 
217 		bits = g_strsplit (lines[i], "=", 2);
218 		if (bits[0] == NULL || bits [1] == NULL) {
219 			g_strfreev (bits);
220 			continue;
221 		}
222 
223 		if (g_ascii_strncasecmp (g_strchug (bits[0]), "file", strlen ("file")) == 0)
224 			num_entries++;
225 
226 		value = g_strdup (bits[1]);
227 
228 		g_hash_table_insert (entries,
229 				     g_ascii_strdown (bits[0], strlen (bits[0])),
230 				     value);
231 		g_strfreev (bits);
232 	}
233 	g_strfreev (lines);
234 
235 	/* Base? */
236 	if (_base_file == NULL)
237 		base_file = g_file_get_parent (file);
238 	else
239 		base_file = g_object_ref (_base_file);
240 
241 	retval = TOTEM_PL_PARSER_RESULT_SUCCESS;
242 
243 	found_entries = 0;
244 	for (i = 1; found_entries < num_entries; i++) {
245 		char *file_str, *title, *genre, *length;
246 		char *file_key, *title_key, *genre_key, *length_key;
247 		gint64 length_num;
248 
249 		file_key = g_strdup_printf ("file%d", i);
250 		title_key = g_strdup_printf ("title%d", i);
251 		length_key = g_strdup_printf ("length%d", i);
252 		length_num = 0;
253 		/* Genre is our own little extension */
254 		genre_key = g_strdup_printf ("genre%d", i);
255 
256 		file_str = g_hash_table_lookup (entries, file_key);
257 		title = g_hash_table_lookup (entries, title_key);
258 		genre = g_hash_table_lookup (entries, genre_key);
259 		length = g_hash_table_lookup (entries, length_key);
260 
261 		g_free (file_key);
262 		g_free (title_key);
263 		g_free (genre_key);
264 		g_free (length_key);
265 
266 		if (file_str == NULL)
267 			continue;
268 		found_entries++;
269 
270 		fallback = parse_data->fallback;
271 		if (parse_data->recurse)
272 			parse_data->fallback = FALSE;
273 
274 		/* Get the length, if it's negative, that means that we have a stream
275 		 * and should push the entry straight away */
276 		if (length != NULL)
277 			length_num = totem_pl_parser_parse_duration (length, totem_pl_parser_is_debugging_enabled (parser));
278 
279 		if (strstr (file_str, "://") != NULL || file_str[0] == G_DIR_SEPARATOR) {
280 			GFile *target;
281 
282 			target = g_file_new_for_commandline_arg (file_str);
283 			if (length_num < 0 || totem_pl_parser_parse_internal (parser, target, NULL, parse_data) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
284 				totem_pl_parser_add_uri (parser,
285 							 TOTEM_PL_PARSER_FIELD_URI, file_str,
286 							 TOTEM_PL_PARSER_FIELD_TITLE, title,
287 							 TOTEM_PL_PARSER_FIELD_GENRE, genre,
288 							 TOTEM_PL_PARSER_FIELD_DURATION, length,
289 							 TOTEM_PL_PARSER_FIELD_BASE_FILE, base_file, NULL);
290 			}
291 			g_object_unref (target);
292 		} else {
293 			GFile *target;
294 			char *utf8_filename;
295 
296 			utf8_filename = ensure_utf8_valid (file_str);
297 			target = g_file_get_child_for_display_name (base_file, utf8_filename, NULL);
298 			g_free (utf8_filename);
299 
300 			if (length_num < 0 || totem_pl_parser_parse_internal (parser, target, base_file, parse_data) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
301 				totem_pl_parser_add_uri (parser,
302 							 TOTEM_PL_PARSER_FIELD_FILE, target,
303 							 TOTEM_PL_PARSER_FIELD_TITLE, title,
304 							 TOTEM_PL_PARSER_FIELD_GENRE, genre,
305 							 TOTEM_PL_PARSER_FIELD_DURATION, length,
306 							 TOTEM_PL_PARSER_FIELD_BASE_FILE, base_file, NULL);
307 			}
308 
309 			g_object_unref (target);
310 		}
311 
312 		parse_data->fallback = fallback;
313 	}
314 
315 	uri = g_file_get_uri (file);
316 	totem_pl_parser_playlist_end (parser, uri);
317 	g_free (uri);
318 
319 	g_object_unref (base_file);
320         g_hash_table_destroy (entries);
321 
322 	return retval;
323 }
324 
325 TotemPlParserResult
totem_pl_parser_add_pls(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)326 totem_pl_parser_add_pls (TotemPlParser *parser,
327 			 GFile *file,
328 			 GFile *base_file,
329 			 TotemPlParseData *parse_data,
330 			 gpointer data)
331 {
332 	TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED;
333 	char *contents;
334 	gsize size;
335 
336 	if (g_file_load_contents (file, NULL, &contents, &size, NULL, NULL) == FALSE)
337 		return TOTEM_PL_PARSER_RESULT_ERROR;
338 
339 	if (size == 0) {
340 		g_free (contents);
341 		return TOTEM_PL_PARSER_RESULT_SUCCESS;
342 	}
343 
344 	retval = totem_pl_parser_add_pls_with_contents (parser, file, base_file, contents, parse_data);
345 	g_free (contents);
346 
347 	return retval;
348 }
349 
350 #endif /* !TOTEM_PL_PARSER_MINI */
351 
352