1 /*
2 * Photos - access, organize and share your photos on GNOME
3 * Copyright © 2016 Umang Jain
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (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, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "config.h"
21
22 #include <dazzle.h>
23 #include <gio/gdesktopappinfo.h>
24 #include <glib/gi18n.h>
25 #include <gtk/gtk.h>
26
27 #include "photos-base-item.h"
28 #include "photos-base-manager.h"
29 #include "photos-export-notification.h"
30 #include "photos-notification-manager.h"
31 #include "photos-utils.h"
32
33
34 struct _PhotosExportNotification
35 {
36 GtkGrid parent_instance;
37 GtkWidget *ntfctn_mngr;
38 GError *error;
39 GFile *file;
40 GList *items;
41 guint timeout_id;
42 };
43
44 enum
45 {
46 PROP_0,
47 PROP_ERROR,
48 PROP_FILE,
49 PROP_ITEMS
50 };
51
52
53 G_DEFINE_TYPE (PhotosExportNotification, photos_export_notification, GTK_TYPE_GRID);
54
55
56 enum
57 {
58 EXPORT_TIMEOUT = 10 /* s */
59 };
60
61
62 static void
photos_export_notification_remove_timeout(PhotosExportNotification * self)63 photos_export_notification_remove_timeout (PhotosExportNotification *self)
64 {
65 if (self->timeout_id != 0)
66 {
67 g_source_remove (self->timeout_id);
68 self->timeout_id = 0;
69 }
70 }
71
72
73 static void
photos_export_notification_destroy(PhotosExportNotification * self)74 photos_export_notification_destroy (PhotosExportNotification *self)
75 {
76 photos_export_notification_remove_timeout (self);
77 gtk_widget_destroy (GTK_WIDGET (self));
78 }
79
80
81 static void
photos_export_notification_analyze(PhotosExportNotification * self)82 photos_export_notification_analyze (PhotosExportNotification *self)
83 {
84 g_autoptr (GAppLaunchContext) ctx = NULL;
85 g_autoptr (GDesktopAppInfo) analyzer = NULL;
86
87 analyzer = g_desktop_app_info_new ("org.gnome.baobab.desktop");
88 ctx = photos_utils_new_app_launch_context_from_widget (GTK_WIDGET (self));
89
90 {
91 g_autoptr (GError) error = NULL;
92
93 if (!g_app_info_launch (G_APP_INFO (analyzer), NULL, ctx, &error))
94 g_warning ("Unable to launch disk usage analyzer: %s", error->message);
95 }
96
97 photos_export_notification_destroy (self);
98 }
99
100
101 static void
photos_export_notification_close(PhotosExportNotification * self)102 photos_export_notification_close (PhotosExportNotification *self)
103 {
104 photos_export_notification_destroy (self);
105 }
106
107
108 static void
photos_export_notification_empty_trash_call_empty_trash(GObject * source_object,GAsyncResult * res,gpointer user_data)109 photos_export_notification_empty_trash_call_empty_trash (GObject *source_object, GAsyncResult *res, gpointer user_data)
110 {
111 GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
112 g_autoptr (GVariant) variant = NULL;
113
114 {
115 g_autoptr (GError) error = NULL;
116
117 variant = g_dbus_connection_call_finish (connection, res, &error);
118 if (error != NULL)
119 {
120 if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
121 || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE)
122 || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)
123 || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT))
124 {
125 g_warning ("Unable to call org.gnome.SettingsDaemon.Housekeeping:EmptyTrash: "
126 "unknown interface/method/object/service");
127 }
128 else
129 {
130 g_warning ("Unable to call org.gnome.SettingsDaemon.Housekeeping:EmptyTrash: %s", error->message);
131 }
132 }
133 }
134 }
135
136
137 static void
photos_export_notification_empty_trash_call_empty_trash_pre_3_24(GObject * source_object,GAsyncResult * res,gpointer user_data)138 photos_export_notification_empty_trash_call_empty_trash_pre_3_24 (GObject *source_object,
139 GAsyncResult *res,
140 gpointer user_data)
141 {
142 GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
143 g_autoptr (GVariant) variant = NULL;
144
145 {
146 g_autoptr (GError) error = NULL;
147
148 variant = g_dbus_connection_call_finish (connection, res, &error);
149 if (error != NULL)
150 {
151 if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
152 || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE)
153 || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)
154 || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT))
155 {
156 g_dbus_connection_call (connection,
157 "org.gnome.SettingsDaemon.Housekeeping",
158 "/org/gnome/SettingsDaemon/Housekeeping",
159 "org.gnome.SettingsDaemon.Housekeeping",
160 "EmptyTrash",
161 NULL,
162 NULL,
163 G_DBUS_CALL_FLAGS_NONE,
164 -1,
165 NULL,
166 photos_export_notification_empty_trash_call_empty_trash,
167 NULL);
168 }
169 else
170 {
171 g_warning ("Unable to call org.gnome.SettingsDaemon.Housekeeping:EmptyTrash (GNOME < 3.24): %s",
172 error->message);
173 }
174 }
175 }
176 }
177
178
179 static void
photos_export_notification_empty_trash(PhotosExportNotification * self)180 photos_export_notification_empty_trash (PhotosExportNotification *self)
181 {
182 GApplication *app;
183 GDBusConnection *connection;
184
185 app = g_application_get_default ();
186 connection = g_application_get_dbus_connection (app);
187
188 g_dbus_connection_call (connection,
189 "org.gnome.SettingsDaemon",
190 "/org/gnome/SettingsDaemon/Housekeeping",
191 "org.gnome.SettingsDaemon.Housekeeping",
192 "EmptyTrash",
193 NULL,
194 NULL,
195 G_DBUS_CALL_FLAGS_NONE,
196 -1,
197 NULL,
198 photos_export_notification_empty_trash_call_empty_trash_pre_3_24,
199 NULL);
200
201 photos_export_notification_destroy (self);
202 }
203
204
205 static void
photos_export_notification_export_folder(PhotosExportNotification * self)206 photos_export_notification_export_folder (PhotosExportNotification *self)
207 {
208 GApplication *app;
209 g_autoptr (GFile) directory = NULL;
210 GtkWindow *parent;
211 g_autofree gchar *directory_uri = NULL;
212 guint32 time;
213
214 g_return_if_fail (self->file != NULL);
215 g_return_if_fail (self->items != NULL);
216
217 app = g_application_get_default ();
218 parent = gtk_application_get_active_window (GTK_APPLICATION (app));
219 time = gtk_get_current_event_time ();
220
221 if (self->items->next == NULL) /* length == 1 */
222 {
223 {
224 g_autoptr (GError) error = NULL;
225 g_autofree gchar *file_uri = NULL;
226
227 if (dzl_file_manager_show (self->file, &error))
228 goto out;
229
230 file_uri = g_file_get_uri (self->file);
231 g_warning ("Unable to use org.freedesktop.FileManager1 for %s: %s", file_uri, error->message);
232 }
233
234 directory = g_file_get_parent (self->file);
235 }
236 else
237 {
238 directory = g_object_ref (self->file);
239 }
240
241 directory_uri = g_file_get_uri (directory);
242
243 {
244 g_autoptr (GError) error = NULL;
245
246 if (!gtk_show_uri_on_window (parent, directory_uri, time, &error))
247 g_warning ("Failed to open uri: %s", error->message);
248 }
249
250 out:
251 photos_export_notification_destroy (self);
252 }
253
254
255 static void
photos_export_notification_open(PhotosExportNotification * self)256 photos_export_notification_open (PhotosExportNotification *self)
257 {
258 GApplication *app;
259 GtkWindow *parent;
260 g_autofree gchar *uri = NULL;
261 guint32 time;
262
263 g_return_if_fail (self->file != NULL);
264 g_return_if_fail (self->items != NULL);
265 g_return_if_fail (self->items->next == NULL);
266
267 app = g_application_get_default ();
268 parent = gtk_application_get_active_window (GTK_APPLICATION (app));
269 time = gtk_get_current_event_time ();
270
271 uri = g_file_get_uri (self->file);
272
273 {
274 g_autoptr (GError) error = NULL;
275
276 if (!gtk_show_uri_on_window (parent, uri, time, &error))
277 g_warning ("Failed to open uri: %s", error->message);
278 }
279
280 photos_export_notification_destroy (self);
281 }
282
283
284 static gboolean
photos_export_notification_timeout(gpointer user_data)285 photos_export_notification_timeout (gpointer user_data)
286 {
287 PhotosExportNotification *self = PHOTOS_EXPORT_NOTIFICATION (user_data);
288
289 self->timeout_id = 0;
290 photos_export_notification_destroy (self);
291 return G_SOURCE_REMOVE;
292 }
293
294
295 static void
photos_export_notification_constructed(GObject * object)296 photos_export_notification_constructed (GObject *object)
297 {
298 PhotosExportNotification *self = PHOTOS_EXPORT_NOTIFICATION (object);
299 GtkWidget *close;
300 GtkWidget *image;
301 GtkWidget *label;
302 g_autofree gchar *msg = NULL;
303 guint length;
304
305 G_OBJECT_CLASS (photos_export_notification_parent_class)->constructed (object);
306
307 gtk_grid_set_column_spacing (GTK_GRID (self), 12);
308 gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
309
310 length = g_list_length (self->items);
311
312 if (length == 0)
313 {
314 g_assert_nonnull (self->error);
315
316 if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_NO_SPACE))
317 msg = g_strdup (_("Failed to export: not enough space"));
318 else
319 msg = g_strdup (_("Failed to export"));
320 }
321 else if (length == 1)
322 {
323 const gchar *name;
324
325 name = photos_base_item_get_name_with_fallback (PHOTOS_BASE_ITEM (self->items->data));
326 msg = g_strdup_printf (_("“%s” exported"), name);
327 }
328 else
329 {
330 msg = g_strdup_printf (ngettext ("%d item exported", "%d items exported", length), length);
331 }
332
333 label = gtk_label_new (msg);
334 gtk_widget_set_halign (label, GTK_ALIGN_START);
335 gtk_widget_set_hexpand (label, TRUE);
336 gtk_container_add (GTK_CONTAINER (self), label);
337
338 if (length == 0)
339 {
340 g_assert_nonnull (self->error);
341
342 if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_NO_SPACE))
343 {
344 GtkWidget *analyze;
345 GtkWidget *empty_trash;
346
347 analyze = gtk_button_new_with_label (_("Analyze"));
348 gtk_widget_set_valign (analyze, GTK_ALIGN_CENTER);
349 gtk_container_add (GTK_CONTAINER (self), analyze);
350 g_signal_connect_swapped (analyze, "clicked", G_CALLBACK (photos_export_notification_analyze), self);
351
352 empty_trash = gtk_button_new_with_label (_("Empty Trash"));
353 gtk_widget_set_valign (empty_trash, GTK_ALIGN_CENTER);
354 gtk_container_add (GTK_CONTAINER (self), empty_trash);
355 g_signal_connect_swapped (empty_trash,
356 "clicked",
357 G_CALLBACK (photos_export_notification_empty_trash),
358 self);
359 }
360 }
361 else
362 {
363 GtkWidget *export_folder;
364
365 if (length == 1)
366 {
367 GtkWidget *open;
368
369 open = gtk_button_new_with_label (_("Open"));
370 gtk_widget_set_valign (open, GTK_ALIGN_CENTER);
371 gtk_widget_set_halign (open, GTK_ALIGN_CENTER);
372 gtk_container_add (GTK_CONTAINER (self), open);
373 g_signal_connect_swapped (open, "clicked", G_CALLBACK (photos_export_notification_open), self);
374 }
375
376 /* Translators: this is the label of the button to open the
377 * folder where the item was exported.
378 */
379 export_folder = gtk_button_new_with_label (_("Export Folder"));
380 gtk_widget_set_valign (export_folder, GTK_ALIGN_CENTER);
381 gtk_container_add (GTK_CONTAINER (self), export_folder);
382 g_signal_connect_swapped (export_folder,
383 "clicked",
384 G_CALLBACK (photos_export_notification_export_folder),
385 self);
386 }
387
388 image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_INVALID);
389 gtk_widget_set_margin_bottom (image, 2);
390 gtk_widget_set_margin_top (image, 2);
391 gtk_image_set_pixel_size (GTK_IMAGE (image), 16);
392
393 close = gtk_button_new ();
394 gtk_widget_set_valign (close, GTK_ALIGN_CENTER);
395 gtk_widget_set_focus_on_click (close, FALSE);
396 gtk_button_set_relief (GTK_BUTTON (close), GTK_RELIEF_NONE);
397 gtk_button_set_image (GTK_BUTTON (close), image);
398 gtk_container_add (GTK_CONTAINER (self), close);
399 g_signal_connect_swapped (close, "clicked", G_CALLBACK (photos_export_notification_close), self);
400
401 photos_notification_manager_add_notification (PHOTOS_NOTIFICATION_MANAGER (self->ntfctn_mngr), GTK_WIDGET (self));
402
403 self->timeout_id = g_timeout_add_seconds (EXPORT_TIMEOUT, photos_export_notification_timeout, self);
404 }
405
406
407 static void
photos_export_notification_dispose(GObject * object)408 photos_export_notification_dispose (GObject *object)
409 {
410 PhotosExportNotification *self = PHOTOS_EXPORT_NOTIFICATION (object);
411
412 photos_export_notification_remove_timeout (self);
413
414 if (self->items != NULL)
415 {
416 g_list_free_full (self->items, g_object_unref);
417 self->items = NULL;
418 }
419
420 g_clear_object (&self->file);
421 g_clear_object (&self->ntfctn_mngr);
422
423 G_OBJECT_CLASS (photos_export_notification_parent_class)->dispose (object);
424 }
425
426
427 static void
photos_export_notification_finalize(GObject * object)428 photos_export_notification_finalize (GObject *object)
429 {
430 PhotosExportNotification *self = PHOTOS_EXPORT_NOTIFICATION (object);
431
432 g_clear_error (&self->error);
433
434 G_OBJECT_CLASS (photos_export_notification_parent_class)->finalize (object);
435 }
436
437
438 static void
photos_export_notification_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)439 photos_export_notification_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
440 {
441 PhotosExportNotification *self = PHOTOS_EXPORT_NOTIFICATION (object);
442
443 switch (prop_id)
444 {
445 case PROP_ERROR:
446 self->error = (GError *) g_value_dup_boxed (value);
447 break;
448
449 case PROP_FILE:
450 self->file = G_FILE (g_value_dup_object (value));
451 break;
452
453 case PROP_ITEMS:
454 {
455 GList *items;
456
457 items = (GList *) g_value_get_pointer (value);
458 self->items = g_list_copy_deep (items, (GCopyFunc) g_object_ref, NULL);
459 break;
460 }
461
462 default:
463 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
464 break;
465 }
466 }
467
468
469 static void
photos_export_notification_init(PhotosExportNotification * self)470 photos_export_notification_init (PhotosExportNotification *self)
471 {
472 self->ntfctn_mngr = photos_notification_manager_dup_singleton ();
473 }
474
475
476 static void
photos_export_notification_class_init(PhotosExportNotificationClass * class)477 photos_export_notification_class_init (PhotosExportNotificationClass *class)
478 {
479 GObjectClass *object_class = G_OBJECT_CLASS (class);
480
481 object_class->constructed = photos_export_notification_constructed;
482 object_class->dispose = photos_export_notification_dispose;
483 object_class->finalize = photos_export_notification_finalize;
484 object_class->set_property = photos_export_notification_set_property;
485
486 g_object_class_install_property (object_class,
487 PROP_ERROR,
488 g_param_spec_boxed ("error",
489 "Error",
490 "Error thrown during export",
491 G_TYPE_ERROR,
492 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
493
494 g_object_class_install_property (object_class,
495 PROP_FILE,
496 g_param_spec_object ("file",
497 "File",
498 "A GFile representing the exported file or directory",
499 G_TYPE_FILE,
500 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
501
502 g_object_class_install_property (object_class,
503 PROP_ITEMS,
504 g_param_spec_pointer ("items",
505 "List of PhotosBaseItems",
506 "List of items that were exported",
507 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
508 }
509
510
511 void
photos_export_notification_new(GList * items,GFile * file)512 photos_export_notification_new (GList *items, GFile *file)
513 {
514 g_return_if_fail (G_IS_FILE (file));
515 g_return_if_fail (items != NULL);
516
517 g_object_new (PHOTOS_TYPE_EXPORT_NOTIFICATION, "file", file, "items", items, NULL);
518 }
519
520
521 void
photos_export_notification_new_with_error(GError * error)522 photos_export_notification_new_with_error (GError *error)
523 {
524 g_return_if_fail (error != NULL);
525 g_object_new (PHOTOS_TYPE_EXPORT_NOTIFICATION, "error", error, NULL);
526 }
527