1 /*
2  * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3  * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program 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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <rawstudio.h>
21 #include <glib.h>
22 #include <libxml/encoding.h>
23 #include <libxml/xmlwriter.h>
24 #include "application.h"
25 #include "rs-cache.h"
26 #include "rs-photo.h"
27 
28 /* This will be written to XML files for making backward compatibility easier to implement */
29 #define CACHEVERSION 5
30 
31 gchar *
rs_cache_get_name(const gchar * src)32 rs_cache_get_name(const gchar *src)
33 {
34 	gchar *ret=NULL;
35 	gchar *dotdir, *filename;
36 	GString *out;
37 	dotdir = rs_dotdir_get(src);
38 	filename = g_path_get_basename(src);
39 	if (dotdir)
40 	{
41 		out = g_string_new(dotdir);
42 		out = g_string_append(out, G_DIR_SEPARATOR_S);
43 		out = g_string_append(out, filename);
44 		out = g_string_append(out, ".cache.xml");
45 		ret = out->str;
46 		g_string_free(out, FALSE);
47 		g_free(dotdir);
48 	}
49 	g_free(filename);
50 	return(ret);
51 }
52 
53 void
rs_cache_save(RS_PHOTO * photo,const RSSettingsMask mask)54 rs_cache_save(RS_PHOTO *photo, const RSSettingsMask mask)
55 {
56 	gint id;
57 	xmlTextWriterPtr writer;
58 	gchar *cachename;
59 
60 	if (!photo->filename) return;
61 
62 	cachename = rs_cache_get_name(photo->filename);
63 	if (!cachename) return;
64 	writer = xmlNewTextWriterFilename(cachename, 0); /* fixme, check for errors */
65 	xmlTextWriterSetIndent(writer, 1);
66 	xmlTextWriterStartDocument(writer, NULL, "ISO-8859-1", NULL);
67 	xmlTextWriterStartElement(writer, BAD_CAST "rawstudio-cache");
68 	xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "version", "%d", CACHEVERSION);
69 	xmlTextWriterWriteFormatElement(writer, BAD_CAST "priority", "%d",
70 		photo->priority);
71 	if (photo->exported)
72 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "exported", "yes");
73 	xmlTextWriterWriteFormatElement(writer, BAD_CAST "orientation", "%d",
74 		photo->orientation);
75 	xmlTextWriterWriteFormatElement(writer, BAD_CAST "angle", "%f",
76 		photo->angle);
77 
78 	RSDcpFile *dcp = rs_photo_get_dcp_profile(photo);
79 	if (RS_IS_DCP_FILE(dcp))
80 	{
81 		const gchar *dcp_id = rs_dcp_get_id(RS_DCP_FILE(dcp));
82 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "dcp-profile", "%s",
83 			dcp_id);
84 	}
85 
86 	RSIccProfile *icc = rs_photo_get_icc_profile(photo);
87 	if (RS_IS_ICC_PROFILE(icc))
88 	{
89 		const gchar *icc_filename;
90 		g_object_get(icc, "filename", &icc_filename, NULL);
91 		if (icc_filename)
92 		{
93 			gchar *basename = g_path_get_basename(icc_filename);
94 			xmlTextWriterWriteFormatElement(writer, BAD_CAST "icc-profile", "%s",
95 			basename);
96 			g_free(basename);
97 		}
98 	}
99 
100 	if (photo->crop)
101 	{
102 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "crop", "%d %d %d %d",
103 			photo->crop->x1, photo->crop->y1,
104 			photo->crop->x2, photo->crop->y2);
105 	}
106 	for(id=0;id<3&&mask>0;id++)
107 	{
108 		xmlTextWriterStartElement(writer, BAD_CAST "settings");
109 		xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "id", "%d", id);
110 		rs_cache_save_settings(photo->settings[id], mask, writer);
111 		xmlTextWriterEndElement(writer);
112 	}
113 	xmlTextWriterEndDocument(writer);
114 	xmlFreeTextWriter(writer);
115 	g_free(cachename);
116 	return;
117 }
118 
119 void
rs_cache_save_settings(RSSettings * rss,const RSSettingsMask mask,xmlTextWriterPtr writer)120 rs_cache_save_settings(RSSettings *rss, const RSSettingsMask mask, xmlTextWriterPtr writer)
121 {
122 	if (mask & MASK_EXPOSURE)
123 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "exposure", "%f", rss->exposure);
124 	if (mask & MASK_SATURATION)
125 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "saturation", "%f", rss->saturation);
126 	if (mask & MASK_HUE)
127 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "hue", "%f", rss->hue);
128 	if (mask & MASK_CONTRAST)
129 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "contrast", "%f", rss->contrast);
130 	if (mask & MASK_WARMTH)
131 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "warmth", "%f", rss->dcp_temp);
132 	if (mask & MASK_TINT)
133 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "tint", "%f", rss->dcp_tint);
134 	if (mask & MASK_WB && rss->wb_ascii)
135 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "wb_ascii", "%s", rss->wb_ascii);
136 	if (mask & MASK_SHARPEN)
137 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "sharpen", "%f", rss->sharpen);
138 	if (mask & MASK_DENOISE_LUMA)
139 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "denoise_luma", "%f", rss->denoise_luma);
140 	if (mask & MASK_DENOISE_CHROMA)
141 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "denoise_chroma", "%f", rss->denoise_chroma);
142 	if (mask & MASK_CHANNELMIXER)
143 	{
144 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "channelmixer_red", "%f", rss->channelmixer_red);
145 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "channelmixer_green", "%f", rss->channelmixer_green);
146 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "channelmixer_blue", "%f", rss->channelmixer_blue);
147 	}
148 	if (mask & MASK_TCA_KR)
149 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "tca_kr", "%f", rss->tca_kr);
150 	if (mask & MASK_TCA_KB)
151 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "tca_kb", "%f", rss->tca_kb);
152 	if (mask & MASK_VIGNETTING)
153 		xmlTextWriterWriteFormatElement(writer, BAD_CAST "vignetting", "%f", rss->vignetting);
154 	if (mask & MASK_CURVE && rss->curve_nknots > 0)
155 	{
156 		gint i;
157 		xmlTextWriterStartElement(writer, BAD_CAST "curve");
158 		xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "num", "%d", rss->curve_nknots);
159 		for(i=0;i<rss->curve_nknots;i++)
160 			xmlTextWriterWriteFormatElement(writer, BAD_CAST "knot", "%f %f",
161 				rss->curve_knots[i*2+0],
162 				rss->curve_knots[i*2+1]);
163 		xmlTextWriterEndElement(writer);
164 	}
165 }
166 
167 guint
rs_cache_load_setting(RSSettings * rss,xmlDocPtr doc,xmlNodePtr cur,gint version)168 rs_cache_load_setting(RSSettings *rss, xmlDocPtr doc, xmlNodePtr cur, gint version)
169 {
170 	RSSettingsMask mask = 0;
171 	xmlChar *val;
172 	gfloat *target=NULL;
173 	xmlNodePtr curve = NULL;
174 	while(cur)
175 	{
176 		target = NULL;
177 		if ((!xmlStrcmp(cur->name, BAD_CAST "exposure")))
178 		{
179 			mask |= MASK_EXPOSURE;
180 			target = &rss->exposure;
181 		}
182 		else if ((!xmlStrcmp(cur->name, BAD_CAST "saturation")))
183 		{
184 			mask |= MASK_SATURATION;
185 			target = &rss->saturation;
186 		}
187 		else if ((!xmlStrcmp(cur->name, BAD_CAST "hue")))
188 		{
189 			mask |= MASK_HUE;
190 			target = &rss->hue;
191 		}
192 		else if ((!xmlStrcmp(cur->name, BAD_CAST "contrast")))
193 		{
194 			mask |= MASK_CONTRAST;
195 			target = &rss->contrast;
196 		}
197 		else if ((!xmlStrcmp(cur->name, BAD_CAST "warmth")))
198 		{
199 			if ( version <= 4)
200 			{
201 				mask |= MASK_WARMTH;
202 				target = &rss->warmth;
203 				rss->recalc_temp = TRUE;
204 			}
205 			else
206 			{
207 				mask |= MASK_WARMTH;
208 				target = &rss->dcp_temp;
209 			}
210 		}
211 		else if ((!xmlStrcmp(cur->name, BAD_CAST "tint")))
212 		{
213 			if ( version <= 4)
214 			{
215 				mask |= MASK_TINT;
216 				target = &rss->tint;
217 				rss->recalc_temp = TRUE;
218 			}
219 			else
220 			{
221 				mask |= MASK_TINT;
222 				target = &rss->dcp_tint;
223 			}
224 		}
225 		else if ((!xmlStrcmp(cur->name, BAD_CAST "wb_ascii")))
226 		{
227 			mask |= MASK_WB;
228 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
229 			rss->wb_ascii = g_strdup((gchar *) val);
230 			xmlFree(val);
231 		}
232 		else if ((!xmlStrcmp(cur->name, BAD_CAST "sharpen")))
233 		{
234 			mask |= MASK_SHARPEN;
235 			target = &rss->sharpen;
236 		}
237 		else if ((!xmlStrcmp(cur->name, BAD_CAST "denoise_luma")))
238 		{
239 			mask |= MASK_DENOISE_LUMA;
240 			target = &rss->denoise_luma;
241 		}
242 		else if ((!xmlStrcmp(cur->name, BAD_CAST "denoise_chroma")))
243 		{
244 			mask |= MASK_DENOISE_CHROMA;
245 			target = &rss->denoise_chroma;
246 		}
247 		else if ((!xmlStrcmp(cur->name, BAD_CAST "channelmixer_red")))
248 		{
249 			mask |= MASK_CHANNELMIXER_RED;
250 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
251 			rss->channelmixer_red =  rs_atof((gchar *) val);
252 			xmlFree(val);
253 
254 			if (version < 4)
255 				rss->channelmixer_red *= 3.0;
256 		}
257 		else if ((!xmlStrcmp(cur->name, BAD_CAST "channelmixer_green")))
258 		{
259 			mask |= MASK_CHANNELMIXER_GREEN;
260 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
261 			rss->channelmixer_green =  rs_atof((gchar *) val);
262 			xmlFree(val);
263 
264 			if (version < 4)
265 				rss->channelmixer_green *= 3.0;
266 		}
267 		else if ((!xmlStrcmp(cur->name, BAD_CAST "channelmixer_blue")))
268 		{
269 			mask |= MASK_CHANNELMIXER_BLUE;
270 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
271 			rss->channelmixer_blue =  rs_atof((gchar *) val);
272 			xmlFree(val);
273 
274 			if (version < 4)
275 				rss->channelmixer_blue *= 3.0;
276 		}
277 		else if ((!xmlStrcmp(cur->name, BAD_CAST "tca_kr")))
278 		{
279 			mask |= MASK_TCA_KR;
280 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
281 			rss->tca_kr =  rs_atof((gchar *) val);
282 			xmlFree(val);
283 		}
284 		else if ((!xmlStrcmp(cur->name, BAD_CAST "tca_kb")))
285 		{
286 			mask |= MASK_TCA_KB;
287 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
288 			rss->tca_kb =  rs_atof((gchar *) val);
289 			xmlFree(val);
290 		}
291 		else if ((!xmlStrcmp(cur->name, BAD_CAST "vignetting")))
292 		{
293 			mask |= MASK_VIGNETTING;
294 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
295 			rss->vignetting =  rs_atof((gchar *) val);
296 			xmlFree(val);
297 		}
298 		else if ((!xmlStrcmp(cur->name, BAD_CAST "curve")))
299 		{
300 			gchar **vals;
301 			gint num;
302 			gfloat x,y;
303 
304 			val = xmlGetProp(cur, BAD_CAST "num");
305 			if (val)
306 				num = atoi((gchar *) val);
307 			else
308 				num = 0;
309 
310 			rss->curve_knots = g_new(gfloat, 2*num);
311 			rss->curve_nknots = 0;
312 			curve = cur->xmlChildrenNode;
313 			while (curve && num)
314 			{
315 				if ((!xmlStrcmp(curve->name, BAD_CAST "knot")))
316 				{
317 					mask |= MASK_CURVE;
318 					val = xmlNodeListGetString(doc, curve->xmlChildrenNode, 1);
319 					vals = g_strsplit((gchar *)val, " ", 4);
320 					if (vals[0] && vals[1])
321 					{
322 						x = rs_atof(vals[0]);
323 						y = rs_atof(vals[1]);
324 						rss->curve_knots[rss->curve_nknots*2+0] = x;
325 						rss->curve_knots[rss->curve_nknots*2+1] = y;
326 						rss->curve_nknots++;
327 						num--;
328 					}
329 					g_strfreev(vals);
330 					xmlFree(val);
331 				}
332 				curve = curve->next;
333 			}
334 		}
335 
336 		if (target)
337 		{
338 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
339 			*target =  rs_atof((gchar *) val);
340 			xmlFree(val);
341 		}
342 		cur = cur->next;
343 	}
344 
345 	return mask;
346 }
347 
348 guint
rs_cache_load(RS_PHOTO * photo)349 rs_cache_load(RS_PHOTO *photo)
350 {
351 	RSSettingsMask mask = 0;
352 	xmlDocPtr doc;
353 	xmlNodePtr cur;
354 	xmlChar *val;
355 	gchar *cachename;
356 	gint id;
357 	gint version = 0;
358 	RSSettings *settings;
359 
360 	cachename = rs_cache_get_name(photo->filename);
361 	if (!cachename) return mask;
362 	if (!g_file_test(cachename, G_FILE_TEST_IS_REGULAR)) return FALSE;
363 	photo->exported = FALSE;
364 	doc = xmlParseFile(cachename);
365 	if(doc==NULL) return mask;
366 
367 	/* Return something if the file exists */
368 	mask = 0x80000000;
369 
370 	cur = xmlDocGetRootElement(doc);
371 
372 	if ((!xmlStrcmp(cur->name, BAD_CAST "rawstudio-cache")))
373 	{
374 		val = xmlGetProp(cur, BAD_CAST "version");
375 		if (val)
376 			version = atoi((gchar *) val);
377 	}
378 
379 	cur = cur->xmlChildrenNode;
380 	while(cur)
381 	{
382 		if ((!xmlStrcmp(cur->name, BAD_CAST "settings")))
383 		{
384 			val = xmlGetProp(cur, BAD_CAST "id");
385 			id = (val) ? atoi((gchar *) val) : 0;
386 			xmlFree(val);
387 			if (id>2) id=0;
388 			if (id<0) id=0;
389 			settings = rs_settings_new();
390 			mask |= rs_cache_load_setting(settings, doc, cur->xmlChildrenNode, version);
391 			rs_photo_apply_settings(photo, id, settings, MASK_ALL);
392 			g_object_unref(settings);
393 		}
394 		else if ((!xmlStrcmp(cur->name, BAD_CAST "priority")))
395 		{
396 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
397 			if (val)
398 			{
399 				photo->priority = atoi((gchar *) val);
400 				xmlFree(val);
401 			}
402 		}
403 		else if ((!xmlStrcmp(cur->name, BAD_CAST "orientation")))
404 		{
405 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
406 			if (val)
407 			{
408 				photo->orientation = atoi((gchar *) val);
409 				xmlFree(val);
410 			}
411 		}
412 		else if ((!xmlStrcmp(cur->name, BAD_CAST "angle")))
413 		{
414 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
415 			if (val)
416 			{
417 				photo->angle = rs_atof((gchar *) val);
418 				xmlFree(val);
419 			}
420 		}
421 		else if ((!xmlStrcmp(cur->name, BAD_CAST "exported")))
422 		{
423 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
424 			if (val)
425 			{
426 				if (g_ascii_strcasecmp((gchar *) val, "yes")==0)
427 					photo->exported = TRUE;
428 				xmlFree(val);
429 			}
430 		}
431 		else if ((!xmlStrcmp(cur->name, BAD_CAST "dcp-profile")))
432 		{
433 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
434 			if (val)
435 			{
436 				RSProfileFactory *factory = rs_profile_factory_new_default();
437 				RSDcpFile *dcp = rs_profile_factory_find_from_id(factory, (gchar *) val);
438 				if (dcp)
439 					rs_photo_set_dcp_profile(photo, dcp);
440 				xmlFree(val);
441 			}
442 		}
443 		else if ((!xmlStrcmp(cur->name, BAD_CAST "icc-profile")))
444 		{
445 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
446 			if (val)
447 			{
448 				RSProfileFactory *factory = rs_profile_factory_new_default();
449 				RSIccProfile *icc = rs_profile_factory_find_icc_from_filename(factory, (gchar *) val);
450 				if (icc)
451 					rs_photo_set_icc_profile(photo, icc);
452 				xmlFree(val);
453 			}
454 		}
455 		else if ((!xmlStrcmp(cur->name, BAD_CAST "crop")))
456 		{
457 			RS_RECT *crop = g_new0(RS_RECT, 1);
458 			gchar **vals = NULL;
459 
460 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
461 			if (val)
462 				vals = g_strsplit((gchar *)val, " ", 4);
463 			if (val && vals[0])
464 			{
465 				crop->x1 = atoi((gchar *) vals[0]);
466 				if (vals[1])
467 				{
468 					crop->y1 = atoi((gchar *) vals[1]);
469 					if (vals[2])
470 					{
471 						crop->x2 = atoi((gchar *) vals[2]);
472 						if (vals[3])
473 							crop->y2 = atoi((gchar *) vals[3]);
474 					}
475 				}
476 			}
477 
478 			/* If crop was done before demosaic was implemented, we should
479 			   double the dimensions */
480 			if (version < 2)
481 			{
482 				crop->x1 *= 2;
483 				crop->y1 *= 2;
484 				crop->x2 *= 2;
485 				crop->y2 *= 2;
486 			}
487 
488 			rs_photo_set_crop(photo, crop);
489 			g_free(crop);
490 			g_strfreev(vals);
491 			xmlFree(val);
492 		}
493 		cur = cur->next;
494 	}
495 
496 	xmlFreeDoc(doc);
497 	g_free(cachename);
498 	return mask;
499 }
500 
501 void
rs_cache_load_quick(const gchar * filename,gint * priority,gboolean * exported)502 rs_cache_load_quick(const gchar *filename, gint *priority, gboolean *exported)
503 {
504 	xmlDocPtr doc;
505 	xmlNodePtr cur;
506 	xmlChar *val;
507 	gchar *cachename;
508 
509 	if (priority) *priority = PRIO_U;
510 	if (exported) *exported = FALSE;
511 
512 	if (!filename)
513 		return;
514 
515 	cachename = rs_cache_get_name(filename);
516 
517 	if (!cachename)
518 		return;
519 
520 	if (!g_file_test(cachename, G_FILE_TEST_IS_REGULAR))
521 	{
522 		g_free(cachename);
523 		return;
524 	}
525 
526 	doc = xmlParseFile(cachename);
527 	g_free(cachename);
528 
529 	if(doc==NULL)
530 		return;
531 
532 	cur = xmlDocGetRootElement(doc);
533 
534 	cur = cur->xmlChildrenNode;
535 	while(cur)
536 	{
537 		if (priority && (!xmlStrcmp(cur->name, BAD_CAST "priority")))
538 		{
539 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
540 			*priority = atoi((gchar *) val);
541 			xmlFree(val);
542 		}
543 		if (exported && (!xmlStrcmp(cur->name, BAD_CAST "exported")))
544 		{
545 			val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
546 			if (g_ascii_strcasecmp((gchar *) val, "yes")==0)
547 				*exported = TRUE;
548 			xmlFree(val);
549 		}
550 		cur = cur->next;
551 	}
552 
553 	xmlFreeDoc(doc);
554 	return;
555 }
556 
557 void
rs_cache_save_flags(const gchar * filename,const guint * priority,const gboolean * exported)558 rs_cache_save_flags(const gchar *filename, const guint *priority, const gboolean *exported)
559 {
560 	RS_PHOTO *photo;
561 	RSSettingsMask mask;
562 
563 	g_assert(filename != NULL);
564 
565 	if (!(priority || exported)) return;
566 
567 	/* Aquire a "fake" RS_PHOTO */
568 	photo = rs_photo_new();
569 	photo->filename = (gchar *) filename;
570 
571 	if ((mask = rs_cache_load(photo)))
572 	{
573 		/* If we got a cache file, save as normal */
574 		if (priority)
575 			photo->priority = *priority;
576 		if (exported)
577 			photo->exported = *exported;
578 		rs_cache_save(photo, mask);
579 	}
580 	else
581 	{
582 		/* If we're creating a new file, only save what we know */
583 		xmlTextWriterPtr writer;
584 		gchar *cachename = rs_cache_get_name(photo->filename);
585 
586 		if (cachename)
587 		{
588 			writer = xmlNewTextWriterFilename(cachename, 0); /* fixme, check for errors */
589 			g_free(cachename);
590 
591 			xmlTextWriterStartDocument(writer, NULL, "ISO-8859-1", NULL);
592 			xmlTextWriterStartElement(writer, BAD_CAST "rawstudio-cache");
593 
594 			if (priority)
595 				xmlTextWriterWriteFormatElement(writer, BAD_CAST "priority", "%d",
596 					*priority);
597 
598 			if (exported && *exported)
599 				xmlTextWriterWriteFormatElement(writer, BAD_CAST "exported", "yes");
600 
601 			xmlTextWriterEndDocument(writer);
602 			xmlFreeTextWriter(writer);
603 		}
604 	}
605 
606 	/* Free the photo */
607 	photo->filename = NULL;
608 	g_object_unref(photo);
609 
610 	return;
611 }
612