1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 Free Software Foundation, Inc.
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, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <glib/gi18n.h>
24 #include "glib-utils.h"
25 #include "gth-main.h"
26 #include "gth-overwrite-dialog.h"
27 #include "gth-image.h"
28 #include "gth-image-list-task.h"
29 #include "gth-image-loader.h"
30 #include "gth-image-saver.h"
31 #include "gtk-utils.h"
32 
33 
34 struct _GthImageListTaskPrivate {
35 	GthBrowser           *browser;
36 	GList                *file_list;
37 	GthTask              *task;
38 	gulong                task_completed;
39 	gulong                task_progress;
40 	gulong                task_dialog;
41 	GList                *current;
42 	int                   n_current;
43 	int                   n_files;
44 	GthImage             *original_image;
45 	GthImage             *new_image;
46 	GFile                *destination_folder;
47 	GthFileData          *destination_file_data;
48 	GthOverwriteMode      overwrite_mode;
49 	GthOverwriteResponse  overwrite_response;
50 	char                 *mime_type;
51 };
52 
53 
G_DEFINE_TYPE_WITH_CODE(GthImageListTask,gth_image_list_task,GTH_TYPE_TASK,G_ADD_PRIVATE (GthImageListTask))54 G_DEFINE_TYPE_WITH_CODE (GthImageListTask,
55 			 gth_image_list_task,
56 			 GTH_TYPE_TASK,
57 			 G_ADD_PRIVATE (GthImageListTask))
58 
59 
60 static void
61 gth_image_list_task_finalize (GObject *object)
62 {
63 	GthImageListTask *self;
64 
65 	self = GTH_IMAGE_LIST_TASK (object);
66 
67 	g_free (self->priv->mime_type);
68 	_g_object_unref (self->priv->destination_folder);
69 	_g_object_unref (self->priv->original_image);
70 	_g_object_unref (self->priv->new_image);
71 	g_signal_handler_disconnect (self->priv->task, self->priv->task_completed);
72 	g_signal_handler_disconnect (self->priv->task, self->priv->task_progress);
73 	g_signal_handler_disconnect (self->priv->task, self->priv->task_dialog);
74 	g_object_unref (self->priv->task);
75 	_g_object_list_unref (self->priv->file_list);
76 	_g_object_unref (self->priv->destination_file_data);
77 
78 	G_OBJECT_CLASS (gth_image_list_task_parent_class)->finalize (object);
79 }
80 
81 
82 static void process_current_file (GthImageListTask *self);
83 
84 
85 static void
process_next_file(GthImageListTask * self)86 process_next_file (GthImageListTask *self)
87 {
88 	self->priv->n_current++;
89 	self->priv->current = self->priv->current->next;
90 	process_current_file (self);
91 }
92 
93 
94 static void image_task_save_current_image (GthImageListTask *self,
95 					   GFile            *file,
96 					   gboolean          replace);
97 
98 
99 static void
overwrite_dialog_response_cb(GtkDialog * dialog,gint response_id,gpointer user_data)100 overwrite_dialog_response_cb (GtkDialog *dialog,
101                               gint       response_id,
102                               gpointer   user_data)
103 {
104 	GthImageListTask *self = user_data;
105 	gboolean          close_overwrite_dialog = TRUE;
106 
107 	if (response_id != GTK_RESPONSE_OK)
108 		self->priv->overwrite_response = GTH_OVERWRITE_RESPONSE_CANCEL;
109 	else
110 		self->priv->overwrite_response = gth_overwrite_dialog_get_response (GTH_OVERWRITE_DIALOG (dialog));
111 
112 	gtk_widget_hide (GTK_WIDGET (dialog));
113 	gth_task_dialog (GTH_TASK (self), FALSE, NULL);
114 
115 	switch (self->priv->overwrite_response) {
116 	case GTH_OVERWRITE_RESPONSE_NO:
117 	case GTH_OVERWRITE_RESPONSE_ALWAYS_NO:
118 	case GTH_OVERWRITE_RESPONSE_UNSPECIFIED:
119 		if (self->priv->overwrite_response == GTH_OVERWRITE_RESPONSE_ALWAYS_NO)
120 			self->priv->overwrite_mode = GTH_OVERWRITE_SKIP;
121 		process_next_file (self);
122 		break;
123 
124 	case GTH_OVERWRITE_RESPONSE_YES:
125 	case GTH_OVERWRITE_RESPONSE_ALWAYS_YES:
126 		if (self->priv->overwrite_response == GTH_OVERWRITE_RESPONSE_ALWAYS_YES)
127 			self->priv->overwrite_mode = GTH_OVERWRITE_OVERWRITE;
128 		image_task_save_current_image (self, NULL, TRUE);
129 		break;
130 
131 	case GTH_OVERWRITE_RESPONSE_RENAME:
132 		{
133 			GFile  *parent;
134 			GFile  *new_destination;
135 			GError *error = NULL;
136 
137 			if (self->priv->destination_folder != NULL)
138 				parent = g_object_ref (self->priv->destination_folder);
139 			else
140 				parent = g_file_get_parent (self->priv->destination_file_data->file);
141 
142 			new_destination = g_file_get_child_for_display_name (parent, gth_overwrite_dialog_get_filename (GTH_OVERWRITE_DIALOG (dialog)), &error);
143 			if (new_destination == NULL) {
144 				_gtk_error_dialog_from_gerror_run (GTK_WINDOW (dialog), _("Could not rename the file"), error);
145 				g_clear_error (&error);
146 				gtk_widget_show (GTK_WIDGET (dialog));
147 				gth_task_dialog (GTH_TASK (self), TRUE, GTK_WIDGET (dialog));
148 				close_overwrite_dialog = FALSE;
149 			}
150 			else
151 				image_task_save_current_image (self, new_destination, FALSE);
152 
153 			g_object_unref (new_destination);
154 			g_object_unref (parent);
155 		}
156 		break;
157 
158 	case GTH_OVERWRITE_RESPONSE_CANCEL:
159 		{
160 			GError *error;
161 
162 			error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, "");
163 			gth_task_completed (GTH_TASK (self), error);
164 		}
165 		break;
166 	}
167 
168 	if (close_overwrite_dialog)
169 		gtk_widget_destroy (GTK_WIDGET (dialog));
170 }
171 
172 
173 static void
image_saved_cb(GthFileData * file_data,GError * error,gpointer user_data)174 image_saved_cb (GthFileData *file_data,
175 		GError      *error,
176 		gpointer     user_data)
177 {
178 	GthImageListTask *self = user_data;
179 	GFile             *parent;
180 	GList             *file_list;
181 
182 	if (error != NULL) {
183 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
184 			if (self->priv->overwrite_mode == GTH_OVERWRITE_SKIP) {
185 				process_next_file (self);
186 			}
187 			else  {
188 				GtkWidget *dialog;
189 
190 				dialog = gth_overwrite_dialog_new (NULL,
191 								   self->priv->new_image,
192 								   self->priv->destination_file_data->file,
193 								   GTH_OVERWRITE_RESPONSE_YES,
194 								   (self->priv->n_files == 1));
195 				gth_task_dialog (GTH_TASK (self), TRUE, dialog);
196 
197 				g_signal_connect (dialog,
198 						  "response",
199 						  G_CALLBACK (overwrite_dialog_response_cb),
200 						  self);
201 				gtk_widget_show (dialog);
202 			}
203 		}
204 		else
205 			gth_task_completed (GTH_TASK (self), error);
206 		return;
207 	}
208 
209 	parent = g_file_get_parent (file_data->file);
210 	file_list = g_list_append (NULL, file_data->file);
211 	gth_monitor_folder_changed (gth_main_get_default_monitor (),
212 				    parent,
213 				    file_list,
214 				    GTH_MONITOR_EVENT_CHANGED);
215 
216 	g_list_free (file_list);
217 	g_object_unref (parent);
218 
219 	process_next_file (self);
220 }
221 
222 
223 static void
image_task_dialog_cb(GthTask * task,gboolean opened,GtkWidget * dialog,gpointer user_data)224 image_task_dialog_cb (GthTask   *task,
225 		      gboolean   opened,
226 		      GtkWidget *dialog,
227 		      gpointer   user_data)
228 {
229 	gth_task_dialog (GTH_TASK (user_data), opened, dialog);
230 }
231 
232 
233 static void
image_task_progress_cb(GthTask * task,const char * description,const char * details,gboolean pulse,double fraction,gpointer user_data)234 image_task_progress_cb (GthTask    *task,
235 		        const char *description,
236 		        const char *details,
237 		        gboolean    pulse,
238 		        double      fraction,
239 		        gpointer    user_data)
240 {
241 	GthImageListTask *self = user_data;
242 	double             total_fraction;
243 	double             file_fraction;
244 
245 	total_fraction =  ((double) self->priv->n_current + 1) / (self->priv->n_files + 1);
246 	if (pulse)
247 		file_fraction = 0.5;
248 	else
249 		file_fraction = fraction;
250 
251 	if (details == NULL) {
252 		GthFileData *source_file_data = self->priv->current->data;
253 		details = g_file_info_get_display_name (source_file_data->info);
254 	}
255 
256 	gth_task_progress (GTH_TASK (self),
257 			   description,
258 			   details,
259 			   FALSE,
260 			   total_fraction + (file_fraction / (self->priv->n_files + 1)));
261 }
262 
263 
264 static void
image_task_save_current_image(GthImageListTask * self,GFile * file,gboolean replace)265 image_task_save_current_image (GthImageListTask *self,
266 			       GFile            *file,
267 			       gboolean          replace)
268 {
269 	GthImage *destination;
270 
271 	if (file != NULL)
272 		gth_file_data_set_file (self->priv->destination_file_data, file);
273 
274 	destination = gth_image_task_get_destination (GTH_IMAGE_TASK (self->priv->task));
275 	if (destination == NULL) {
276 		process_next_file (self);
277 		return;
278 	}
279 
280 	/* add a reference before unref-ing new_image because dest and
281 	 * new_image can be the same object. */
282 
283 	g_object_ref (destination);
284 	_g_object_unref (self->priv->new_image);
285 	self->priv->new_image = destination;
286 
287 	gth_image_save_to_file (self->priv->new_image,
288 				gth_file_data_get_mime_type (self->priv->destination_file_data),
289 				self->priv->destination_file_data,
290 				replace,
291 				gth_task_get_cancellable (GTH_TASK (self)),
292 				image_saved_cb,
293 				self);
294 }
295 
296 
297 static void
set_current_destination_file(GthImageListTask * self)298 set_current_destination_file (GthImageListTask *self)
299 {
300 	char  *display_name;
301 	GFile *parent;
302 	GFile *destination;
303 
304 	_g_object_unref (self->priv->destination_file_data);
305 	self->priv->destination_file_data = g_object_ref (self->priv->current->data);
306 
307 	if (self->priv->mime_type != NULL) {
308 		char          *no_ext;
309 		GthImageSaver *saver;
310 
311 		no_ext = _g_path_remove_extension (g_file_info_get_display_name (self->priv->destination_file_data->info));
312 		saver = gth_main_get_image_saver (self->priv->mime_type);
313 		g_return_if_fail (saver != NULL);
314 
315 		display_name = g_strconcat (no_ext, ".", gth_image_saver_get_default_ext (saver), NULL);
316 		gth_file_data_set_mime_type (self->priv->destination_file_data, self->priv->mime_type);
317 
318 		g_object_unref (saver);
319 		g_free (no_ext);
320 	}
321 	else
322 		display_name = g_strdup (g_file_info_get_display_name (self->priv->destination_file_data->info));
323 
324 	if (self->priv->destination_folder != NULL)
325 		parent = g_object_ref (self->priv->destination_folder);
326 	else
327 		parent = g_file_get_parent (self->priv->destination_file_data->file);
328 	destination = g_file_get_child_for_display_name (parent, display_name, NULL);
329 
330 	gth_file_data_set_file (self->priv->destination_file_data, destination);
331 
332 	g_object_unref (destination);
333 	g_object_unref (parent);
334 	g_free (display_name);
335 }
336 
337 
338 static void
image_task_completed_cb(GthTask * task,GError * error,gpointer user_data)339 image_task_completed_cb (GthTask  *task,
340 			 GError   *error,
341 			 gpointer  user_data)
342 {
343 	GthImageListTask *self = user_data;
344 
345 	if (g_error_matches (error, GTH_TASK_ERROR, GTH_TASK_ERROR_SKIP_TO_NEXT_FILE)) {
346 		process_next_file (self);
347 		return;
348 	}
349 
350 	if (error != NULL) {
351 		gth_task_completed (GTH_TASK (self), error);
352 		return;
353 	}
354 
355 	set_current_destination_file (self);
356 	image_task_save_current_image (self, NULL, (self->priv->overwrite_mode == GTH_OVERWRITE_OVERWRITE));
357 }
358 
359 
360 static void
file_buffer_ready_cb(void ** buffer,gsize count,GError * error,gpointer user_data)361 file_buffer_ready_cb (void     **buffer,
362 		      gsize      count,
363 		      GError    *error,
364 		      gpointer   user_data)
365 {
366 	GthImageListTask *self = user_data;
367 	GInputStream     *istream;
368 
369 	if (error != NULL) {
370 		gth_task_completed (GTH_TASK (self), error);
371 		return;
372 	}
373 
374 	istream = g_memory_input_stream_new_from_data (*buffer, count, NULL);
375 	self->priv->original_image = gth_image_new_from_stream (istream, -1, NULL, NULL, gth_task_get_cancellable (GTH_TASK (self)), &error);
376 
377 	g_object_unref (istream);
378 
379 	if (self->priv->original_image == NULL) {
380 		gth_task_completed (GTH_TASK (self), error);
381 		return;
382 	}
383 
384 	gth_image_task_set_source (GTH_IMAGE_TASK (self->priv->task), self->priv->original_image);
385 	gth_task_exec (self->priv->task, gth_task_get_cancellable (GTH_TASK (self)));
386 }
387 
388 
389 static void
file_info_ready_cb(GList * files,GError * error,gpointer user_data)390 file_info_ready_cb (GList    *files,
391 		    GError   *error,
392 		    gpointer  user_data)
393 {
394 	GthImageListTask *self = user_data;
395 	GthFileData       *updated_file_data;
396 	GthFileData       *source_file_data;
397 
398 	if (error != NULL) {
399 		gth_task_completed (GTH_TASK (self), error);
400 		return;
401 	}
402 
403 	source_file_data = self->priv->current->data;
404 	updated_file_data = (GthFileData*) files->data;
405 	g_file_info_copy_into (updated_file_data->info, source_file_data->info);
406 
407 	_g_file_load_async (source_file_data->file,
408 			    G_PRIORITY_DEFAULT,
409 			    gth_task_get_cancellable (GTH_TASK (self)),
410 			    file_buffer_ready_cb,
411 			    self);
412 }
413 
414 
415 static void
process_current_file(GthImageListTask * self)416 process_current_file (GthImageListTask *self)
417 {
418 	GthFileData *source_file_data;
419 	GList       *source_singleton;
420 
421 	if (self->priv->current == NULL) {
422 		if (self->priv->destination_folder != NULL)
423 			gth_browser_go_to (self->priv->browser, self->priv->destination_folder, NULL);
424 		gth_task_completed (GTH_TASK (self), NULL);
425 		return;
426 	}
427 
428 	_g_object_unref (self->priv->original_image);
429 	self->priv->original_image = NULL;
430 
431 	_g_object_unref (self->priv->new_image);
432 	self->priv->new_image = NULL;
433 
434 	gth_task_progress (GTH_TASK (self),
435 			   NULL,
436 			   NULL,
437 			   FALSE,
438 			   ((double) self->priv->n_current + 1) / (self->priv->n_files + 1));
439 
440 	source_file_data = self->priv->current->data;
441 	source_singleton = g_list_append (NULL, g_object_ref (source_file_data->file));
442 	_g_query_all_metadata_async (source_singleton,
443 				     GTH_LIST_DEFAULT,
444 				     "*",
445 				     gth_task_get_cancellable (GTH_TASK (self)),
446 				     file_info_ready_cb,
447 				     self);
448 
449 	_g_object_list_unref (source_singleton);
450 }
451 
452 
453 static void
gth_image_list_task_exec(GthTask * task)454 gth_image_list_task_exec (GthTask *task)
455 {
456 	GthImageListTask *self;
457 
458 	g_return_if_fail (GTH_IS_IMAGE_LIST_TASK (task));
459 
460 	self = GTH_IMAGE_LIST_TASK (task);
461 
462 	self->priv->current = self->priv->file_list;
463 	self->priv->n_current = 0;
464 	self->priv->n_files = g_list_length (self->priv->file_list);
465 	process_current_file (self);
466 }
467 
468 
469 static void
gth_image_list_task_class_init(GthImageListTaskClass * klass)470 gth_image_list_task_class_init (GthImageListTaskClass *klass)
471 {
472 	GObjectClass *object_class;
473 	GthTaskClass *task_class;
474 
475 	object_class = G_OBJECT_CLASS (klass);
476 	object_class->finalize = gth_image_list_task_finalize;
477 
478 	task_class = GTH_TASK_CLASS (klass);
479 	task_class->exec = gth_image_list_task_exec;
480 }
481 
482 
483 static void
gth_image_list_task_init(GthImageListTask * self)484 gth_image_list_task_init (GthImageListTask *self)
485 {
486 	self->priv = gth_image_list_task_get_instance_private (self);
487 	self->priv->original_image = NULL;
488 	self->priv->new_image = NULL;
489 	self->priv->destination_folder = NULL;
490 	self->priv->overwrite_response = GTH_OVERWRITE_RESPONSE_UNSPECIFIED;
491 	self->priv->mime_type = NULL;
492 }
493 
494 
495 GthTask *
gth_image_list_task_new(GthBrowser * browser,GList * file_list,GthImageTask * task)496 gth_image_list_task_new (GthBrowser    *browser,
497 			  GList        *file_list,
498 			  GthImageTask *task)
499 {
500 	GthImageListTask *self;
501 
502 	g_return_val_if_fail (task != NULL, NULL);
503 	g_return_val_if_fail (GTH_IS_IMAGE_TASK (task), NULL);
504 
505 	self = GTH_IMAGE_LIST_TASK (g_object_new (GTH_TYPE_IMAGE_LIST_TASK, NULL));
506 	self->priv->browser = browser;
507 	self->priv->file_list = _g_object_list_ref (file_list);
508 	self->priv->task = GTH_TASK (g_object_ref (task));
509 	self->priv->task_completed = g_signal_connect (self->priv->task,
510 						       "completed",
511 						       G_CALLBACK (image_task_completed_cb),
512 						       self);
513 	self->priv->task_progress = g_signal_connect (self->priv->task,
514 						      "progress",
515 						      G_CALLBACK (image_task_progress_cb),
516 						      self);
517 	self->priv->task_dialog = g_signal_connect (self->priv->task,
518 						    "dialog",
519 						    G_CALLBACK (image_task_dialog_cb),
520 						    self);
521 
522 	return (GthTask *) self;
523 }
524 
525 
526 void
gth_image_list_task_set_destination(GthImageListTask * self,GFile * folder)527 gth_image_list_task_set_destination (GthImageListTask *self,
528 				      GFile             *folder)
529 {
530 	_g_object_unref (self->priv->destination_folder);
531 	self->priv->destination_folder = _g_object_ref (folder);
532 }
533 
534 
535 void
gth_image_list_task_set_overwrite_mode(GthImageListTask * self,GthOverwriteMode overwrite_mode)536 gth_image_list_task_set_overwrite_mode (GthImageListTask    *self,
537 					 GthOverwriteMode      overwrite_mode)
538 {
539 	self->priv->overwrite_mode = overwrite_mode;
540 }
541 
542 
543 void
gth_image_list_task_set_output_mime_type(GthImageListTask * self,const char * mime_type)544 gth_image_list_task_set_output_mime_type (GthImageListTask *self,
545 					   const char        *mime_type)
546 {
547 	g_free (self->priv->mime_type);
548 	self->priv->mime_type = NULL;
549 	if (mime_type != NULL)
550 		self->priv->mime_type = g_strdup (mime_type);
551 }
552