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