1 	/*
2 	|  Copyright (C) 2007 P.G. Richardson <phantom_sf at users.sourceforge.net>
3 	|  Part of the gtkpod project.
4 	|
5 	|  URL: http://www.gtkpod.org/
6 	|  URL: http://gtkpod.sourceforge.net/
7 	|
8 	|  This program is free software; you can redistribute it and/or modify
9 	|  it under the terms of the GNU General Public License as published by
10 	|  the Free Software Foundation; either version 2 of the License, or
11 	|  (at your option) any later version.
12 	|
13 	|  This program is distributed in the hope that it will be useful,
14 	|  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	|  GNU General Public License for more details.
17 	|
18 	|  You should have received a copy of the GNU General Public License
19 	|  along with this program; if not, write to the Free Software
20 	|  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 	|
22 	|  iTunes and iPod are trademarks of Apple
23 	|
24 	|  This product is not supported/written/published by Apple!
25 	|
26 	|  $Id$
27 	*/
28 
29 #ifdef HAVE_CONFIG_H
30 	#include <config.h>
31 #endif
32 
33 #include "fetchcover.h"
34 #include "display_coverart.h"
35 #include <glib/gprintf.h>
36 #include <glib/gstdio.h>
37 #include <gdk/gdkkeysyms.h>
38 
39 #undef FETCHCOVER_DEBUG
40 
41 #define IMGSCALE 256
42 
43 /* Display a dialog explaining the options if a file with the proposed name already exists */
44 static gchar *display_file_exist_dialog (Fetch_Cover *fetch_cover);
45 static gchar *fetchcover_check_file_exists (Fetch_Cover *fetch_cover);
46 
47 #ifdef HAVE_CURL
48 
49 	#include <curl/curl.h>
50 
51 	/* Declarations */
52 	static void *safe_realloc(void *ptr, size_t size);
53 	static size_t curl_write_fetchcover_func(void *ptr, size_t itemsize, size_t numitems, void *data);
54 
55 	struct chunk
56 	{
57 		gchar *memory;
58 		size_t size;
59 	};
60 
61 	/* Data structure for use with curl */
62 	struct chunk fetchcover_curl_data;
63 
64 	/****
65 	 * safe_realloc:
66 	 *
67 	 * @void: ptr
68 	 * @size_t: size
69 	 *
70 	 * Memory allocation function
71 	 **/
safe_realloc(void * ptr,size_t size)72 	static void *safe_realloc(void *ptr, size_t size)
73 	{
74 		if (ptr)
75 			return realloc(ptr, size);
76 		else
77 			return malloc(size);
78 	}
79 
80 	/****
81 	 *
82 	 * curl_write_fetchcover_func:
83 	 *
84 	 * @void: *ptr
85 	 * @size_t: itemsize
86 	 * @size_t:numitems
87 	 * @void: *data
88 	 *
89 	 * Curl writing function
90 	 *
91 	 * @Return size_t
92 	 **/
curl_write_fetchcover_func(void * ptr,size_t itemsize,size_t numitems,void * data)93 	static size_t curl_write_fetchcover_func(void *ptr, size_t itemsize, size_t numitems, void *data)
94 	{
95 		size_t size = itemsize * numitems;
96 		struct chunk *mem = (struct chunk*)data;
97 		mem->memory = (gchar*)safe_realloc(mem->memory, mem->size + size + 1);
98 
99 		if (mem->memory)
100 		{
101 			memcpy(&(mem->memory[mem->size]), ptr, size);
102 			mem->size += size;
103 			mem->memory[mem->size] = 0;
104 		}
105 		return size;
106 	}
107 #endif
108 
109 	/*****
110 	 * fetchcover_new:
111 	 *
112 	 * @GString: url
113 	 * @GList: trks
114 	 *
115 	 * Initialise a new fetch cover object for use with the fetchcover functions
116 	 **/
fetchcover_new(gchar * url_path,GList * trks)117 	Fetch_Cover *fetchcover_new (gchar *url_path, GList *trks)
118 	{
119 		/*Create a fetchcover object */
120 		Fetch_Cover * fcover;
121 
122 		fcover = g_new0(Fetch_Cover, 1);
123 		fcover->url = g_string_new ((gchar*) url_path);
124 		fcover->image = NULL;
125 		fcover->tracks = trks;
126 		fcover->err_msg = NULL;
127 		fcover->parent_window = NULL;
128 
129 		return fcover;
130 	}
131 
132 #ifdef HAVE_CURL
133 	/*****
134 	 * net_retrieve_image:
135 	 *
136 	 * @GString: url
137 	 *
138 	 * Use the url acquired from the net search to fetch the image,
139 	 * save it to a file inside the track's parent directory then display
140 	 * it as a pixbuf
141 	 **/
fetchcover_net_retrieve_image(Fetch_Cover * fetch_cover)142 	gboolean fetchcover_net_retrieve_image (Fetch_Cover *fetch_cover)
143 	{
144 		g_return_val_if_fail (fetch_cover, FALSE);
145 
146 		if (! g_str_has_suffix(fetch_cover->url->str, ".jpg") && ! g_str_has_suffix(fetch_cover->url->str, ".JPG"))
147 		{
148 			fetch_cover->err_msg = g_strdup("Only jpg images are currently supported at this time\n");
149 			return FALSE;
150 		}
151 
152 		gchar *path = NULL;
153 		FILE *tmpf = NULL;
154 
155 		fetchcover_curl_data.size = 0;
156 		fetchcover_curl_data.memory = NULL;
157 
158 		/* Use curl to retrieve the data from the net */
159 		CURL *curl;
160 		curl_global_init(CURL_GLOBAL_ALL);
161 		curl = curl_easy_init();
162 		curl_easy_setopt(curl, CURLOPT_URL, fetch_cover->url->str);
163 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_fetchcover_func);
164 		curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fetchcover_curl_data);
165 		curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
166 		curl_easy_perform(curl);
167 		curl_easy_cleanup(curl);
168 
169 		if (fetchcover_curl_data.memory == NULL)
170 		{
171 			fetch_cover->err_msg = g_strdup("fetchcover curl data memory is null so failed to download anything!\n");
172 			return FALSE;
173 		}
174 
175 		/* Check that the page returned is a valid web page */
176 		if (strstr(fetchcover_curl_data.memory, "<html>") != NULL)
177 		{
178 			fetch_cover->err_msg = g_strdup("fetchcover memory contains <html> tag so not a valid jpg image\n");
179 			return FALSE;
180 		}
181 
182 		if (! fetchcover_select_filename (fetch_cover))
183 			return FALSE;
184 
185 		path = g_build_filename(fetch_cover->dir, fetch_cover->filename, NULL);
186 #if DEBUG
187 		printf ("path of download file is %s\n", path);
188 #endif
189 		if ((tmpf = fopen(path, "wb")) == NULL)
190 		{
191 			if (fetchcover_curl_data.memory)
192 			{
193 				g_free(fetchcover_curl_data.memory);
194 				fetchcover_curl_data.memory = NULL;
195 				fetchcover_curl_data.size = 0;
196 			}
197 			g_free (path);
198 			fetch_cover->err_msg = g_strdup ("Failed to create a file with the filename\n");
199 			return FALSE;
200 		}
201 
202 		if (fwrite(fetchcover_curl_data.memory, fetchcover_curl_data.size, 1, tmpf) != 1)
203 		{
204 			if (fetchcover_curl_data.memory)
205 			{
206 				g_free(fetchcover_curl_data.memory);
207 				fetchcover_curl_data.memory = NULL;
208 				fetchcover_curl_data.size = 0;
209 			}
210 			fclose(tmpf);
211 			g_free (path);
212 			fetch_cover->err_msg = g_strdup("fetchcover failed to write the data to the new file\n");
213 			return FALSE;
214 		}
215 
216 		fclose(tmpf);
217 
218 		/* Check the file is a valid pixbuf type file */
219 		GdkPixbufFormat *fileformat= NULL;
220 		fileformat = gdk_pixbuf_get_file_info (path, NULL, NULL);
221 		if (fileformat == NULL)
222 		{
223 			fetch_cover->err_msg = g_strdup("fetchcover downloaded file is not a valid image file\n");
224 			return FALSE;
225 		}
226 
227 		GError *error = NULL;
228 		fetch_cover->image = gdk_pixbuf_new_from_file(path, &error);
229 		if (error != NULL)
230 		{
231 			g_error_free (error);
232 			if (fetchcover_curl_data.memory)
233 			{
234 				g_free(fetchcover_curl_data.memory);
235 				fetchcover_curl_data.memory = NULL;
236 				fetchcover_curl_data.size = 0;
237 			}
238 			g_free(path);
239 			fetch_cover->err_msg = g_strconcat ("fetchcover error occurred while creating a pixbuf from the file\n", error->message, NULL);
240 			return FALSE;
241 		}
242 
243 		if (fetchcover_curl_data.memory)
244 			g_free(fetchcover_curl_data.memory);
245 
246 		fetchcover_curl_data.memory = NULL;
247 		fetchcover_curl_data.size = 0;
248 		g_free(path);
249 		return TRUE;
250 	}
251 #endif
252 
fetchcover_select_filename(Fetch_Cover * fetch_cover)253 	gboolean fetchcover_select_filename (Fetch_Cover *fetch_cover)
254 	{
255 		GList *tracks = fetch_cover->tracks;
256 
257 		if (tracks == NULL || g_list_length (tracks) <= 0)
258 		{
259 			fetch_cover->err_msg = g_strdup("fetchcover object's tracks list either NULL or no tracks were selected\n");
260 			return FALSE;
261 		}
262 
263 		Track *track = g_list_nth_data (tracks, 0);
264 		ExtraTrackData *etd = track->userdata;
265 
266 		fetch_cover->dir = g_path_get_dirname(etd->pc_path_utf8);
267 		gchar *template = prefs_get_string("coverart_template");
268 		gchar **template_items = g_strsplit(template, ";", 0);
269 
270 		gint i;
271 		for (i = 0; fetch_cover->filename == NULL && i < g_strv_length (template_items); ++i)
272 		{
273 			fetch_cover->filename = get_string_from_template(track, template_items[i], FALSE, FALSE);
274 			if (strlen(fetch_cover->filename) == 0)
275 				fetch_cover->filename = NULL;
276 		}
277 
278 		g_strfreev(template_items);
279 		g_free(template);
280 
281 		/* Check filename still equals null then take a default stance
282 		 * to ensure the file has a name. Default stance applies if the
283 		 * extra track data has been left as NULL
284 		 */
285 		if (fetch_cover->filename == NULL)
286 			fetch_cover->filename = "folder.jpg";
287 		else
288 		{
289 			if (! g_str_has_suffix(fetch_cover->filename, ".jpg"))
290 			{
291 				gchar *oldname;
292 				oldname = fetch_cover->filename;
293 				fetch_cover->filename = g_strconcat(oldname, ".jpg", NULL);
294 				g_free (oldname);
295 			}
296 		}
297 
298 		if (fetchcover_check_file_exists (fetch_cover) == NULL)
299 		{
300 			fetch_cover->err_msg = g_strdup("operation cancelled\n");
301 			return FALSE;
302 		}
303 
304 		return TRUE;
305 	}
306 
307 	/*****
308 	 * fetchcover_check_file_exists:
309 	 *
310 	 * @Fetch_Cover
311 	 *
312 	 * Save the displayed cover.
313 	 * Set thumbnails, update details window.
314 	 * Called on response to the clicking of the save button in the dialog
315 	 *
316 	 * Returns:
317 	 * Filename of chosen cover image file
318 	 ***/
fetchcover_check_file_exists(Fetch_Cover * fetch_cover)319 	static gchar *fetchcover_check_file_exists (Fetch_Cover *fetch_cover)
320 	{
321 		gchar *newname = NULL;
322 		/* The default cover image will have both dir and filename set
323 		 * to null because no need to save because it is already saved (!!)
324 		 * Thus, this whole process is avoided. Added bonus that pressing
325 		 * save by accident if, for instance, no images are found means the
326 		 * whole thing safely completes
327 		 */
328 		if (fetch_cover->dir && fetch_cover->filename)
329 		{
330 			/* path is valid so first move the file to be the folder.jpg or
331 			 * whatever is the preferred preference
332 			 */
333 
334 			/* Assign the full path name ready to rename the file */
335 			newname = g_build_filename(fetch_cover->dir, fetch_cover->filename, NULL);
336 
337 			if (g_file_test (newname, G_FILE_TEST_EXISTS))
338 			{
339 				newname = display_file_exist_dialog (fetch_cover);
340 			}
341 		}
342 		return newname;
343 	}
344 
display_file_exist_dialog(Fetch_Cover * fetch_cover)345 	static gchar *display_file_exist_dialog (Fetch_Cover *fetch_cover)
346 	{
347 		gchar *filepath;
348 		gint result;
349 		gchar **splitarr = NULL;
350 		gchar *basename = NULL;
351 		gint i;
352 		gchar *message;
353 
354 		if (fetch_cover->parent_window == NULL)
355 			fetch_cover->parent_window = GTK_WINDOW(gtkpod_window);
356 
357 		filepath = g_build_filename(fetch_cover->dir, fetch_cover->filename, NULL);
358 
359 		message = g_strdup_printf (_("The picture file %s already exists.\n" \
360 				"This may be associated with other music files in the directory.\n\n" \
361 				"Do you want to overwrite the existing file, possibly associating\n" \
362 				"other music files in the same directory with this cover art file,\n" \
363 				"to save the file with a unique file name, or to abort the fetchcover operation?"),
364 								   filepath);
365 
366 		result = gtkpod_confirmation_hig (fetch_cover->parent_window,
367 									      GTK_MESSAGE_WARNING,
368 									      _("Cover art file already exists"),
369 									      message,
370 									      _("Overwrite"),
371 									      _("Rename"),
372 									      _("Abort"),
373 									      NULL);
374 		g_free (message);
375 
376 		switch (result)
377 		{
378 			case GTK_RESPONSE_APPLY:
379 				/* Abort has been clicked so no save */
380 				return NULL;
381 			case GTK_RESPONSE_OK:
382 				/*** Overwrite clicked so overwrite the file is okay. Leave final_filename intact
383 				 * and remove the original
384 				 **/
385 				g_remove (filepath);
386 				return filepath;
387 			case GTK_RESPONSE_CANCEL:
388 				/* User doesn't want to overwrite anything so need to do some work on filename */
389 				/* Remove the suffix from the end of the filename */
390 				splitarr = g_strsplit (fetch_cover->filename, ".", 0);
391 				basename = splitarr[0];
392 
393 				gchar *newfilename = g_strdup (fetch_cover->filename);;
394 
395 				for (i = 1; g_file_test (filepath, G_FILE_TEST_EXISTS); ++i)
396 				{
397 					g_free (newfilename);
398 					gchar *intext= NULL;
399 					intext = g_strdup_printf ("%d.jpg", i);
400 					newfilename = g_strconcat (basename, intext, NULL);
401 
402 					g_free (filepath);
403 					g_free (intext);
404 
405 					filepath = g_build_filename(fetch_cover->dir, newfilename, NULL);
406 				}
407 
408 				/* Should have found a filename that really doesn't exist so this needs to be returned */
409 				g_free (fetch_cover->filename);
410 				fetch_cover->filename = g_strdup (newfilename);
411 				g_free (newfilename);
412 				newfilename = NULL;
413 				basename = NULL;
414 				g_strfreev(splitarr);
415 
416 				return filepath;
417 			default:
418 				return NULL;
419 		}
420 	}
421 
422 /****
423  * free_fetchcover:
424  *
425  * @Fetch_Cover: fcover
426  *
427  * Free the elements of the passed in Fetch_Cover structure
428  */
free_fetchcover(Fetch_Cover * fcover)429 void free_fetchcover (Fetch_Cover *fcover)
430 {
431 	if (! fcover)
432 		return;
433 
434 	if (fcover->url)
435 		g_string_free (fcover->url, TRUE);
436 
437 	if (fcover->image)
438 		g_object_unref (fcover->image);
439 
440 	if (fcover->dir)
441 		g_free (fcover->dir);
442 
443 	if (fcover->filename)
444 		g_free (fcover->filename);
445 
446 	if (fcover->err_msg)
447 		g_free (fcover->err_msg);
448 
449 	if (fcover->parent_window)
450 		fcover->parent_window = NULL;
451 
452 	g_free (fcover);
453 }
454