1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2010 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 "gth-change-date-task.h"
24 
25 
26 struct _GthChangeDateTaskPrivate {
27 	GFile           *location;
28 	GList           *files; /* GFile */
29 	GList           *file_list; /* GthFileData */
30 	GthChangeFields  fields;
31 	GthChangeType    change_type;
32 	GthDateTime     *date_time;
33 	int              time_offset;
34 	int              n_files;
35 	int              n_current;
36 	GList           *current;
37 };
38 
39 
G_DEFINE_TYPE_WITH_CODE(GthChangeDateTask,gth_change_date_task,GTH_TYPE_TASK,G_ADD_PRIVATE (GthChangeDateTask))40 G_DEFINE_TYPE_WITH_CODE (GthChangeDateTask,
41 			 gth_change_date_task,
42 			 GTH_TYPE_TASK,
43 			 G_ADD_PRIVATE (GthChangeDateTask))
44 
45 
46 static void
47 gth_change_date_task_finalize (GObject *object)
48 {
49 	GthChangeDateTask *self;
50 
51 	self = GTH_CHANGE_DATE_TASK (object);
52 
53 	_g_object_unref (self->priv->location);
54 	_g_object_list_unref (self->priv->file_list);
55 	_g_object_list_unref (self->priv->files);
56 	gth_datetime_free (self->priv->date_time);
57 
58 	G_OBJECT_CLASS (gth_change_date_task_parent_class)->finalize (object);
59 }
60 
61 
62 static void
set_date_time_from_change_type(GthChangeDateTask * self,GthDateTime * date_time,GthChangeType change_type,GthFileData * file_data)63 set_date_time_from_change_type (GthChangeDateTask *self,
64 				GthDateTime       *date_time,
65 				GthChangeType      change_type,
66 				GthFileData       *file_data)
67 {
68 	if (change_type == GTH_CHANGE_TO_FOLLOWING_DATE) {
69 		gth_datetime_copy (self->priv->date_time, date_time);
70 	}
71 	else if (change_type == GTH_CHANGE_TO_FILE_MODIFIED_DATE) {
72 		gth_datetime_from_timeval (date_time, gth_file_data_get_modification_time (file_data));
73 	}
74 	else if (change_type == GTH_CHANGE_TO_FILE_CREATION_DATE) {
75 		gth_datetime_from_timeval (date_time, gth_file_data_get_creation_time (file_data));
76 	}
77 	else if (change_type == GTH_CHANGE_TO_PHOTO_ORIGINAL_DATE) {
78 		GTimeVal time_val;
79 
80 		if (gth_file_data_get_digitalization_time (file_data, &time_val))
81 			gth_datetime_from_timeval (date_time, &time_val);
82 	}
83 }
84 
85 
86 static void
update_modification_time(GthChangeDateTask * self)87 update_modification_time (GthChangeDateTask *self)
88 {
89 
90 	GList      *scan;
91 	GError     *error = NULL;
92 	GthMonitor *monitor;
93 	GList      *files;
94 
95 	if ((self->priv->fields & GTH_CHANGE_LAST_MODIFIED_DATE) == GTH_CHANGE_LAST_MODIFIED_DATE) {
96 		GthDateTime *date_time;
97 
98 		date_time = gth_datetime_new ();
99 		for (scan = self->priv->file_list; scan; scan = scan->next) {
100 			GthFileData *file_data = scan->data;
101 			GTimeVal     timeval;
102 
103 			gth_datetime_clear (date_time);
104 			if (self->priv->change_type == GTH_CHANGE_ADJUST_TIME)
105 				set_date_time_from_change_type (self, date_time, GTH_CHANGE_TO_FILE_MODIFIED_DATE, file_data);
106 			else
107 				set_date_time_from_change_type (self, date_time, self->priv->change_type, file_data);
108 
109 			/* Read the time from the attribute if it's not valid in
110 			 * date_time. */
111 
112 			if (! gth_time_valid (date_time->time)) {
113 				GTimeVal    *original_modification_time;
114 				GthDateTime *original_date_time;
115 
116 				original_modification_time = gth_file_data_get_modification_time (file_data);
117 				original_date_time = gth_datetime_new ();
118 				gth_datetime_from_timeval (original_date_time, original_modification_time);
119 				*date_time->time = *original_date_time->time;
120 
121 				gth_datetime_free (original_date_time);
122 			}
123 
124 			if (! gth_datetime_valid (date_time))
125 				continue;
126 
127 			if (gth_datetime_to_timeval (date_time, &timeval)) {
128 				if (self->priv->change_type == GTH_CHANGE_ADJUST_TIME)
129 					timeval.tv_sec += self->priv->time_offset;
130 				if (! _g_file_set_modification_time (file_data->file,
131 								     &timeval,
132 								     gth_task_get_cancellable (GTH_TASK (self)),
133 								     &error))
134 				{
135 					break;
136 				}
137 			}
138 		}
139 
140 		gth_datetime_free (date_time);
141 	}
142 
143 	monitor = gth_main_get_default_monitor ();
144 	files = gth_file_data_list_to_file_list (self->priv->file_list);
145 	gth_monitor_folder_changed (monitor,
146 				    self->priv->location,
147 				    files,
148 				    GTH_MONITOR_EVENT_CHANGED);
149 
150 	gth_task_completed (GTH_TASK (self), error);
151 
152 	_g_object_list_unref (files);
153 }
154 
155 
156 static void
write_metadata_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)157 write_metadata_ready_cb (GObject      *source_object,
158                 	 GAsyncResult *result,
159                 	 gpointer      user_data)
160 {
161 	GthChangeDateTask *self = user_data;
162 	GError            *error = NULL;
163 
164 	if (! _g_write_metadata_finish (result, &error)) {
165 		gth_task_completed (GTH_TASK (self), error);
166 		return;
167 	}
168 
169 	if (g_cancellable_set_error_if_cancelled (gth_task_get_cancellable (GTH_TASK (self)), &error)) {
170 		gth_task_completed (GTH_TASK (self), error);
171 		return;
172 	}
173 
174 	update_modification_time (self);
175 }
176 
177 
178 static void
set_date_metadata(GthFileData * file_data,const char * attribute,GthDateTime * date_time,int time_offset)179 set_date_metadata (GthFileData *file_data,
180 		   const char  *attribute,
181 		   GthDateTime *date_time,
182 		   int          time_offset)
183 {
184 	GthDateTime *new_date_time;
185 
186 	new_date_time = gth_datetime_new ();
187 	gth_datetime_copy (date_time, new_date_time);
188 
189 	if (time_offset != 0) {
190 		GTimeVal timeval;
191 
192 		gth_datetime_to_timeval (new_date_time, &timeval);
193 		timeval.tv_sec += time_offset;
194 		gth_datetime_from_timeval (new_date_time, &timeval);
195 	}
196 	else {
197 		/* Read the time from the attribute if it's not valid in
198 		 * date_time. */
199 
200 		if (! gth_time_valid (new_date_time->time)) {
201 			GthMetadata *original_date;
202 
203 			original_date = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, attribute);
204 			if (original_date != NULL) {
205 				GthDateTime *original_date_time;
206 
207 				original_date_time = gth_datetime_new ();
208 				gth_datetime_from_exif_date (original_date_time, gth_metadata_get_raw (original_date));
209 				*new_date_time->time = *original_date_time->time;
210 
211 				gth_datetime_free (original_date_time);
212 			}
213 		}
214 	}
215 
216 	if (gth_datetime_valid (new_date_time)) {
217 		char    *raw;
218 		char    *formatted;
219 		GObject *metadata;
220 
221 		raw = gth_datetime_to_exif_date (new_date_time);
222 		formatted = gth_datetime_strftime (new_date_time, "%x");
223 		metadata = (GObject *) gth_metadata_new ();
224 		g_object_set (metadata,
225 			      "id", attribute,
226 			      "raw", raw,
227 			      "formatted", formatted,
228 			      NULL);
229 		g_file_info_set_attribute_object (file_data->info, attribute, metadata);
230 
231 		g_object_unref (metadata);
232 		g_free (formatted);
233 		g_free (raw);
234 	}
235 
236 	gth_datetime_free (new_date_time);
237 }
238 
239 
240 static void
set_date_time_from_field(GthChangeDateTask * self,GthDateTime * date_time,GthChangeFields field,GthFileData * file_data)241 set_date_time_from_field (GthChangeDateTask *self,
242 			  GthDateTime       *date_time,
243 			  GthChangeFields    field,
244 			  GthFileData       *file_data)
245 {
246 	if (field & GTH_CHANGE_LAST_MODIFIED_DATE) {
247 		gth_datetime_from_timeval (date_time, gth_file_data_get_modification_time (file_data));
248 	}
249 	else if (field & GTH_CHANGE_COMMENT_DATE) {
250 		GthMetadata *m;
251 		GTimeVal     time_val;
252 
253 		m = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::datetime");
254 		if ((m != NULL) && _g_time_val_from_exif_date (gth_metadata_get_raw (m), &time_val))
255 			gth_datetime_from_timeval (date_time, &time_val);
256 	}
257 }
258 
259 
260 static void
info_ready_cb(GList * files,GError * error,gpointer user_data)261 info_ready_cb (GList    *files,
262 	       GError   *error,
263 	       gpointer  user_data)
264 {
265 	GthChangeDateTask *self = user_data;
266 	GthDateTime       *date_time;
267 	GList             *scan;
268 	GPtrArray         *attribute_v;
269 
270 	if (error != NULL) {
271 		gth_task_completed (GTH_TASK (self), error);
272 		return;
273 	}
274 
275 	if (g_cancellable_set_error_if_cancelled (gth_task_get_cancellable (GTH_TASK (self)), &error)) {
276 		gth_task_completed (GTH_TASK (self), error);
277 		return;
278 	}
279 
280 	date_time = gth_datetime_new ();
281 	self->priv->file_list = _g_object_list_ref (files);
282 	for (scan = self->priv->file_list; scan; scan = scan->next) {
283 		GthFileData *file_data = scan->data;
284 
285 		if (self->priv->change_type == GTH_CHANGE_ADJUST_TIME) {
286 			if (self->priv->fields & GTH_CHANGE_COMMENT_DATE) {
287 				gth_datetime_clear (date_time);
288 				set_date_time_from_field (self, date_time, GTH_CHANGE_COMMENT_DATE, file_data);
289 				if (gth_datetime_valid (date_time))
290 					set_date_metadata (file_data, "general::datetime", date_time, self->priv->time_offset);
291 			}
292 		}
293 		else {
294 			gth_datetime_clear (date_time);
295 			set_date_time_from_change_type (self, date_time, self->priv->change_type, file_data);
296 			if (g_date_valid (date_time->date)) {
297 				if (self->priv->fields & GTH_CHANGE_COMMENT_DATE) {
298 					set_date_metadata (file_data, "general::datetime", date_time, 0);
299 				}
300 			}
301 		}
302 	}
303 
304 	attribute_v = g_ptr_array_new ();
305 	if (self->priv->fields & GTH_CHANGE_COMMENT_DATE)
306 		g_ptr_array_add (attribute_v, "general::datetime");
307 	if (attribute_v->len > 0) {
308 		char *attributes;
309 
310 		attributes = _g_string_array_join (attribute_v, ",");
311 		_g_write_metadata_async (self->priv->file_list,
312 					 GTH_METADATA_WRITE_DEFAULT,
313 					 attributes,
314 					 gth_task_get_cancellable (GTH_TASK (self)),
315 					 write_metadata_ready_cb,
316 					 self);
317 
318 		g_free (attributes);
319 	}
320 	else
321 		update_modification_time (self);
322 
323 	g_ptr_array_unref (attribute_v);
324 	gth_datetime_free (date_time);
325 }
326 
327 
328 static void
gth_change_date_task_exec(GthTask * task)329 gth_change_date_task_exec (GthTask *task)
330 {
331 	GthChangeDateTask *self = GTH_CHANGE_DATE_TASK (task);
332 
333 	_g_query_all_metadata_async (self->priv->files,
334 				     GTH_LIST_DEFAULT,
335 				     "*",
336 				     gth_task_get_cancellable (task),
337 				     info_ready_cb,
338 				     self);
339 }
340 
341 
342 static void
gth_change_date_task_class_init(GthChangeDateTaskClass * klass)343 gth_change_date_task_class_init (GthChangeDateTaskClass *klass)
344 {
345 	GObjectClass *object_class;
346 	GthTaskClass *task_class;
347 
348 	object_class = G_OBJECT_CLASS (klass);
349 	object_class->finalize = gth_change_date_task_finalize;
350 
351 	task_class = GTH_TASK_CLASS (klass);
352 	task_class->exec = gth_change_date_task_exec;
353 }
354 
355 
356 static void
gth_change_date_task_init(GthChangeDateTask * self)357 gth_change_date_task_init (GthChangeDateTask *self)
358 {
359 	self->priv = gth_change_date_task_get_instance_private (self);
360 	self->priv->date_time = gth_datetime_new ();
361 }
362 
363 
364 GthTask *
gth_change_date_task_new(GFile * location,GList * files,GthChangeFields fields,GthChangeType change_type,GthDateTime * date_time,int time_offset)365 gth_change_date_task_new (GFile             *location,
366 			  GList             *files, /* GthFileData */
367 			  GthChangeFields    fields,
368 			  GthChangeType      change_type,
369 			  GthDateTime       *date_time,
370 			  int                time_offset)
371 {
372 	GthChangeDateTask *self;
373 
374 	self = GTH_CHANGE_DATE_TASK (g_object_new (GTH_TYPE_CHANGE_DATE_TASK, NULL));
375 	self->priv->location = g_file_dup (location);
376 	self->priv->files = gth_file_data_list_to_file_list (files);
377 	self->priv->fields = fields;
378 	self->priv->change_type = change_type;
379 	if (date_time != NULL)
380 		gth_datetime_copy (date_time, self->priv->date_time);
381 	self->priv->time_offset = time_offset;
382 
383 	return (GthTask *) self;
384 }
385