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