1 /*
2  * frogr-controller.c -- Controller of the whole application
3  *
4  * Copyright (C) 2009-2020 Mario Sanchez Prada
5  * Authors: Mario Sanchez Prada <msanchez@gnome.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 3 of the GNU General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>
18  *
19  */
20 
21 #include "frogr-controller.h"
22 
23 #include "frogr-about-dialog.h"
24 #include "frogr-account.h"
25 #include "frogr-add-tags-dialog.h"
26 #include "frogr-add-to-group-dialog.h"
27 #include "frogr-add-to-set-dialog.h"
28 #include "frogr-auth-dialog.h"
29 #include "frogr-config.h"
30 #include "frogr-create-new-set-dialog.h"
31 #include "frogr-details-dialog.h"
32 #include "frogr-file-loader.h"
33 #include "frogr-global-defs.h"
34 #include "frogr-main-view.h"
35 #include "frogr-settings-dialog.h"
36 #include "frogr-util.h"
37 
38 #include <config.h>
39 #include <flicksoup/flicksoup.h>
40 #include <glib/gi18n.h>
41 #include <gtk/gtk.h>
42 #include <json-glib/json-glib.h>
43 #include <string.h>
44 
45 #define API_KEY "18861766601de84f0921ce6be729f925"
46 #define SHARED_SECRET "6233fbefd85f733a"
47 
48 #define DEFAULT_TIMEOUT 100
49 #define MAX_AUTH_TIMEOUT 60000
50 
51 #define MAX_ATTEMPTS 5
52 
53 
54 struct _FrogrController
55 {
56   GObject parent;
57 
58   FrogrControllerState state;
59 
60   FrogrMainView *mainview;
61   FrogrConfig *config;
62   FrogrAccount *account;
63 
64   FspSession *session;
65   GList *cancellables;
66 
67   /* We use this booleans as flags */
68   gboolean app_running;
69   gboolean fetching_token_replacement;
70   gboolean fetching_auth_url;
71   gboolean fetching_auth_token;
72   gboolean fetching_photosets;
73   gboolean fetching_groups;
74   gboolean fetching_tags;
75   gboolean setting_license;
76   gboolean setting_location;
77   gboolean setting_replace_date_posted;
78   gboolean adding_to_set;
79   gboolean adding_to_group;
80 
81   gboolean photosets_fetched;
82   gboolean groups_fetched;
83   gboolean tags_fetched;
84 
85   /* Event sources IDs for dialogs to be shown on idle */
86   guint show_details_dialog_source_id;
87   guint show_add_tags_dialog_source_id;
88   guint show_create_new_set_dialog_source_id;
89   guint show_add_to_set_dialog_source_id;
90   guint show_add_to_group_dialog_source_id;
91 };
92 
93 G_DEFINE_TYPE (FrogrController, frogr_controller, G_TYPE_OBJECT)
94 
95 
96 /* Signals */
97 enum {
98   STATE_CHANGED,
99   ACTIVE_ACCOUNT_CHANGED,
100   ACCOUNTS_CHANGED,
101   N_SIGNALS
102 };
103 
104 static guint signals[N_SIGNALS] = { 0 };
105 
106 static FrogrController *_instance = NULL;
107 
108 typedef enum {
109   AFTER_UPLOAD_OP_SETTING_LICENSE,
110   AFTER_UPLOAD_OP_SETTING_LOCATION,
111   AFTER_UPLOAD_OP_SETTING_REPLACE_DATE_POSTED,
112   AFTER_UPLOAD_OP_ADDING_TO_SET,
113   AFTER_UPLOAD_OP_ADDING_TO_GROUP,
114   N_AFTER_UPLOAD_OPS
115 } AfterUploadOp;
116 
117 typedef struct {
118   gboolean retrieve_everything;
119   gboolean force_extra_data;
120 } FetchAccountInfoData;
121 
122 typedef struct {
123   GSList *pictures;
124   GSList *current;
125   guint index;
126   guint n_pictures;
127   gint upload_attempts;
128   GError *error;
129 } UploadPicturesData;
130 
131 typedef struct {
132   FrogrController *controller;
133   FrogrPicture *picture;
134   GSList *photosets;
135   GSList *groups;
136   gint after_upload_attempts[N_AFTER_UPLOAD_OPS];
137   GCancellable *cancellable;
138   UploadPicturesData *up_data;
139 } UploadOnePictureData;
140 
141 typedef struct {
142   FrogrController *controller;
143   GCancellable *cancellable;
144 } CancellableOperationData;
145 
146 typedef enum {
147   FETCHING_NOTHING,
148   FETCHING_TOKEN_REPLACEMENT,
149   FETCHING_AUTH_URL,
150   FETCHING_AUTH_TOKEN,
151   FETCHING_ACCOUNT_INFO,
152   FETCHING_ACCOUNT_EXTRA_INFO,
153   FETCHING_PHOTOSETS,
154   FETCHING_GROUPS,
155   FETCHING_TAGS
156 } FetchingActivity;
157 
158 /* Prototypes */
159 
160 static gboolean _load_pictures_on_idle (gpointer data);
161 
162 static gboolean _load_project_file_on_idle (gpointer data);
163 
164 static void _g_application_startup_cb (GApplication *app, gpointer data);
165 
166 static void _g_application_activate_cb (GApplication *app, gpointer data);
167 
168 static void _g_application_open_files_cb (GApplication *app, GFile **files, gint n_files, gchar *hint, gpointer data);
169 
170 static void _g_application_shutdown_cb (GApplication *app, gpointer data);
171 
172 static void _set_active_account (FrogrController *self, FrogrAccount *account);
173 
174 static void _set_state (FrogrController *self, FrogrControllerState state);
175 
176 static GCancellable *_register_new_cancellable (FrogrController *self);
177 
178 static void _clear_cancellable (FrogrController *self, GCancellable *cancellable);
179 
180 static void _handle_flicksoup_error (FrogrController *self, GError *error, gboolean notify_user);
181 
182 static void _show_auth_failed_dialog (GtkWindow *parent, const gchar *message, gboolean auto_retry);
183 
184 static void _show_auth_failed_dialog_and_retry (GtkWindow *parent, const gchar *message);
185 
186 static void _data_fraction_sent_cb (FspSession *session, gdouble fraction, gpointer data);
187 
188 static void _auth_failed_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data);
189 
190 static void _get_auth_url_cb (GObject *obj, GAsyncResult *res, gpointer data);
191 
192 static void _complete_auth_cb (GObject *object, GAsyncResult *result, gpointer data);
193 
194 static void _exchange_token_cb (GObject *object, GAsyncResult *result, gpointer data);
195 
196 static gboolean _cancel_authorization_on_timeout (gpointer data);
197 
198 static gboolean _should_retry_operation (GError *error, gint attempts);
199 
200 static void _invalidate_extra_data (FrogrController *self);
201 
202 static void _update_upload_progress (FrogrController *self, UploadPicturesData *up_data);
203 
204 static void _upload_next_picture (FrogrController *self, UploadPicturesData *up_data);
205 
206 static void _upload_picture (FrogrController *self, FrogrPicture *picture, UploadPicturesData *up_data);
207 
208 static void _upload_picture_cb (GObject *object, GAsyncResult *res, gpointer data);
209 
210 static void _finish_upload_one_picture_process (FrogrController *self, UploadOnePictureData *uop_data);
211 
212 static void _finish_upload_pictures_process (FrogrController *self, UploadPicturesData *up_data);
213 
214 static void _perform_after_upload_operations (FrogrController *controller, UploadOnePictureData *uop_data);
215 
216 static void _set_license_cb (GObject *object, GAsyncResult *res, gpointer data);
217 
218 static void _set_license_for_picture (FrogrController *self, UploadOnePictureData *uop_data);
219 
220 static void _set_location_cb (GObject *object, GAsyncResult *res, gpointer data);
221 
222 static void _set_location_for_picture (FrogrController *self, UploadOnePictureData *uop_data);
223 
224 static void _set_replace_date_posted_cb (GObject *object, GAsyncResult *res, gpointer data);
225 
226 static void _set_replace_date_posted_for_picture (FrogrController *self, UploadOnePictureData *uop_data);
227 
228 static gboolean _add_picture_to_photosets_or_create (FrogrController *self, UploadOnePictureData *uop_data);
229 
230 static void _create_photoset_for_picture (FrogrController *self, UploadOnePictureData *uop_data);
231 
232 static void _create_photoset_cb (GObject *object, GAsyncResult *res, gpointer data);
233 
234 static void _add_picture_to_photoset (FrogrController *self, UploadOnePictureData *uop_data);
235 
236 static void _add_to_photoset_cb (GObject *object, GAsyncResult *res, gpointer data);
237 
238 static gboolean _add_picture_to_groups (FrogrController *self, UploadOnePictureData *uop_data);
239 
240 static void _add_picture_to_group (FrogrController *self, UploadOnePictureData *uop_data);
241 
242 static void _add_to_group_cb (GObject *object, GAsyncResult *res, gpointer data);
243 
244 static gboolean _complete_picture_upload (gpointer data);
245 
246 static void _on_file_loaded (FrogrFileLoader *loader, FrogrPicture *picture, FrogrController *self);
247 
248 static void _on_files_loaded (FrogrFileLoader *loader, FrogrController *self);
249 
250 static void _on_model_deserialized (FrogrModel *model, FrogrController *self);
251 
252 static void _fetch_everything (FrogrController *self, gboolean force_extra_data);
253 
254 static void _fetch_account_info (FrogrController *self);
255 
256 static void _fetch_account_info_finish (FrogrController *self, FetchAccountInfoData *data);
257 
258 static void _fetch_account_basic_info (FrogrController *self, FetchAccountInfoData *data);
259 
260 static void _fetch_account_basic_info_cb (GObject *object, GAsyncResult *res, FetchAccountInfoData *data);
261 
262 static void _fetch_account_upload_status (FrogrController *self, FetchAccountInfoData *data);
263 
264 static void _fetch_account_upload_status_cb (GObject *object, GAsyncResult *res, FetchAccountInfoData *data);
265 
266 static void _fetch_extra_data (FrogrController *self, gboolean force);
267 
268 static void _fetch_photosets (FrogrController *self);
269 
270 static void _fetch_photosets_cb (GObject *object, GAsyncResult *res, gpointer data);
271 
272 static void _fetch_groups (FrogrController *self);
273 
274 static void _fetch_groups_cb (GObject *object, GAsyncResult *res, gpointer data);
275 
276 static void _fetch_tags (FrogrController *self);
277 
278 static void _fetch_tags_cb (GObject *object, GAsyncResult *res, gpointer data);
279 
280 static void _dispose_slist_of_objects (GSList *objects);
281 
282 static gboolean _show_progress_on_idle (gpointer data);
283 
284 static gboolean _show_details_dialog_on_idle (GSList *pictures);
285 
286 static gboolean _show_add_tags_dialog_on_idle (GSList *pictures);
287 
288 static gboolean _show_create_new_set_dialog_on_idle (GSList *pictures);
289 
290 static gboolean _show_add_to_set_dialog_on_idle (GSList *pictures);
291 
292 static gboolean _show_add_to_group_dialog_on_idle (GSList *pictures);
293 
294 static gboolean _is_modal_dialog_about_to_be_shown  (FrogrController *self);
295 
296 /* Private functions */
297 
298 static gboolean
_load_pictures_on_idle(gpointer data)299 _load_pictures_on_idle (gpointer data)
300 {
301   FrogrController *fcontroller = NULL;
302   GSList *fileuris = NULL;
303 
304   g_return_val_if_fail (data, FALSE);
305 
306   fcontroller = frogr_controller_get_instance ();
307   fileuris = (GSList *)data;
308 
309   frogr_controller_load_pictures (fcontroller, fileuris);
310   return G_SOURCE_REMOVE;
311 }
312 
313 static gboolean
_load_project_file_on_idle(gpointer data)314 _load_project_file_on_idle (gpointer data)
315 {
316   FrogrController *fcontroller = NULL;
317   g_autofree gchar *filepath = NULL;
318 
319   g_return_val_if_fail (data, FALSE);
320 
321   fcontroller = frogr_controller_get_instance ();
322   filepath = (gchar *)data;
323 
324   frogr_controller_open_project_from_file (fcontroller, filepath);
325 
326   return G_SOURCE_REMOVE;
327 }
328 
329 static void
_g_application_startup_cb(GApplication * app,gpointer data)330 _g_application_startup_cb (GApplication *app, gpointer data)
331 {
332   FrogrController *self = FROGR_CONTROLLER (data);
333   FrogrModel *model = NULL;
334   FrogrAccount *account = NULL;
335   gboolean use_dark_theme;
336 
337   DEBUG ("%s", "Application started!\n");
338 
339   /* Create UI window */
340   self->mainview = frogr_main_view_new (GTK_APPLICATION (app));
341   g_object_add_weak_pointer (G_OBJECT (self->mainview),
342                              (gpointer) & self->mainview);
343   /* Connect to signals */
344   model = frogr_main_view_get_model (self->mainview);
345   g_signal_connect (G_OBJECT (model), "model-deserialized",
346                     G_CALLBACK (_on_model_deserialized),
347                     self);
348 
349   /* Start on idle state */
350   _set_state (self, FROGR_STATE_IDLE);
351 
352   /* Select the dark theme if needed */
353   use_dark_theme = frogr_config_get_use_dark_theme (self->config);
354   frogr_controller_set_use_dark_theme (self, use_dark_theme);
355 
356   /* Select the right account */
357   account = frogr_config_get_active_account (self->config);
358   if (account)
359     _set_active_account (self, account);
360 
361   /* Set HTTP proxy if needed */
362   if (frogr_config_get_use_proxy (self->config))
363     {
364       const gchar *host = frogr_config_get_proxy_host (self->config);
365       const gchar *port = frogr_config_get_proxy_port (self->config);
366       const gchar *username = frogr_config_get_proxy_username (self->config);
367       const gchar *password = frogr_config_get_proxy_password (self->config);
368       frogr_controller_set_proxy (self, FALSE, host, port, username, password);
369     }
370 }
371 
372 static void
_g_application_activate_cb(GApplication * app,gpointer data)373 _g_application_activate_cb (GApplication *app, gpointer data)
374 {
375   FrogrController *self = FROGR_CONTROLLER (data);
376 
377   DEBUG ("%s", "Application activated!\n");
378 
379   /* Show the UI */
380   gtk_widget_show (GTK_WIDGET(self->mainview));
381   gtk_window_present (GTK_WINDOW (self->mainview));
382 }
383 
384 static void
_g_application_open_files_cb(GApplication * app,GFile ** files,gint n_files,gchar * hint,gpointer data)385 _g_application_open_files_cb (GApplication *app, GFile **files, gint n_files, gchar *hint, gpointer data)
386 {
387   FrogrController *self = FROGR_CONTROLLER (data);
388   g_autoptr(GFileInfo) file_info = NULL;
389   gboolean is_project_file = FALSE;
390 
391   DEBUG ("Trying to open %d files\n", n_files);
392 
393   /* Check the first file's MIME type to check whether we are loading
394      media files (pictures, videos) or project files (text files). */
395   file_info = g_file_query_info (files[0],
396                                  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
397                                  G_FILE_QUERY_INFO_NONE,
398                                  NULL,
399                                  NULL);
400   if (file_info)
401     {
402       const gchar *mime_type = NULL;
403 
404       /* Thus, if the file being opened is a text file, we will assume
405          is a project file, since the other kind of files frogr can
406          handle two type of files: media files (pictures and videos),
407          normally with MIME types 'image' and 'video' */
408       mime_type = g_file_info_get_content_type (file_info);
409       is_project_file = g_str_has_prefix (mime_type, "text");
410     }
411 
412   if (is_project_file)
413     {
414       gchar *filepath = NULL;
415 
416       /* Assume the first file is a project file and ignore the rest */
417       filepath = g_strdup (g_file_get_path (files[0]));
418       gdk_threads_add_idle (_load_project_file_on_idle, filepath);
419     }
420   else
421     {
422       GSList *fileuris = NULL;
423       gchar *fileuri = NULL;
424       int i = 0;
425 
426       /* Load media files otherwise */
427       for (i = 0; i < n_files; i++)
428         {
429           fileuri = g_strdup (g_file_get_uri (files[i]));
430           if (fileuri)
431             fileuris = g_slist_append (fileuris, fileuri);
432         }
433 
434       if (fileuris)
435         gdk_threads_add_idle (_load_pictures_on_idle, fileuris);
436     }
437 
438   /* Show the UI */
439   gtk_widget_show (GTK_WIDGET(self->mainview));
440   gtk_window_present (GTK_WINDOW (self->mainview));
441 }
442 
443 static void
_g_application_shutdown_cb(GApplication * app,gpointer data)444 _g_application_shutdown_cb (GApplication *app, gpointer data)
445 {
446   FrogrController *self = FROGR_CONTROLLER (data);
447 
448   DEBUG ("%s", "Shutting down application...");
449 
450   if (self->app_running)
451     {
452       while (gtk_events_pending ())
453         gtk_main_iteration ();
454 
455       gtk_widget_destroy (GTK_WIDGET (self->mainview));
456       self->app_running = FALSE;
457 
458       frogr_config_save_all (self->config);
459     }
460 }
461 
462 static void
_set_active_account(FrogrController * self,FrogrAccount * account)463 _set_active_account (FrogrController *self, FrogrAccount *account)
464 {
465   FrogrAccount *new_account = NULL;
466   gboolean accounts_changed = FALSE;
467   const gchar *token = NULL;
468   const gchar *token_secret = NULL;
469   const gchar *account_version = NULL;
470 
471   g_return_if_fail(FROGR_IS_CONTROLLER (self));
472 
473   new_account = FROGR_IS_ACCOUNT (account) ? g_object_ref (account) : NULL;
474   if (new_account)
475     {
476       const gchar *new_username = NULL;
477 
478       new_username = frogr_account_get_username (new_account);
479       if (!frogr_config_set_active_account (self->config, new_username))
480         {
481           /* Fallback to manually creating a new account */
482           frogr_account_set_is_active (new_account, TRUE);
483           accounts_changed = frogr_config_add_account (self->config, new_account);
484         }
485 
486       /* Get the token for setting it later on */
487       token = frogr_account_get_token (new_account);
488       token_secret = frogr_account_get_token_secret (new_account);
489     }
490   else if (FROGR_IS_ACCOUNT (self->account))
491     {
492       /* If NULL is passed it means 'delete current account' */
493       const gchar *username = frogr_account_get_username (self->account);
494       accounts_changed = frogr_config_remove_account (self->config, username);
495     }
496 
497   /* Update internal pointer in the controller */
498   if (self->account)
499     g_object_unref (self->account);
500   self->account = new_account;
501 
502   /* Update token in the session */
503   fsp_session_set_token (self->session, token);
504   fsp_session_set_token_secret (self->session, token_secret);
505 
506   /* Fetch needed info for this account or update tokens stored */
507   account_version = new_account ? frogr_account_get_version (new_account) : NULL;
508   if (account_version && g_strcmp0 (account_version, ACCOUNTS_CURRENT_VERSION))
509     {
510       self->fetching_token_replacement = TRUE;
511       fsp_session_exchange_token (self->session, NULL, _exchange_token_cb, self);
512       gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_TOKEN_REPLACEMENT));
513 
514       /* Make sure we show proper feedback if connection is too slow */
515       gdk_threads_add_timeout (MAX_AUTH_TIMEOUT, (GSourceFunc) _cancel_authorization_on_timeout, self);
516     }
517   else
518     {
519       /* If a new account has been activated, fetch everything */
520       if (new_account)
521         _fetch_everything (self, TRUE);
522 
523       /* Emit proper signals */
524       g_signal_emit (self, signals[ACTIVE_ACCOUNT_CHANGED], 0, new_account);
525       if (accounts_changed)
526         g_signal_emit (self, signals[ACCOUNTS_CHANGED], 0);
527     }
528 
529   /* Save new state in configuration */
530   frogr_config_save_accounts (self->config);
531 }
532 
533 void
_set_state(FrogrController * self,FrogrControllerState state)534 _set_state (FrogrController *self, FrogrControllerState state)
535 {
536   self->state = state;
537   g_signal_emit (self, signals[STATE_CHANGED], 0, state);
538 }
539 
540 static GCancellable *
_register_new_cancellable(FrogrController * self)541 _register_new_cancellable (FrogrController *self)
542 {
543   GCancellable *cancellable = NULL;
544   cancellable = g_cancellable_new();
545   self->cancellables = g_list_prepend (self->cancellables, cancellable);
546 
547   return cancellable;
548 }
549 
550 static void
_clear_cancellable(FrogrController * self,GCancellable * cancellable)551 _clear_cancellable (FrogrController *self, GCancellable *cancellable)
552 {
553   GList *item = NULL;
554 
555   item = g_list_find (self->cancellables, cancellable);
556   if (item)
557     {
558       g_object_unref (G_OBJECT (item->data));
559       self->cancellables = g_list_delete_link (self->cancellables, item);
560     }
561 }
562 
563 static void
_handle_flicksoup_error(FrogrController * self,GError * error,gboolean notify_user)564 _handle_flicksoup_error (FrogrController *self, GError *error, gboolean notify_user)
565 {
566   void (* error_function) (GtkWindow *, const gchar *) = NULL;
567   g_autofree gchar *msg = NULL;
568   g_autofree gchar *video_quota_msg = NULL;
569   gint n_videos = 0;
570 
571   error_function = frogr_util_show_error_dialog;
572   switch (error->code)
573     {
574     case FSP_ERROR_CANCELLED:
575       msg = g_strdup (_("Process cancelled"));
576       error_function = NULL; /* Don't notify the user about this */
577       break;
578 
579     case FSP_ERROR_NETWORK_ERROR:
580       msg = g_strdup (_("Connection error:\nNetwork not available"));
581       break;
582 
583     case FSP_ERROR_CLIENT_ERROR:
584       msg = g_strdup (_("Connection error:\nBad request"));
585       break;
586 
587     case FSP_ERROR_SERVER_ERROR:
588       msg = g_strdup (_("Connection error:\nServer-side error"));
589       break;
590 
591     case FSP_ERROR_UPLOAD_INVALID_FILE:
592       msg = g_strdup (_("Error uploading:\nFile invalid"));
593       break;
594 
595     case FSP_ERROR_UPLOAD_QUOTA_PICTURE_EXCEEDED:
596       msg = g_strdup (_("Error uploading picture:\nQuota exceeded"));
597       break;
598 
599     case FSP_ERROR_UPLOAD_QUOTA_VIDEO_EXCEEDED:
600       n_videos = frogr_account_get_current_videos (self->account);
601       video_quota_msg = g_strdup_printf (ngettext ("Quota exceeded (limit: %d video per month)",
602                                                    "Quota exceeded (limit: %d videos per month)", n_videos),
603                                          n_videos);
604       msg = g_strdup_printf ("%s\n%s",
605                              _("Error uploading video:\nYou can't upload more videos with this account"),
606                              video_quota_msg);
607       break;
608 
609     case FSP_ERROR_PHOTO_NOT_FOUND:
610       msg = g_strdup (_("Error:\nPhoto not found"));
611       break;
612 
613     case FSP_ERROR_PHOTOSET_PHOTO_ALREADY_IN:
614       msg = g_strdup (_("Error:\nPhoto already in photoset"));
615       break;
616 
617     case FSP_ERROR_GROUP_PHOTO_ALREADY_IN:
618       msg = g_strdup (_("Error:\nPhoto already in group"));
619       break;
620 
621     case FSP_ERROR_GROUP_PHOTO_IN_MAX_NUM:
622       msg = g_strdup (_("Error:\nPhoto already in the maximum number of groups possible"));
623       break;
624 
625     case FSP_ERROR_GROUP_LIMIT_REACHED:
626       msg = g_strdup (_("Error:\nGroup limit already reached"));
627       break;
628 
629     case FSP_ERROR_GROUP_PHOTO_ADDED_TO_QUEUE:
630       msg = g_strdup (_("Error:\nPhoto added to group's queue"));
631       break;
632 
633     case FSP_ERROR_GROUP_PHOTO_ALREADY_IN_QUEUE:
634       msg = g_strdup (_("Error:\nPhoto already added to group's queue"));
635       break;
636 
637     case FSP_ERROR_GROUP_CONTENT_NOT_ALLOWED:
638       msg = g_strdup (_("Error:\nContent not allowed for this group"));
639       break;
640 
641     case FSP_ERROR_AUTHENTICATION_FAILED:
642       msg = g_strdup_printf (_("Authorization failed.\nPlease try again"));
643       error_function = _show_auth_failed_dialog_and_retry;
644       break;
645 
646     case FSP_ERROR_NOT_AUTHENTICATED:
647       frogr_controller_revoke_authorization (self);
648       msg = g_strdup_printf (_("Error\n%s is not properly authorized to upload pictures "
649                                "to Flickr.\nPlease re-authorize it"), APP_SHORTNAME);
650       break;
651 
652     case FSP_ERROR_OAUTH_UNKNOWN_ERROR:
653       msg = g_strdup_printf (_("Unable to authenticate in Flickr\nPlease try again."));
654       break;
655 
656     case FSP_ERROR_OAUTH_NOT_AUTHORIZED_YET:
657       msg = g_strdup_printf (_("You have not properly authorized %s yet.\n"
658                                "Please try again."), APP_SHORTNAME);
659       break;
660 
661     case FSP_ERROR_OAUTH_VERIFIER_INVALID:
662       msg = g_strdup_printf (_("Invalid verification code.\nPlease try again."));
663       break;
664 
665     case FSP_ERROR_SERVICE_UNAVAILABLE:
666       msg = g_strdup_printf (_("Error:\nService not available"));
667       break;
668 
669     default:
670       /* General error: just dump the raw error description */
671       msg = g_strdup_printf (_("An error happened: %s."), error->message);
672     }
673 
674   if (notify_user && error_function)
675     error_function (GTK_WINDOW (self->mainview), msg);
676 
677   DEBUG ("%s", msg);
678 }
679 
680 static void
_show_auth_failed_dialog(GtkWindow * parent,const gchar * message,gboolean auto_retry)681 _show_auth_failed_dialog (GtkWindow *parent, const gchar *message, gboolean auto_retry)
682 {
683   GtkWidget *dialog = NULL;
684 
685   dialog = gtk_message_dialog_new (parent,
686                                    GTK_DIALOG_MODAL,
687                                    GTK_MESSAGE_ERROR,
688                                    GTK_BUTTONS_CLOSE,
689                                    "%s", message);
690   gtk_window_set_title (GTK_WINDOW (dialog), APP_SHORTNAME);
691 
692   g_signal_connect (G_OBJECT (dialog), "response",
693                     G_CALLBACK (_auth_failed_dialog_response_cb),
694                     GINT_TO_POINTER ((gint)auto_retry));
695 
696   gtk_widget_show (dialog);
697 }
698 
699 static void
_show_auth_failed_dialog_and_retry(GtkWindow * parent,const gchar * message)700 _show_auth_failed_dialog_and_retry (GtkWindow *parent, const gchar *message)
701 {
702   _show_auth_failed_dialog (parent, message, TRUE);
703 }
704 
705 static void
_data_fraction_sent_cb(FspSession * session,gdouble fraction,gpointer data)706 _data_fraction_sent_cb (FspSession *session, gdouble fraction, gpointer data)
707 {
708   FrogrController *self = NULL;
709   self = FROGR_CONTROLLER(data);
710   frogr_main_view_set_progress_status_fraction (self->mainview, fraction);
711 }
712 
713 static void
_auth_failed_dialog_response_cb(GtkDialog * dialog,gint response,gpointer data)714 _auth_failed_dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
715 {
716   gboolean auto_retry = (gboolean)GPOINTER_TO_INT (data);
717   if (auto_retry && response == GTK_RESPONSE_CLOSE)
718     {
719       frogr_controller_show_auth_dialog (frogr_controller_get_instance ());
720       DEBUG ("%s", "Showing the authorization dialog once again...");
721     }
722 
723   gtk_widget_destroy (GTK_WIDGET (dialog));
724 }
725 
726 static void
_get_auth_url_cb(GObject * obj,GAsyncResult * res,gpointer data)727 _get_auth_url_cb (GObject *obj, GAsyncResult *res, gpointer data)
728 {
729   FspSession *session = NULL;
730   CancellableOperationData *co_data = NULL;
731   FrogrController *self = NULL;
732   g_autoptr(GError) error = NULL;
733   g_autofree gchar *auth_url = NULL;
734 
735   session = FSP_SESSION (obj);
736   co_data = (CancellableOperationData*) data;
737   self = co_data->controller;
738 
739   auth_url = fsp_session_get_auth_url_finish (session, res, &error);
740   if (auth_url != NULL && error == NULL)
741     {
742       g_autofree gchar *url_with_permissions = NULL;
743 
744       url_with_permissions = g_strdup_printf ("%s&perms=write", auth_url);
745       frogr_util_open_uri (url_with_permissions);
746 
747       /* Run the auth confirmation dialog */
748       frogr_auth_dialog_show (GTK_WINDOW (self->mainview), CONFIRM_AUTHORIZATION);
749 
750       DEBUG ("Auth URL: %s", url_with_permissions);
751     }
752 
753   if (error != NULL)
754     {
755       _handle_flicksoup_error (self, error, TRUE);
756       DEBUG ("Error getting auth URL: %s", error->message);
757     }
758 
759   frogr_main_view_hide_progress (self->mainview);
760 
761   _clear_cancellable (self, co_data->cancellable);
762   g_slice_free (CancellableOperationData, co_data);
763 
764   self->fetching_auth_url = FALSE;
765 }
766 
767 static void
_complete_auth_cb(GObject * object,GAsyncResult * result,gpointer data)768 _complete_auth_cb (GObject *object, GAsyncResult *result, gpointer data)
769 {
770   FspSession *session = NULL;
771   CancellableOperationData *co_data = NULL;
772   FrogrController *controller = NULL;
773   FspDataAuthToken *auth_token = NULL;
774   g_autoptr(GError) error = NULL;
775 
776   session = FSP_SESSION (object);
777   co_data = (CancellableOperationData*) data;
778   controller = co_data->controller;
779 
780   auth_token = fsp_session_complete_auth_finish (session, result, &error);
781   if (auth_token)
782     {
783       if (auth_token->token)
784         {
785           FrogrAccount *account = NULL;
786 
787           /* Set and save the auth token and the settings to disk */
788           account = frogr_account_new_full (auth_token->token, auth_token->token_secret);
789           frogr_account_set_id (account, auth_token->nsid);
790           frogr_account_set_username (account, auth_token->username);
791           frogr_account_set_fullname (account, auth_token->fullname);
792 
793           /* Frogr always always ask for 'write' permissions at the moment */
794           frogr_account_set_permissions (account, "write");
795 
796           /* Try to set the active account again */
797           _set_active_account (controller, account);
798 
799           DEBUG ("%s", "Authorization successfully completed!");
800         }
801 
802       fsp_data_free (FSP_DATA (auth_token));
803     }
804 
805   if (error != NULL)
806     {
807       _handle_flicksoup_error (controller, error, TRUE);
808       DEBUG ("Authorization failed: %s", error->message);
809     }
810 
811   frogr_main_view_hide_progress (controller->mainview);
812 
813   _clear_cancellable (controller, co_data->cancellable);
814   g_slice_free (CancellableOperationData, co_data);
815 
816   controller->fetching_auth_token = FALSE;
817 }
818 
819 static void
_exchange_token_cb(GObject * object,GAsyncResult * result,gpointer data)820 _exchange_token_cb (GObject *object, GAsyncResult *result, gpointer data)
821 {
822   FspSession *session = NULL;
823   FrogrController *controller = NULL;
824   g_autoptr(GError) error = NULL;
825 
826   session = FSP_SESSION (object);
827   controller = FROGR_CONTROLLER (data);
828 
829   fsp_session_exchange_token_finish (session, result, &error);
830   if (error == NULL)
831     {
832       const gchar *token = NULL;
833       const gchar *token_secret = NULL;
834 
835       /* If everything went fine, get the token and secret from the
836          session and update the current user account */
837       token = fsp_session_get_token (controller->session);
838       frogr_account_set_token (controller->account, token);
839 
840       token_secret = fsp_session_get_token_secret (controller->session);
841       frogr_account_set_token_secret (controller->account, token_secret);
842 
843       /* Make sure we update the version for the account too */
844       frogr_account_set_version (controller->account, ACCOUNTS_CURRENT_VERSION);
845 
846       /* Finally, try to set the active account again */
847       _set_active_account (controller, controller->account);
848     }
849   else
850     {
851       _handle_flicksoup_error (controller, error, TRUE);
852       DEBUG ("Authorization failed: %s", error->message);
853     }
854 
855   frogr_main_view_hide_progress (controller->mainview);
856   controller->fetching_token_replacement = FALSE;
857 }
858 
859 static gboolean
_cancel_authorization_on_timeout(gpointer data)860 _cancel_authorization_on_timeout (gpointer data)
861 {
862   FrogrController *self = FROGR_CONTROLLER (data);
863 
864   if (self->fetching_auth_url || self->fetching_auth_token || self->fetching_token_replacement)
865     {
866       frogr_controller_cancel_ongoing_requests (self);
867       frogr_main_view_hide_progress (self->mainview);
868 
869       _show_auth_failed_dialog (GTK_WINDOW (self->mainview), _("Authorization failed (timed out)"), FALSE);
870     }
871 
872   return G_SOURCE_REMOVE;
873 }
874 
875 static gboolean
_should_retry_operation(GError * error,gint attempts)876 _should_retry_operation (GError *error, gint attempts)
877 {
878   if (error->code == FSP_ERROR_CANCELLED
879       || error->code == FSP_ERROR_UPLOAD_INVALID_FILE
880       || error->code == FSP_ERROR_UPLOAD_QUOTA_PICTURE_EXCEEDED
881       || error->code == FSP_ERROR_UPLOAD_QUOTA_VIDEO_EXCEEDED
882       || error->code == FSP_ERROR_OAUTH_NOT_AUTHORIZED_YET
883       || error->code == FSP_ERROR_NOT_AUTHENTICATED
884       || error->code == FSP_ERROR_NOT_ENOUGH_PERMISSIONS
885       || error->code == FSP_ERROR_INVALID_API_KEY)
886     {
887       /* We are pretty sure we don't want to retry in these cases */
888       return FALSE;
889     }
890 
891   return attempts < MAX_ATTEMPTS;
892 }
893 
894 static void
_invalidate_extra_data(FrogrController * self)895 _invalidate_extra_data (FrogrController *self)
896 {
897   /* Just reset the flags */
898   self->photosets_fetched = FALSE;
899   self->groups_fetched = FALSE;
900   self->tags_fetched = FALSE;
901 }
902 
903 static void
_update_upload_progress(FrogrController * self,UploadPicturesData * up_data)904 _update_upload_progress (FrogrController *self, UploadPicturesData *up_data)
905 {
906   g_autofree gchar *description = NULL;
907   g_autofree gchar *status_text = NULL;
908 
909   if (up_data->current)
910     {
911       FrogrPicture *picture = FROGR_PICTURE (up_data->current->data);
912       g_autofree gchar *title = g_strdup (frogr_picture_get_title (picture));
913 
914       /* Update progress */
915       if (up_data->upload_attempts > 0)
916         {
917           description = g_strdup_printf (_("Retrying Upload (attempt %d/%d)…"),
918                                          (up_data->upload_attempts), MAX_ATTEMPTS);
919         }
920       else
921         {
922           description = g_strdup_printf (_("Uploading '%s'…"), title);
923         }
924       status_text = g_strdup_printf ("%d / %d", up_data->index, up_data->n_pictures);
925     }
926   frogr_main_view_set_progress_description(self->mainview, description);
927   frogr_main_view_set_progress_status_text (self->mainview, status_text);
928 }
929 
930 static void
_upload_next_picture(FrogrController * self,UploadPicturesData * up_data)931 _upload_next_picture (FrogrController *self, UploadPicturesData *up_data)
932 {
933   /* Advance the list only if not in the first element */
934   if (up_data->index > 0)
935     up_data->current = g_slist_next(up_data->current);
936 
937   if (up_data->current)
938     {
939       FrogrPicture *picture = FROGR_PICTURE (up_data->current->data);
940 
941       up_data->index++;
942       up_data->upload_attempts = 0;
943       up_data->error = NULL;
944 
945       _update_upload_progress (self, up_data);
946       _upload_picture (self, picture, up_data);
947     }
948   else
949     {
950       /* Hide progress bar dialog and finish */
951       frogr_main_view_hide_progress (self->mainview);
952       _finish_upload_pictures_process (self, up_data);
953     }
954 }
955 
956 static void
_upload_picture(FrogrController * self,FrogrPicture * picture,UploadPicturesData * up_data)957 _upload_picture (FrogrController *self, FrogrPicture *picture, UploadPicturesData *up_data)
958 {
959   UploadOnePictureData *uop_data = NULL;
960   FspVisibility public_visibility = FSP_VISIBILITY_NONE;
961   FspVisibility family_visibility = FSP_VISIBILITY_NONE;
962   FspVisibility friend_visibility = FSP_VISIBILITY_NONE;
963   FspSafetyLevel safety_level = FSP_SAFETY_LEVEL_NONE;
964   FspContentType content_type = FSP_CONTENT_TYPE_NONE;
965   FspSearchScope search_scope = FSP_SEARCH_SCOPE_NONE;
966 
967   g_return_if_fail(FROGR_IS_CONTROLLER (self));
968   g_return_if_fail(FROGR_IS_PICTURE (picture));
969 
970   uop_data = g_slice_new0 (UploadOnePictureData);
971   uop_data->controller = self;
972   uop_data->picture = picture;
973   uop_data->photosets = NULL;
974   uop_data->groups = NULL;
975   uop_data->cancellable = _register_new_cancellable (self);
976   uop_data->up_data = up_data;
977 
978   g_object_ref (picture);
979 
980   public_visibility = frogr_picture_is_public (picture) ? FSP_VISIBILITY_YES : FSP_VISIBILITY_NO;
981   family_visibility = frogr_picture_is_family (picture) ? FSP_VISIBILITY_YES : FSP_VISIBILITY_NO;
982   friend_visibility = frogr_picture_is_friend (picture) ? FSP_VISIBILITY_YES : FSP_VISIBILITY_NO;
983   safety_level = frogr_picture_get_safety_level (picture);
984   content_type = frogr_picture_get_content_type (picture);
985   search_scope = frogr_picture_show_in_search (picture) ? FSP_SEARCH_SCOPE_PUBLIC : FSP_SEARCH_SCOPE_HIDDEN;
986 
987   /* Connect to this signal to report progress to the user */
988   g_signal_connect (G_OBJECT (self->session), "data-fraction-sent",
989                     G_CALLBACK (_data_fraction_sent_cb),
990                     self);
991 
992   fsp_session_upload (self->session,
993                       frogr_picture_get_fileuri (picture),
994                       frogr_picture_get_title (picture),
995                       frogr_picture_get_description (picture),
996                       frogr_picture_get_tags (picture),
997                       public_visibility,
998                       family_visibility,
999                       friend_visibility,
1000                       safety_level,
1001                       content_type,
1002                       search_scope,
1003                       uop_data->cancellable,
1004                       _upload_picture_cb, uop_data);
1005 }
1006 
1007 static void
_upload_picture_cb(GObject * object,GAsyncResult * res,gpointer data)1008 _upload_picture_cb (GObject *object, GAsyncResult *res, gpointer data)
1009 {
1010   FspSession *session = NULL;
1011   UploadOnePictureData *uop_data = NULL;
1012   UploadPicturesData *up_data = NULL;
1013   FrogrController *controller = NULL;
1014   FrogrPicture *picture = NULL;
1015   g_autofree gchar *photo_id = NULL;
1016   GError *error = NULL;
1017 
1018   session = FSP_SESSION (object);
1019   uop_data = (UploadOnePictureData*) data;
1020   controller = uop_data->controller;
1021   picture = uop_data->picture;
1022 
1023   photo_id = fsp_session_upload_finish (session, res, &error);
1024   if (photo_id)
1025     frogr_picture_set_id (picture, photo_id);
1026 
1027   /* Stop reporting to the user */
1028   g_signal_handlers_disconnect_by_func (controller->session, _data_fraction_sent_cb, controller);
1029 
1030   up_data = uop_data->up_data;
1031   if (g_cancellable_is_cancelled (uop_data->cancellable)) {
1032     _complete_picture_upload (uop_data);
1033     return;
1034   }
1035 
1036   if (error && _should_retry_operation (error, up_data->upload_attempts))
1037     {
1038       up_data->upload_attempts++;
1039       _update_upload_progress (controller, up_data);
1040 
1041       DEBUG("Error uploading picture %s: %s. Retrying... (attempt %d / %d)",
1042             frogr_picture_get_title (picture), error->message,
1043             up_data->upload_attempts, MAX_ATTEMPTS);
1044       g_error_free (error);
1045 
1046       /* Try again! */
1047       _finish_upload_one_picture_process (controller, uop_data);
1048       _upload_picture (controller, picture, up_data);
1049     }
1050   else
1051     {
1052       if (!error)
1053         _perform_after_upload_operations (controller, uop_data);
1054       else
1055         {
1056           DEBUG ("Error uploading picture %s: %s",
1057                  frogr_picture_get_title (picture), error->message);
1058           uop_data->up_data->error = error;
1059         }
1060 
1061       /* Complete the upload process when possible */
1062       gdk_threads_add_timeout (DEFAULT_TIMEOUT, _complete_picture_upload, uop_data);
1063     }
1064 }
1065 
1066 static void
_finish_upload_one_picture_process(FrogrController * self,UploadOnePictureData * uop_data)1067 _finish_upload_one_picture_process (FrogrController *self, UploadOnePictureData *uop_data)
1068 {
1069   g_object_unref (uop_data->picture);
1070   if (uop_data->cancellable)
1071     _clear_cancellable (self, uop_data->cancellable);
1072   g_slice_free (UploadOnePictureData, uop_data);
1073 }
1074 
1075 static void
_finish_upload_pictures_process(FrogrController * self,UploadPicturesData * up_data)1076 _finish_upload_pictures_process (FrogrController *self, UploadPicturesData *up_data)
1077 {
1078   if (!up_data->error)
1079     {
1080       /* Fetch sets and tags (if needed) right after finishing */
1081       _fetch_photosets (self);
1082       _fetch_tags (self);
1083 
1084       DEBUG ("%s", "Finished uploading pictures!");
1085     }
1086   else
1087     {
1088       _handle_flicksoup_error (self, up_data->error, TRUE);
1089       DEBUG ("Error uploading pictures: %s", up_data->error->message);
1090       g_error_free (up_data->error);
1091     }
1092 
1093   /* Change state and clean up */
1094   _set_state (self, FROGR_STATE_IDLE);
1095 
1096   if (up_data->pictures)
1097     g_slist_free_full (up_data->pictures, g_object_unref);
1098 
1099   g_slice_free (UploadPicturesData, up_data);
1100 }
1101 
1102 static void
_perform_after_upload_operations(FrogrController * controller,UploadOnePictureData * uop_data)1103 _perform_after_upload_operations (FrogrController *controller, UploadOnePictureData *uop_data)
1104 {
1105   if (frogr_picture_get_license (uop_data->picture) != FSP_LICENSE_NONE)
1106     {
1107       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LICENSE] = 0;
1108       _set_license_for_picture (controller, uop_data);
1109     }
1110 
1111   if (frogr_picture_send_location (uop_data->picture)
1112       && frogr_picture_get_location (uop_data->picture) != NULL)
1113     {
1114       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LOCATION] = 0;
1115       _set_location_for_picture (controller, uop_data);
1116     }
1117 
1118   if (frogr_picture_replace_date_posted (uop_data->picture))
1119     {
1120       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_REPLACE_DATE_POSTED] = 0;
1121       _set_replace_date_posted_for_picture (controller, uop_data);
1122     }
1123 
1124   if (frogr_picture_get_photosets (uop_data->picture))
1125     {
1126       uop_data->photosets = frogr_picture_get_photosets (uop_data->picture);
1127       _add_picture_to_photosets_or_create (controller, uop_data);
1128     }
1129 
1130   if (frogr_picture_get_groups (uop_data->picture))
1131     {
1132       uop_data->groups = frogr_picture_get_groups (uop_data->picture);
1133       _add_picture_to_groups (controller, uop_data);
1134     }
1135 }
1136 
1137 static void
_set_license_cb(GObject * object,GAsyncResult * res,gpointer data)1138 _set_license_cb (GObject *object, GAsyncResult *res, gpointer data)
1139 {
1140   FspSession *session = NULL;
1141   UploadOnePictureData *uop_data = NULL;
1142   FrogrController *controller = NULL;
1143   GError *error = NULL;
1144 
1145   session = FSP_SESSION (object);
1146   uop_data = (UploadOnePictureData*) data;
1147   controller = uop_data->controller;
1148 
1149   fsp_session_set_license_finish (session, res, &error);
1150   if (error && _should_retry_operation (error, uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LICENSE]))
1151     {
1152       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LICENSE]++;
1153 
1154       DEBUG("Error setting license for picture %s: %s. Retrying... (attempt %d / %d)",
1155             frogr_picture_get_title (uop_data->picture),
1156             error->message,
1157             uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LICENSE],
1158             MAX_ATTEMPTS);
1159       g_error_free (error);
1160 
1161       /* Try again! */
1162       _set_license_for_picture (controller, uop_data);
1163     }
1164   else
1165     {
1166       if (error)
1167         {
1168           DEBUG ("Error setting license for picture: %s", error->message);
1169           uop_data->up_data->error = error;
1170         }
1171 
1172       controller->setting_license = FALSE;
1173     }
1174 }
1175 
1176 static void
_set_license_for_picture(FrogrController * self,UploadOnePictureData * uop_data)1177 _set_license_for_picture (FrogrController *self, UploadOnePictureData *uop_data)
1178 {
1179   FrogrPicture *picture = NULL;
1180   g_autofree gchar *debug_msg = NULL;
1181 
1182   self->setting_license = TRUE;
1183   picture = uop_data->picture;
1184 
1185   fsp_session_set_license (self->session,
1186                            frogr_picture_get_id (picture),
1187                            frogr_picture_get_license (picture),
1188                            uop_data->cancellable,
1189                            _set_license_cb,
1190                            uop_data);
1191 
1192   debug_msg = g_strdup_printf ("Setting license %d for picture %s…",
1193                                frogr_picture_get_license (picture),
1194                                frogr_picture_get_title (picture));
1195   DEBUG ("%s", debug_msg);
1196 }
1197 
1198 static void
_set_location_cb(GObject * object,GAsyncResult * res,gpointer data)1199 _set_location_cb (GObject *object, GAsyncResult *res, gpointer data)
1200 {
1201   FspSession *session = NULL;
1202   UploadOnePictureData *uop_data = NULL;
1203   FrogrController *controller = NULL;
1204   GError *error = NULL;
1205 
1206   session = FSP_SESSION (object);
1207   uop_data = (UploadOnePictureData*) data;
1208   controller = uop_data->controller;
1209 
1210   fsp_session_set_location_finish (session, res, &error);
1211   if (error && _should_retry_operation (error, uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LOCATION]))
1212     {
1213       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LOCATION]++;
1214 
1215       DEBUG("Error setting location for picture %s: %s. Retrying... (attempt %d / %d)",
1216             frogr_picture_get_title (uop_data->picture),
1217             error->message,
1218             uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_LOCATION],
1219             MAX_ATTEMPTS);
1220       g_error_free (error);
1221 
1222       _set_location_for_picture (controller, uop_data);
1223     }
1224   else
1225     {
1226       if (error)
1227         {
1228           DEBUG ("Error setting location for picture: %s", error->message);
1229           uop_data->up_data->error = error;
1230         }
1231 
1232       controller->setting_location = FALSE;
1233     }
1234 }
1235 
1236 static void
_set_location_for_picture(FrogrController * self,UploadOnePictureData * uop_data)1237 _set_location_for_picture (FrogrController *self, UploadOnePictureData *uop_data)
1238 {
1239   FrogrPicture *picture = NULL;
1240   FrogrLocation *location = NULL;
1241   FspDataLocation *data_location = NULL;
1242   g_autofree gchar *debug_msg = NULL;
1243 
1244   picture = uop_data->picture;
1245   location = frogr_picture_get_location (picture);
1246   data_location = FSP_DATA_LOCATION (fsp_data_new (FSP_LOCATION));
1247   data_location->latitude = frogr_location_get_latitude (location);
1248   data_location->longitude = frogr_location_get_longitude (location);
1249 
1250   self->setting_location = TRUE;
1251 
1252   fsp_session_set_location (self->session,
1253                             frogr_picture_get_id (picture),
1254                             data_location,
1255                             uop_data->cancellable,
1256                             _set_location_cb,
1257                             uop_data);
1258 
1259   location = frogr_picture_get_location (picture);
1260   debug_msg = g_strdup_printf ("Setting geolocation (%f, %f) for picture %s…",
1261                                frogr_location_get_latitude (location),
1262                                frogr_location_get_longitude (location),
1263                                frogr_picture_get_title (picture));
1264   DEBUG ("%s", debug_msg);
1265 
1266   fsp_data_free (FSP_DATA (data_location));
1267 }
1268 
1269 static void
_set_replace_date_posted_cb(GObject * object,GAsyncResult * res,gpointer data)1270 _set_replace_date_posted_cb (GObject *object, GAsyncResult *res, gpointer data)
1271 {
1272   FspSession *session = NULL;
1273   UploadOnePictureData *uop_data = NULL;
1274   FrogrController *controller = NULL;
1275   GError *error = NULL;
1276 
1277   session = FSP_SESSION (object);
1278   uop_data = (UploadOnePictureData*) data;
1279   controller = uop_data->controller;
1280 
1281   fsp_session_set_date_posted_finish (session, res, &error);
1282   if (error && _should_retry_operation (error, uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_REPLACE_DATE_POSTED]))
1283     {
1284       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_REPLACE_DATE_POSTED]++;
1285 
1286       DEBUG("Error replacing 'date posted' with 'date taken' for picture %s: %s. Retrying... (attempt %d / %d)",
1287             frogr_picture_get_title (uop_data->picture),
1288             error->message,
1289             uop_data->after_upload_attempts[AFTER_UPLOAD_OP_SETTING_REPLACE_DATE_POSTED],
1290             MAX_ATTEMPTS);
1291       g_error_free (error);
1292 
1293       _set_replace_date_posted_for_picture (controller, uop_data);
1294     }
1295   else
1296     {
1297       if (error)
1298         {
1299           DEBUG ("Error replacing 'date posted' with 'date taken' for picture: %s", error->message);
1300           uop_data->up_data->error = error;
1301         }
1302 
1303       controller->setting_replace_date_posted = FALSE;
1304     }
1305 }
1306 
1307 static void
_set_replace_date_posted_for_picture(FrogrController * self,UploadOnePictureData * uop_data)1308 _set_replace_date_posted_for_picture (FrogrController *self, UploadOnePictureData *uop_data)
1309 {
1310   FrogrPicture *picture = NULL;
1311   g_autoptr(GDateTime) picture_date = NULL;
1312   const gchar *picture_date_str = NULL;
1313   g_autofree gchar *debug_msg = NULL;
1314   gchar date_iso8601[20];
1315 
1316   picture = uop_data->picture;
1317   picture_date_str = frogr_picture_get_datetime (picture);
1318   if (!picture_date_str)
1319     return;
1320 
1321   if (g_strlcpy (date_iso8601, picture_date_str, 20) < 19)
1322     return;
1323 
1324   /* Exif's date time values stored in FrogrPicture are in the format
1325      '%Y:%m:%d %H:%M:%S', while iso8601 uses format '%Y-%m-%dT%H:%M:%S' */
1326   date_iso8601[10] = 'T';
1327   date_iso8601[4] = '-';
1328   date_iso8601[7] = '-';
1329   date_iso8601[19] = '\0';
1330 
1331   picture_date = g_date_time_new_from_iso8601 (date_iso8601, g_time_zone_new_utc ());
1332   if (!picture_date)
1333     return;
1334 
1335   self->setting_replace_date_posted = TRUE;
1336 
1337   fsp_session_set_date_posted (self->session,
1338                                frogr_picture_get_id (picture),
1339                                picture_date,
1340                                uop_data->cancellable,
1341                                _set_replace_date_posted_cb,
1342                                uop_data);
1343 
1344   debug_msg = g_strdup_printf ("Replacing 'date posted' with 'date taken' (%s) for picture %s…",
1345                                date_iso8601, frogr_picture_get_title (picture));
1346   DEBUG ("%s", debug_msg);
1347 }
1348 
1349 static gboolean
_add_picture_to_photosets_or_create(FrogrController * self,UploadOnePictureData * uop_data)1350 _add_picture_to_photosets_or_create (FrogrController *self, UploadOnePictureData *uop_data)
1351 {
1352   FrogrPhotoSet *set = NULL;
1353 
1354   if (g_slist_length (uop_data->photosets) == 0)
1355     return FALSE;
1356 
1357   self->adding_to_set = TRUE;
1358 
1359   uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET] = 0;
1360 
1361   set = FROGR_PHOTOSET (uop_data->photosets->data);
1362   if (frogr_photoset_is_local (set))
1363     _create_photoset_for_picture (self, uop_data);
1364   else
1365     _add_picture_to_photoset (self, uop_data);
1366 
1367   return TRUE;
1368 }
1369 
1370 static void
_create_photoset_for_picture(FrogrController * self,UploadOnePictureData * uop_data)1371 _create_photoset_for_picture (FrogrController *self, UploadOnePictureData *uop_data)
1372 {
1373   FrogrPicture *picture = NULL;
1374   FrogrPhotoSet *set = NULL;
1375   g_autofree gchar *debug_msg = NULL;
1376 
1377   picture = uop_data->picture;
1378   set = FROGR_PHOTOSET (uop_data->photosets->data);
1379 
1380   /* Set with ID: Create set along with this picture */
1381   fsp_session_create_photoset (self->session,
1382                                frogr_photoset_get_title (set),
1383                                frogr_photoset_get_description (set),
1384                                frogr_picture_get_id (picture),
1385                                uop_data->cancellable,
1386                                _create_photoset_cb,
1387                                uop_data);
1388 
1389   debug_msg = g_strdup_printf ("Creating new photoset for picture %s. "
1390                                "Title: %s / Description: %s",
1391                                frogr_picture_get_title (picture),
1392                                frogr_photoset_get_title (set),
1393                                frogr_photoset_get_description (set));
1394   DEBUG ("%s", debug_msg);
1395 }
1396 
1397 static void
_create_photoset_cb(GObject * object,GAsyncResult * res,gpointer data)1398 _create_photoset_cb (GObject *object, GAsyncResult *res, gpointer data)
1399 {
1400   FspSession *session = NULL;
1401   UploadOnePictureData *uop_data = NULL;
1402   FrogrController *controller = NULL;
1403   FrogrPhotoSet *set = NULL;
1404   GSList *photosets = NULL;
1405   g_autofree gchar *photoset_id = NULL;
1406   GError *error = NULL;
1407 
1408   session = FSP_SESSION (object);
1409   uop_data = (UploadOnePictureData*) data;
1410   controller = uop_data->controller;
1411   photosets = uop_data->photosets;
1412   set = FROGR_PHOTOSET (photosets->data);
1413 
1414   photoset_id = fsp_session_create_photoset_finish (session, res, &error);
1415   if (error && _should_retry_operation (error, uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET]))
1416     {
1417       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET]++;
1418 
1419       DEBUG("Error adding picture %s to NEW photoset %s: %s. Retrying... (attempt %d / %d)",
1420             frogr_picture_get_title (uop_data->picture),
1421             error->message,
1422             frogr_photoset_get_title (set),
1423             uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET],
1424             MAX_ATTEMPTS);
1425       g_error_free (error);
1426 
1427       _add_picture_to_photoset (controller, uop_data);
1428     }
1429   else
1430     {
1431       gboolean keep_going = FALSE;
1432 
1433       if (!error)
1434         {
1435           frogr_photoset_set_id (set, photoset_id);
1436           frogr_photoset_set_n_photos (set, frogr_photoset_get_n_photos (set) + 1);
1437 
1438           uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET] = 0;
1439           uop_data->photosets = g_slist_next (photosets);
1440           keep_going = _add_picture_to_photosets_or_create (controller, uop_data);
1441         }
1442       else
1443         {
1444           DEBUG ("Error creating set: %s", error->message);
1445           uop_data->up_data->error = error;
1446         }
1447 
1448       if (!keep_going)
1449         controller->adding_to_set = FALSE;
1450     }
1451 }
1452 
1453 static void
_add_picture_to_photoset(FrogrController * self,UploadOnePictureData * uop_data)1454 _add_picture_to_photoset (FrogrController *self, UploadOnePictureData *uop_data)
1455 {
1456   FrogrPicture *picture = NULL;
1457   FrogrPhotoSet *set = NULL;
1458   g_autofree gchar *debug_msg = NULL;
1459 
1460   picture = uop_data->picture;
1461   set = FROGR_PHOTOSET (uop_data->photosets->data);
1462 
1463   /* Set with ID: Add picture to it */
1464   fsp_session_add_to_photoset (self->session,
1465                                frogr_picture_get_id (picture),
1466                                frogr_photoset_get_id (set),
1467                                uop_data->cancellable,
1468                                _add_to_photoset_cb,
1469                                uop_data);
1470 
1471   debug_msg = g_strdup_printf ("Adding picture %s to photoset %s…",
1472                                frogr_picture_get_title (picture),
1473                                frogr_photoset_get_title (set));
1474   DEBUG ("%s", debug_msg);
1475 }
1476 
1477 static void
_add_to_photoset_cb(GObject * object,GAsyncResult * res,gpointer data)1478 _add_to_photoset_cb (GObject *object, GAsyncResult *res, gpointer data)
1479 {
1480   FspSession *session = NULL;
1481   UploadOnePictureData *uop_data = NULL;
1482   FrogrController *controller = NULL;
1483   FrogrPhotoSet *set = NULL;
1484   GSList *photosets = NULL;
1485   GError *error = NULL;
1486 
1487   session = FSP_SESSION (object);
1488   uop_data = (UploadOnePictureData*) data;
1489   controller = uop_data->controller;
1490   photosets = uop_data->photosets;
1491   set = FROGR_PHOTOSET (photosets->data);
1492 
1493   fsp_session_add_to_photoset_finish (session, res, &error);
1494   if (error && _should_retry_operation (error, uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET]))
1495     {
1496       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET]++;
1497 
1498       DEBUG("Error adding picture %s to EXISTING photoset %s: %s. Retrying... (attempt %d / %d)",
1499             frogr_picture_get_title (uop_data->picture),
1500             error->message,
1501             frogr_photoset_get_title (set),
1502             uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET],
1503             MAX_ATTEMPTS);
1504       g_error_free (error);
1505 
1506       _add_picture_to_photoset (controller, uop_data);
1507     }
1508   else
1509     {
1510       gboolean keep_going = FALSE;
1511 
1512       if (!error)
1513         {
1514           frogr_photoset_set_n_photos (set, frogr_photoset_get_n_photos (set) + 1);
1515 
1516           uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_SET] = 0;
1517           uop_data->photosets = g_slist_next (photosets);
1518           keep_going = _add_picture_to_photosets_or_create (controller, uop_data);
1519         }
1520       else
1521         {
1522           DEBUG ("Error adding picture to set: %s", error->message);
1523           uop_data->up_data->error = error;
1524         }
1525 
1526       if (!keep_going)
1527         controller->adding_to_set = FALSE;
1528     }
1529 }
1530 
1531 static gboolean
_add_picture_to_groups(FrogrController * self,UploadOnePictureData * uop_data)1532 _add_picture_to_groups (FrogrController *self, UploadOnePictureData *uop_data)
1533 {
1534   /* Add pictures to groups, if any */
1535   if (g_slist_length (uop_data->groups) == 0)
1536     return FALSE;
1537 
1538   self->adding_to_group = TRUE;
1539 
1540   _add_picture_to_group (self, uop_data);
1541 
1542   return TRUE;
1543 }
1544 
1545 static void
_add_picture_to_group(FrogrController * self,UploadOnePictureData * uop_data)1546 _add_picture_to_group (FrogrController *self, UploadOnePictureData *uop_data)
1547 {
1548   FrogrPicture *picture = NULL;
1549   FrogrGroup *group = NULL;
1550   g_autofree gchar *debug_msg = NULL;
1551 
1552   picture = uop_data->picture;
1553   group = FROGR_GROUP (uop_data->groups->data);
1554 
1555   fsp_session_add_to_group (self->session,
1556                             frogr_picture_get_id (picture),
1557                             frogr_group_get_id (group),
1558                             uop_data->cancellable,
1559                             _add_to_group_cb,
1560                             uop_data);
1561 
1562   debug_msg = g_strdup_printf ("Adding picture %s to group %s…",
1563                                frogr_picture_get_title (picture),
1564                                frogr_group_get_name (group));
1565   DEBUG ("%s", debug_msg);
1566 }
1567 
1568 static void
_add_to_group_cb(GObject * object,GAsyncResult * res,gpointer data)1569 _add_to_group_cb (GObject *object, GAsyncResult *res, gpointer data)
1570 {
1571   FspSession *session = NULL;
1572   UploadOnePictureData *uop_data = NULL;
1573   FrogrController *controller = NULL;
1574   FrogrGroup *group = NULL;
1575   GSList *groups = NULL;
1576   GError *error = NULL;
1577 
1578   session = FSP_SESSION (object);
1579   uop_data = (UploadOnePictureData*) data;
1580   controller = uop_data->controller;
1581   groups = uop_data->groups;
1582   group = FROGR_GROUP (groups->data);
1583 
1584   fsp_session_add_to_group_finish (session, res, &error);
1585   if (error && _should_retry_operation (error, uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_GROUP]))
1586     {
1587       uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_GROUP]++;
1588 
1589       DEBUG("Error adding picture %s to group %s: %s. Retrying... (attempt %d / %d)",
1590             frogr_picture_get_title (uop_data->picture),
1591             error->message,
1592             frogr_group_get_name (group),
1593             uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_GROUP],
1594             MAX_ATTEMPTS);
1595       g_error_free (error);
1596 
1597       _add_picture_to_group (controller, uop_data);
1598     }
1599   else
1600     {
1601       gboolean keep_going = FALSE;
1602 
1603       if (!error)
1604         {
1605           frogr_group_set_n_photos (group, frogr_group_get_n_photos (group) + 1);
1606 
1607           uop_data->after_upload_attempts[AFTER_UPLOAD_OP_ADDING_TO_GROUP] = 0;
1608           uop_data->groups = g_slist_next (groups);
1609           keep_going = _add_picture_to_groups (controller, uop_data);
1610         }
1611       else
1612         {
1613           DEBUG ("Error adding picture to group: %s", error->message);
1614           uop_data->up_data->error = error;
1615         }
1616 
1617       if (!keep_going)
1618         controller->adding_to_group = FALSE;
1619     }
1620 }
1621 
1622 static gboolean
_complete_picture_upload(gpointer data)1623 _complete_picture_upload (gpointer data)
1624 {
1625   UploadOnePictureData *uop_data = NULL;
1626   UploadPicturesData *up_data = NULL;
1627   FrogrController *controller = NULL;
1628   FrogrPicture *picture = NULL;
1629 
1630   uop_data = (UploadOnePictureData*) data;
1631   controller = uop_data->controller;
1632   up_data = uop_data->up_data;
1633 
1634   /* Keep the source while busy */
1635   if (controller->setting_license || controller->setting_location || controller->setting_replace_date_posted
1636       || controller->adding_to_set || controller->adding_to_group)
1637     {
1638       frogr_main_view_pulse_progress (controller->mainview);
1639       _update_upload_progress (controller, up_data);
1640       return G_SOURCE_CONTINUE;
1641     }
1642   picture = uop_data->picture;
1643 
1644   if (g_cancellable_is_cancelled (uop_data->cancellable) || up_data->error)
1645     {
1646       up_data->current = NULL;
1647     }
1648   else
1649     {
1650       /* Remove it from the model if no error happened */
1651       FrogrModel *model = NULL;
1652       model = frogr_main_view_get_model (controller->mainview);
1653       frogr_model_remove_picture (model, picture);
1654     }
1655 
1656   /* Re-check account info to make sure we have up-to-date info */
1657   _fetch_account_info (controller);
1658 
1659   _finish_upload_one_picture_process (controller, uop_data);
1660   _upload_next_picture (controller, up_data);
1661 
1662   return G_SOURCE_REMOVE;
1663 }
1664 
1665 static void
_on_file_loaded(FrogrFileLoader * loader,FrogrPicture * picture,FrogrController * self)1666 _on_file_loaded (FrogrFileLoader *loader, FrogrPicture *picture, FrogrController *self)
1667 {
1668   FrogrModel *model = NULL;
1669 
1670   g_return_if_fail (FROGR_IS_CONTROLLER (self));
1671   g_return_if_fail (FROGR_IS_PICTURE (picture));
1672 
1673   model = frogr_main_view_get_model (self->mainview);
1674   frogr_model_add_picture (model, picture);
1675 }
1676 
1677 static void
_on_files_loaded(FrogrFileLoader * loader,FrogrController * self)1678 _on_files_loaded (FrogrFileLoader *loader, FrogrController *self)
1679 {
1680   g_return_if_fail (FROGR_IS_CONTROLLER (self));
1681   _set_state (self, FROGR_STATE_IDLE);
1682 }
1683 
1684 static void
_on_model_deserialized(FrogrModel * model,FrogrController * self)1685 _on_model_deserialized (FrogrModel *model, FrogrController *self)
1686 {
1687   g_return_if_fail (FROGR_IS_CONTROLLER (self));
1688   _set_state (self, FROGR_STATE_IDLE);
1689 }
1690 
1691 static void
_fetch_everything(FrogrController * self,gboolean force_extra_data)1692 _fetch_everything (FrogrController *self, gboolean force_extra_data)
1693 {
1694   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1695 
1696   if (!frogr_controller_is_authorized (self))
1697     return;
1698 
1699   /* Invalidate all fetched data as soon as possible if we already
1700      know upfront we will force fetching it again later. */
1701   if (force_extra_data)
1702     _invalidate_extra_data (self);
1703 
1704   FetchAccountInfoData *data = g_slice_new0 (FetchAccountInfoData);
1705   data->retrieve_everything = TRUE;
1706   data->force_extra_data = force_extra_data;
1707 
1708   _fetch_account_basic_info (self, data);
1709 }
1710 
1711 static void
_fetch_account_info(FrogrController * self)1712 _fetch_account_info (FrogrController *self)
1713 {
1714   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1715 
1716   if (!frogr_controller_is_authorized (self))
1717     return;
1718 
1719   FetchAccountInfoData *data = g_slice_new0 (FetchAccountInfoData);
1720   data->retrieve_everything = FALSE;
1721   data->force_extra_data = FALSE;
1722 
1723   _fetch_account_basic_info (self, data);
1724 }
1725 
1726 static void
_fetch_account_info_finish(FrogrController * self,FetchAccountInfoData * data)1727 _fetch_account_info_finish (FrogrController *self, FetchAccountInfoData *data)
1728 {
1729   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1730   if (data)
1731     g_slice_free (FetchAccountInfoData, data);
1732 }
1733 
1734 static void
_fetch_account_basic_info(FrogrController * self,FetchAccountInfoData * data)1735 _fetch_account_basic_info (FrogrController *self, FetchAccountInfoData *data)
1736 {
1737   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1738 
1739   if (!frogr_controller_is_authorized (self))
1740     {
1741       _fetch_account_info_finish (self, data);
1742       return;
1743     }
1744 
1745   fsp_session_check_auth_info (self->session, NULL,
1746                                (GAsyncReadyCallback)_fetch_account_basic_info_cb,
1747                                data);
1748 }
1749 
1750 static void
_fetch_account_basic_info_cb(GObject * object,GAsyncResult * res,FetchAccountInfoData * data)1751 _fetch_account_basic_info_cb (GObject *object, GAsyncResult *res, FetchAccountInfoData *data)
1752 {
1753   FspSession *session = NULL;
1754   FrogrController *controller = NULL;
1755   FspDataAuthToken *auth_token = NULL;
1756   g_autoptr(GError) error = NULL;
1757 
1758   session = FSP_SESSION (object);
1759   controller = frogr_controller_get_instance ();
1760 
1761   auth_token = fsp_session_check_auth_info_finish (session, res, &error);
1762   if (auth_token && controller->account)
1763     {
1764       const gchar *old_username = NULL;
1765       const gchar *old_fullname = NULL;
1766       gboolean username_changed = FALSE;
1767 
1768       /* Check for changes (only for fields that it makes sense) */
1769       old_username = frogr_account_get_username (controller->account);
1770       old_fullname = frogr_account_get_fullname (controller->account);
1771       if (g_strcmp0 (old_username, auth_token->username)
1772           || g_strcmp0 (old_fullname, auth_token->fullname))
1773         {
1774           username_changed = TRUE;
1775         }
1776 
1777       frogr_account_set_username (controller->account, auth_token->username);
1778       frogr_account_set_fullname (controller->account, auth_token->fullname);
1779 
1780       if (username_changed)
1781         {
1782           /* Save to disk and emit signal if basic info changed */
1783           frogr_config_save_accounts (controller->config);
1784           g_signal_emit (controller, signals[ACTIVE_ACCOUNT_CHANGED], 0, controller->account);
1785         }
1786 
1787       /* Now fetch the remaining bits of information */
1788       _fetch_account_upload_status (controller, data);
1789     }
1790   else
1791     {
1792       if (error != NULL)
1793         {
1794           DEBUG ("Fetching basic info from the account: %s", error->message);
1795           _handle_flicksoup_error (controller, error, FALSE);
1796         }
1797       _fetch_account_info_finish (controller, data);
1798     }
1799 
1800   fsp_data_free (FSP_DATA (auth_token));
1801 }
1802 
_fetch_account_upload_status(FrogrController * self,FetchAccountInfoData * data)1803 static void _fetch_account_upload_status (FrogrController *self, FetchAccountInfoData *data)
1804 {
1805   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1806 
1807   if (!frogr_controller_is_authorized (self))
1808     {
1809       _fetch_account_info_finish (self, data);
1810       return;
1811     }
1812 
1813   fsp_session_get_upload_status (self->session, NULL,
1814                                  (GAsyncReadyCallback)_fetch_account_upload_status_cb,
1815                                  data);
1816 }
1817 
1818 static void
_fetch_account_upload_status_cb(GObject * object,GAsyncResult * res,FetchAccountInfoData * data)1819 _fetch_account_upload_status_cb (GObject *object, GAsyncResult *res, FetchAccountInfoData *data)
1820 {
1821   FspSession *session = NULL;
1822   FrogrController *controller = NULL;
1823   FspDataUploadStatus *upload_status = NULL;
1824   g_autoptr(GError) error = NULL;
1825 
1826   session = FSP_SESSION (object);
1827   controller = frogr_controller_get_instance ();
1828 
1829   upload_status = fsp_session_get_upload_status_finish (session, res, &error);
1830   if (upload_status && controller->account)
1831     {
1832       gulong old_remaining_bw;
1833       gulong old_max_bw;
1834       gboolean old_is_pro;
1835 
1836       /* Check for changes */
1837       old_remaining_bw = frogr_account_get_remaining_bandwidth (controller->account);
1838       old_max_bw = frogr_account_get_max_bandwidth (controller->account);
1839       old_is_pro = frogr_account_is_pro (controller->account);
1840 
1841       frogr_account_set_remaining_bandwidth (controller->account, upload_status->bw_remaining_kb);
1842       frogr_account_set_max_bandwidth (controller->account, upload_status->bw_max_kb);
1843       frogr_account_set_max_picture_filesize (controller->account, upload_status->picture_fs_max_kb);
1844 
1845       frogr_account_set_remaining_videos (controller->account, upload_status->bw_remaining_videos);
1846       frogr_account_set_current_videos (controller->account, upload_status->bw_used_videos);
1847       frogr_account_set_max_video_filesize (controller->account, upload_status->video_fs_max_kb);
1848 
1849       frogr_account_set_is_pro (controller->account, upload_status->pro_user);
1850 
1851       /* Mark that we received this extra info for the user */
1852       frogr_account_set_has_extra_info (controller->account, TRUE);
1853 
1854       if (old_remaining_bw != upload_status->bw_remaining_kb
1855           || old_max_bw != upload_status->bw_max_kb
1856           || old_is_pro != upload_status->pro_user)
1857         {
1858           /* Emit signal if extra info changed */
1859           g_signal_emit (controller, signals[ACTIVE_ACCOUNT_CHANGED], 0, controller->account);
1860         }
1861 
1862       /* Chain with the continuation function if any */
1863       if (data->retrieve_everything)
1864         _fetch_extra_data (controller, data->force_extra_data);
1865     }
1866   else if (error != NULL)
1867     {
1868       DEBUG ("Fetching upload status for the account: %s", error->message);
1869       _handle_flicksoup_error (controller, error, FALSE);
1870     }
1871 
1872   fsp_data_free (FSP_DATA (upload_status));
1873   _fetch_account_info_finish (controller, data);
1874 }
1875 
1876 static void
_fetch_extra_data(FrogrController * self,gboolean force)1877 _fetch_extra_data (FrogrController *self, gboolean force)
1878 {
1879   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1880 
1881   if (!frogr_controller_is_connected (self))
1882     return;
1883 
1884   /* Invalidate all fetched data. */
1885   if (force)
1886     _invalidate_extra_data (self);
1887 
1888   /* Sets, groups and tags can take much longer to retrieve,
1889      so we only retrieve that if actually needed */
1890   if (force || !self->photosets_fetched)
1891     _fetch_photosets (self);
1892   if (force || !self->groups_fetched)
1893     _fetch_groups (self);
1894   if (force || !self->tags_fetched)
1895     _fetch_tags (self);
1896 }
1897 
1898 static void
_fetch_photosets(FrogrController * self)1899 _fetch_photosets (FrogrController *self)
1900 {
1901   CancellableOperationData *co_data = NULL;
1902 
1903   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1904 
1905   if (!frogr_controller_is_connected (self))
1906     return;
1907 
1908   self->photosets_fetched = FALSE;
1909   self->fetching_photosets = TRUE;
1910 
1911   co_data = g_slice_new0 (CancellableOperationData);
1912   co_data->controller = self;
1913   co_data->cancellable = _register_new_cancellable (self);
1914   fsp_session_get_photosets (self->session, co_data->cancellable,
1915                              _fetch_photosets_cb, co_data);
1916 }
1917 
1918 static void
_fetch_photosets_cb(GObject * object,GAsyncResult * res,gpointer data)1919 _fetch_photosets_cb (GObject *object, GAsyncResult *res, gpointer data)
1920 {
1921   FspSession *session = NULL;
1922   CancellableOperationData *co_data = NULL;
1923   FrogrController *controller = NULL;
1924   GSList *sets_list = NULL;
1925   g_autoptr(GSList) data_sets_list = NULL;
1926   g_autoptr(GError) error = NULL;
1927   gboolean valid = FALSE;
1928 
1929   session = FSP_SESSION (object);
1930   co_data = (CancellableOperationData*) data;
1931   controller = co_data->controller;
1932 
1933   data_sets_list = fsp_session_get_photosets_finish (session, res, &error);
1934   if (error != NULL)
1935     {
1936       DEBUG ("Fetching list of sets: %s (%d)", error->message, error->code);
1937 
1938       _handle_flicksoup_error (controller, error, FALSE);
1939 
1940       /* If no photosets are found is a valid outcome */
1941       if (error->code == FSP_ERROR_MISSING_DATA)
1942         valid = TRUE;
1943     }
1944   else
1945     {
1946       if (data_sets_list)
1947         {
1948           GSList *item = NULL;
1949           FspDataPhotoSet *current_data_set = NULL;
1950           FrogrPhotoSet *current_set = NULL;
1951 
1952           /* Consider the received result valid if no previous one has arrived first */
1953           valid = !controller->photosets_fetched;
1954 
1955           for (item = data_sets_list; item; item = g_slist_next (item))
1956             {
1957               current_data_set = FSP_DATA_PHOTO_SET (item->data);
1958               if (valid)
1959                 {
1960                   current_set = frogr_photoset_new (current_data_set->id,
1961                                                     current_data_set->title,
1962                                                     current_data_set->description);
1963                   frogr_photoset_set_primary_photo_id (current_set, current_data_set->primary_photo_id);
1964                   frogr_photoset_set_n_photos (current_set, current_data_set->n_photos);
1965 
1966                   sets_list = g_slist_append (sets_list, current_set);
1967                 }
1968               fsp_data_free (FSP_DATA (current_data_set));
1969             }
1970         }
1971     }
1972 
1973   if (valid)
1974     {
1975       FrogrModel *model = frogr_main_view_get_model (controller->mainview);
1976       frogr_model_set_remote_photosets (model, sets_list);
1977       controller->photosets_fetched = TRUE;
1978     }
1979 
1980   _clear_cancellable (controller, co_data->cancellable);
1981   g_slice_free (CancellableOperationData, co_data);
1982 
1983   controller->fetching_photosets = FALSE;
1984 }
1985 
1986 static void
_fetch_groups(FrogrController * self)1987 _fetch_groups (FrogrController *self)
1988 {
1989   CancellableOperationData *co_data = NULL;
1990 
1991   g_return_if_fail(FROGR_IS_CONTROLLER (self));
1992 
1993   if (!frogr_controller_is_connected (self))
1994     return;
1995 
1996   self->groups_fetched = FALSE;
1997   self->fetching_groups = TRUE;
1998 
1999   co_data = g_slice_new0 (CancellableOperationData);
2000   co_data->controller = self;
2001   co_data->cancellable = _register_new_cancellable (self);
2002   fsp_session_get_groups (self->session, co_data->cancellable,
2003                           _fetch_groups_cb, co_data);
2004 }
2005 
2006 static void
_fetch_groups_cb(GObject * object,GAsyncResult * res,gpointer data)2007 _fetch_groups_cb (GObject *object, GAsyncResult *res, gpointer data)
2008 {
2009   FspSession *session = NULL;
2010   CancellableOperationData *co_data = NULL;
2011   FrogrController *controller = NULL;
2012   GSList *groups_list = NULL;
2013   g_autoptr(GSList) data_groups_list = NULL;
2014   g_autoptr(GError) error = NULL;
2015   gboolean valid = FALSE;
2016 
2017   session = FSP_SESSION (object);
2018   co_data = (CancellableOperationData*) data;
2019   controller = co_data->controller;
2020 
2021   data_groups_list = fsp_session_get_groups_finish (session, res, &error);
2022   if (error != NULL)
2023     {
2024       DEBUG ("Fetching list of groups: %s (%d)", error->message, error->code);
2025 
2026       _handle_flicksoup_error (controller, error, FALSE);
2027 
2028       /* If no groups are found is a valid outcome */
2029       if (error->code == FSP_ERROR_MISSING_DATA)
2030         valid = TRUE;
2031     }
2032   else
2033     {
2034       if (data_groups_list)
2035         {
2036           GSList *item = NULL;
2037           FspDataGroup *data_group = NULL;
2038           FrogrGroup *current_group = NULL;
2039 
2040           /* Consider the received result valid if no previous one has arrived first */
2041           valid = !controller->groups_fetched;
2042 
2043           for (item = data_groups_list; item; item = g_slist_next (item))
2044             {
2045               data_group = FSP_DATA_GROUP (item->data);
2046               if (valid)
2047                 {
2048                   current_group = frogr_group_new (data_group->id,
2049                                                    data_group->name,
2050                                                    data_group->privacy,
2051                                                    data_group->n_photos);
2052 
2053                   groups_list = g_slist_append (groups_list, current_group);
2054                 }
2055               fsp_data_free (FSP_DATA (data_group));
2056             }
2057         }
2058     }
2059 
2060   if (valid)
2061     {
2062       FrogrModel *model = frogr_main_view_get_model (controller->mainview);
2063       frogr_model_set_groups (model, groups_list);
2064       controller->groups_fetched = TRUE;
2065     }
2066 
2067   _clear_cancellable (controller, co_data->cancellable);
2068   g_slice_free (CancellableOperationData, co_data);
2069 
2070   controller->fetching_groups = FALSE;
2071 }
2072 
2073 static void
_fetch_tags(FrogrController * self)2074 _fetch_tags (FrogrController *self)
2075 {
2076   CancellableOperationData *co_data = NULL;
2077 
2078   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2079 
2080   if (!frogr_controller_is_connected (self))
2081     return;
2082 
2083   self->tags_fetched = FALSE;
2084 
2085   /* Do not actually fetch tags if the autocompletion is off */
2086   if (!frogr_config_get_tags_autocompletion (self->config))
2087     return;
2088   self->fetching_tags = TRUE;
2089 
2090   co_data = g_slice_new0 (CancellableOperationData);
2091   co_data->controller = self;
2092   co_data->cancellable = _register_new_cancellable (self);
2093   fsp_session_get_tags_list (self->session, co_data->cancellable,
2094                              _fetch_tags_cb, co_data);
2095 }
2096 
2097 static void
_fetch_tags_cb(GObject * object,GAsyncResult * res,gpointer data)2098 _fetch_tags_cb (GObject *object, GAsyncResult *res, gpointer data)
2099 {
2100   FspSession *session = NULL;
2101   CancellableOperationData *co_data = NULL;
2102   FrogrController *controller = NULL;
2103   GSList *tags_list = NULL;
2104   g_autoptr(GError) error = NULL;
2105   gboolean valid = FALSE;
2106 
2107   session = FSP_SESSION (object);
2108   co_data = (CancellableOperationData*) data;
2109   controller = co_data->controller;
2110 
2111   tags_list = fsp_session_get_tags_list_finish (session, res, &error);
2112   if (error != NULL)
2113     {
2114       DEBUG ("Fetching list of tags: %s", error->message);
2115 
2116       _handle_flicksoup_error (controller, error, FALSE);
2117 
2118       /* If no tags are found is a valid outcome */
2119       if (error->code == FSP_ERROR_MISSING_DATA)
2120         valid = TRUE;
2121 
2122       tags_list = NULL;
2123     }
2124   else
2125     {
2126       /* Consider the received result valid if no previous one has arrived first */
2127       valid = !controller->tags_fetched;
2128       if (!valid)
2129         g_slist_free_full (tags_list, g_free);
2130     }
2131 
2132   if (valid)
2133     {
2134       FrogrModel *model = frogr_main_view_get_model (controller->mainview);
2135       frogr_model_set_remote_tags (model, tags_list);
2136       controller->tags_fetched = TRUE;
2137     }
2138 
2139   _clear_cancellable (controller, co_data->cancellable);
2140   g_slice_free (CancellableOperationData, co_data);
2141 
2142   controller->fetching_tags = FALSE;
2143 }
2144 
2145 static void
_dispose_slist_of_objects(GSList * objects)2146 _dispose_slist_of_objects (GSList *objects)
2147 {
2148   if (!objects)
2149     return;
2150 
2151   /* FrogrController's responsibility over this list ends here */
2152   g_slist_free_full (objects, g_object_unref);
2153 }
2154 
2155 static gboolean
_show_progress_on_idle(gpointer data)2156 _show_progress_on_idle (gpointer data)
2157 {
2158   FrogrController *controller = NULL;
2159   FetchingActivity activity = FETCHING_NOTHING;
2160   const gchar *text = NULL;
2161   gboolean show_dialog = FALSE;
2162 
2163   controller = frogr_controller_get_instance ();
2164 
2165   activity = GPOINTER_TO_INT (data);
2166   switch (activity)
2167     {
2168     case FETCHING_TOKEN_REPLACEMENT:
2169       text = _("Updating credentials…");
2170       show_dialog = controller->fetching_token_replacement;
2171       break;
2172 
2173     case FETCHING_AUTH_URL:
2174       text = _("Retrieving data for authorization…");
2175       show_dialog = controller->fetching_auth_url;
2176       break;
2177 
2178     case FETCHING_AUTH_TOKEN:
2179       text = _("Finishing authorization…");
2180       show_dialog = controller->fetching_auth_token;
2181       break;
2182 
2183     case FETCHING_PHOTOSETS:
2184       text = _("Retrieving list of sets…");
2185       show_dialog = controller->fetching_photosets;
2186       break;
2187 
2188     case FETCHING_GROUPS:
2189       text = _("Retrieving list of groups…");
2190       show_dialog = controller->fetching_groups;
2191       break;
2192 
2193     case FETCHING_TAGS:
2194       text = _("Retrieving list of tags…");
2195       show_dialog = controller->fetching_tags;
2196       break;
2197 
2198     default:
2199       text = NULL;
2200     }
2201 
2202   /* Pulse and show/hide the progress dialog as needed */
2203   frogr_main_view_pulse_progress (controller->mainview);
2204   if (show_dialog)
2205     {
2206       g_autofree gchar *title = NULL;
2207 
2208       switch (activity)
2209         {
2210         case FETCHING_TOKEN_REPLACEMENT:
2211         case FETCHING_AUTH_URL:
2212         case FETCHING_AUTH_TOKEN:
2213           title = g_strdup_printf (_("Authorize %s"), APP_SHORTNAME);
2214           break;
2215 
2216         case FETCHING_PHOTOSETS:
2217         case FETCHING_GROUPS:
2218         case FETCHING_TAGS:
2219           title = g_strdup_printf (_("Fetching information"));
2220           break;
2221 
2222         default:
2223           title = g_strdup ("");
2224         }
2225 
2226       frogr_main_view_show_progress (controller->mainview, title, text);
2227       return G_SOURCE_CONTINUE;
2228     }
2229   else
2230     {
2231       frogr_main_view_hide_progress (controller->mainview);
2232       return G_SOURCE_REMOVE;
2233     }
2234 }
2235 
2236 static gboolean
_show_details_dialog_on_idle(GSList * pictures)2237 _show_details_dialog_on_idle (GSList *pictures)
2238 {
2239   FrogrController *controller = NULL;
2240   FrogrModel *model = NULL;
2241   GSList *tags_list = NULL;
2242 
2243   controller = frogr_controller_get_instance ();
2244 
2245   /* Keep the source while internally busy */
2246   if (controller->fetching_tags && frogr_config_get_tags_autocompletion (controller->config))
2247     return G_SOURCE_CONTINUE;
2248 
2249   model = frogr_main_view_get_model (controller->mainview);
2250   tags_list = frogr_model_get_tags (model);
2251 
2252   /* Sets already pre-fetched: show the dialog */
2253   frogr_details_dialog_show (GTK_WINDOW (controller->mainview), pictures, tags_list);
2254 
2255   controller->show_details_dialog_source_id = 0;
2256   return G_SOURCE_REMOVE;
2257 }
2258 
2259 static gboolean
_show_add_tags_dialog_on_idle(GSList * pictures)2260 _show_add_tags_dialog_on_idle (GSList *pictures)
2261 {
2262   FrogrController *controller = NULL;
2263   FrogrModel *model = NULL;
2264   GSList *tags_list = NULL;
2265 
2266   controller = frogr_controller_get_instance ();
2267 
2268   /* Keep the source while internally busy */
2269   if (controller->fetching_tags && frogr_config_get_tags_autocompletion (controller->config))
2270       return G_SOURCE_CONTINUE;
2271 
2272   model = frogr_main_view_get_model (controller->mainview);
2273   tags_list = frogr_model_get_tags (model);
2274 
2275   /* Sets already pre-fetched: show the dialog */
2276   frogr_add_tags_dialog_show (GTK_WINDOW (controller->mainview), pictures, tags_list);
2277 
2278   controller->show_add_tags_dialog_source_id = 0;
2279   return G_SOURCE_REMOVE;
2280 }
2281 
2282 static gboolean
_show_create_new_set_dialog_on_idle(GSList * pictures)2283 _show_create_new_set_dialog_on_idle (GSList *pictures)
2284 {
2285   FrogrController *controller = NULL;
2286   FrogrModel *model = NULL;
2287   GSList *photosets = NULL;
2288 
2289   controller = frogr_controller_get_instance ();
2290 
2291   /* Keep the source while internally busy */
2292   if (controller->fetching_photosets)
2293       return G_SOURCE_CONTINUE;
2294 
2295   model = frogr_main_view_get_model (controller->mainview);
2296   photosets = frogr_model_get_photosets (model);
2297 
2298   frogr_create_new_set_dialog_show (GTK_WINDOW (controller->mainview), pictures, photosets);
2299 
2300   controller->show_create_new_set_dialog_source_id = 0;
2301   return G_SOURCE_REMOVE;
2302 }
2303 
2304 static gboolean
_show_add_to_set_dialog_on_idle(GSList * pictures)2305 _show_add_to_set_dialog_on_idle (GSList *pictures)
2306 {
2307   FrogrController *controller = NULL;
2308   FrogrModel *model = NULL;
2309   GSList *photosets = NULL;
2310 
2311   controller = frogr_controller_get_instance ();
2312 
2313   /* Keep the source while internally busy */
2314   if (controller->fetching_photosets)
2315       return G_SOURCE_CONTINUE;
2316 
2317   model = frogr_main_view_get_model (controller->mainview);
2318   photosets = frogr_model_get_photosets (model);
2319 
2320   if (frogr_model_n_photosets (model) > 0)
2321     frogr_add_to_set_dialog_show (GTK_WINDOW (controller->mainview), pictures, photosets);
2322   else if (controller->photosets_fetched)
2323     frogr_util_show_info_dialog (GTK_WINDOW (controller->mainview), _("No sets found"));
2324 
2325   controller->show_add_to_set_dialog_source_id = 0;
2326   return G_SOURCE_REMOVE;
2327 }
2328 
2329 static gboolean
_show_add_to_group_dialog_on_idle(GSList * pictures)2330 _show_add_to_group_dialog_on_idle (GSList *pictures)
2331 {
2332   FrogrController *controller = NULL;
2333   FrogrModel *model = NULL;
2334   GSList *groups = NULL;
2335 
2336   controller = frogr_controller_get_instance ();
2337 
2338   /* Keep the source while internally busy */
2339   if (controller->fetching_groups)
2340       return G_SOURCE_CONTINUE;
2341 
2342   model = frogr_main_view_get_model (controller->mainview);
2343   groups = frogr_model_get_groups (model);
2344 
2345   if (frogr_model_n_groups (model) > 0)
2346     frogr_add_to_group_dialog_show (GTK_WINDOW (controller->mainview), pictures, groups);
2347   else if (controller->groups_fetched)
2348     frogr_util_show_info_dialog (GTK_WINDOW (controller->mainview), _("No groups found"));
2349 
2350   controller->show_add_to_group_dialog_source_id = 0;
2351   return G_SOURCE_REMOVE;
2352 }
2353 
2354 static gboolean
_is_modal_dialog_about_to_be_shown(FrogrController * self)2355 _is_modal_dialog_about_to_be_shown  (FrogrController *self)
2356 {
2357   if (self->show_details_dialog_source_id
2358       || self->show_add_tags_dialog_source_id
2359       || self->show_create_new_set_dialog_source_id
2360       || self->show_add_to_set_dialog_source_id
2361       || self->show_add_to_group_dialog_source_id)
2362     return TRUE;
2363 
2364   return FALSE;
2365 }
2366 
2367 static GObject *
_frogr_controller_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)2368 _frogr_controller_constructor (GType type,
2369                                guint n_construct_properties,
2370                                GObjectConstructParam *construct_properties)
2371 {
2372   GObject *object;
2373 
2374   if (!_instance)
2375     {
2376       object =
2377         G_OBJECT_CLASS (frogr_controller_parent_class)->constructor (type,
2378                                                                      n_construct_properties,
2379                                                                      construct_properties);
2380       _instance = FROGR_CONTROLLER (object);
2381     }
2382   else
2383     object = G_OBJECT (_instance);
2384 
2385   return object;
2386 }
2387 
2388 static void
_frogr_controller_dispose(GObject * object)2389 _frogr_controller_dispose (GObject* object)
2390 {
2391   FrogrController *controller = FROGR_CONTROLLER (object);
2392 
2393   g_clear_object (&controller->mainview);
2394   g_clear_object (&controller->config);
2395   g_clear_object (&controller->account);
2396   g_clear_object (&controller->session);
2397 
2398   if (controller->cancellables)
2399     {
2400       g_list_free_full (controller->cancellables, g_object_unref);
2401       controller->cancellables = NULL;
2402     }
2403 
2404   G_OBJECT_CLASS (frogr_controller_parent_class)->dispose (object);
2405 }
2406 
2407 static void
frogr_controller_class_init(FrogrControllerClass * klass)2408 frogr_controller_class_init (FrogrControllerClass *klass)
2409 {
2410   GObjectClass *obj_class = G_OBJECT_CLASS (klass);
2411 
2412   obj_class->constructor = _frogr_controller_constructor;
2413   obj_class->dispose = _frogr_controller_dispose;
2414 
2415   signals[STATE_CHANGED] =
2416     g_signal_new ("state-changed",
2417                   G_OBJECT_CLASS_TYPE (klass),
2418                   G_SIGNAL_RUN_FIRST,
2419                   0, NULL, NULL,
2420                   g_cclosure_marshal_VOID__INT,
2421                   G_TYPE_NONE, 1, G_TYPE_INT);
2422 
2423   signals[ACTIVE_ACCOUNT_CHANGED] =
2424     g_signal_new ("active-account-changed",
2425                   G_OBJECT_CLASS_TYPE (klass),
2426                   G_SIGNAL_RUN_FIRST,
2427                   0, NULL, NULL,
2428                   g_cclosure_marshal_VOID__OBJECT,
2429                   G_TYPE_NONE, 1, FROGR_TYPE_ACCOUNT);
2430 
2431   signals[ACCOUNTS_CHANGED] =
2432     g_signal_new ("accounts-changed",
2433                   G_OBJECT_CLASS_TYPE (klass),
2434                   G_SIGNAL_RUN_FIRST,
2435                   0, NULL, NULL,
2436                   g_cclosure_marshal_VOID__VOID,
2437                   G_TYPE_NONE, 0);
2438 }
2439 
2440 static void
frogr_controller_init(FrogrController * self)2441 frogr_controller_init (FrogrController *self)
2442 {
2443   /* Default variables */
2444   self->state = FROGR_STATE_IDLE;
2445   self->mainview = NULL;
2446 
2447   self->config = frogr_config_get_instance ();
2448   g_object_ref (self->config);
2449 
2450   self->session = fsp_session_new (API_KEY, SHARED_SECRET, NULL);
2451   self->cancellables = NULL;
2452   self->app_running = FALSE;
2453   self->fetching_token_replacement = FALSE;
2454   self->fetching_auth_url = FALSE;
2455   self->fetching_auth_token = FALSE;
2456   self->fetching_photosets = FALSE;
2457   self->fetching_groups = FALSE;
2458   self->fetching_tags = FALSE;
2459   self->setting_license = FALSE;
2460   self->setting_location = FALSE;
2461   self->setting_replace_date_posted = FALSE;
2462   self->adding_to_set = FALSE;
2463   self->adding_to_group = FALSE;
2464   self->photosets_fetched = FALSE;
2465   self->groups_fetched = FALSE;
2466   self->tags_fetched = FALSE;
2467   self->show_details_dialog_source_id = 0;
2468   self->show_add_tags_dialog_source_id = 0;
2469   self->show_create_new_set_dialog_source_id = 0;
2470   self->show_add_to_set_dialog_source_id = 0;
2471   self->show_add_to_group_dialog_source_id = 0;
2472 }
2473 
2474 
2475 /* Public API */
2476 
2477 FrogrController *
frogr_controller_get_instance(void)2478 frogr_controller_get_instance (void)
2479 {
2480   if (_instance)
2481     return _instance;
2482 
2483   return FROGR_CONTROLLER (g_object_new (FROGR_TYPE_CONTROLLER, NULL));
2484 }
2485 
2486 gint
frogr_controller_run_app(FrogrController * self,int argc,char ** argv)2487 frogr_controller_run_app (FrogrController *self, int argc, char **argv)
2488 {
2489   g_autoptr(GtkApplication) app = NULL;
2490   gint status;
2491 
2492   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), -1);
2493 
2494   if (self->app_running)
2495     {
2496       DEBUG ("%s", "Application already running");
2497       return -1;
2498     }
2499   self->app_running = TRUE;
2500 
2501   /* Initialize and run the Gtk application */
2502   g_set_application_name(APP_SHORTNAME);
2503   app = gtk_application_new (APP_ID,
2504                              G_APPLICATION_NON_UNIQUE
2505                              | G_APPLICATION_HANDLES_OPEN);
2506 
2507   g_signal_connect (app, "startup", G_CALLBACK (_g_application_startup_cb), self);
2508   g_signal_connect (app, "activate", G_CALLBACK (_g_application_activate_cb), self);
2509   g_signal_connect (app, "shutdown", G_CALLBACK (_g_application_shutdown_cb), self);
2510   g_signal_connect (app, "open", G_CALLBACK (_g_application_open_files_cb), self);
2511 
2512   status = g_application_run (G_APPLICATION (app), argc, argv);
2513 
2514   return status;
2515 }
2516 
2517 FrogrMainView *
frogr_controller_get_main_view(FrogrController * self)2518 frogr_controller_get_main_view (FrogrController *self)
2519 {
2520   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FALSE);
2521   return self->mainview;
2522 }
2523 
2524 FrogrModel *
frogr_controller_get_model(FrogrController * self)2525 frogr_controller_get_model (FrogrController *self)
2526 {
2527   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FALSE);
2528 
2529   if (!self->mainview)
2530     return NULL;
2531 
2532   return frogr_main_view_get_model (self->mainview);;
2533 }
2534 
2535 void
frogr_controller_set_active_account(FrogrController * self,const gchar * username)2536 frogr_controller_set_active_account (FrogrController *self, const gchar *username)
2537 {
2538   FrogrAccount *account = NULL;
2539 
2540   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2541   g_return_if_fail(username);
2542 
2543   frogr_config_set_active_account (self->config, username);
2544   account = frogr_config_get_active_account (self->config);
2545 
2546   _set_active_account (self, account);
2547 }
2548 
2549 FrogrAccount *
frogr_controller_get_active_account(FrogrController * self)2550 frogr_controller_get_active_account (FrogrController *self)
2551 {
2552   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), NULL);
2553   return self->account;
2554 }
2555 
2556 GSList *
frogr_controller_get_all_accounts(FrogrController * self)2557 frogr_controller_get_all_accounts (FrogrController *self)
2558 {
2559   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), NULL);
2560   return frogr_config_get_accounts (self->config);
2561 }
2562 
2563 FrogrControllerState
frogr_controller_get_state(FrogrController * self)2564 frogr_controller_get_state (FrogrController *self)
2565 {
2566   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FROGR_STATE_UNKNOWN);
2567   return self->state;
2568 }
2569 
2570 void
frogr_controller_set_proxy(FrogrController * self,gboolean use_default_proxy,const char * host,const char * port,const char * username,const char * password)2571 frogr_controller_set_proxy (FrogrController *self,
2572                             gboolean use_default_proxy,
2573                             const char *host, const char *port,
2574                             const char *username, const char *password)
2575 {
2576   gboolean proxy_changed = FALSE;
2577 
2578   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2579 
2580   if (use_default_proxy)
2581     {
2582       DEBUG ("Using default proxy settings");
2583       fsp_session_set_default_proxy (self->session, TRUE);
2584 
2585       if (!self->photosets_fetched || !self->groups_fetched || !self->tags_fetched)
2586         _fetch_everything (self, FALSE);
2587 
2588       return;
2589     }
2590 
2591   /* The host is mandatory to set up a proxy */
2592   if (host == NULL || *host == '\0') {
2593     proxy_changed = fsp_session_set_custom_proxy (self->session, NULL, NULL, NULL, NULL);
2594     DEBUG ("%s", "Not enabling the HTTP proxy");
2595   } else {
2596     g_autofree gchar *auth_part = NULL;
2597     gboolean has_username = FALSE;
2598     gboolean has_password = FALSE;
2599 
2600     has_username = (username != NULL && *username != '\0');
2601     has_password = (password != NULL && *password != '\0');
2602 
2603     if (has_username && has_password)
2604       auth_part = g_strdup_printf ("%s:%s@", username, password);
2605 
2606     DEBUG ("Using HTTP proxy: %s%s:%s", auth_part ? auth_part : "", host, port);
2607 
2608     proxy_changed = fsp_session_set_custom_proxy (self->session,
2609                                                   host, port,
2610                                                   username, password);
2611   }
2612 
2613   /* Re-fetch information if needed after changing proxy configuration */
2614   if (self->app_running && proxy_changed)
2615     _fetch_everything (self, FALSE);
2616 }
2617 
2618 void
frogr_controller_fetch_tags_if_needed(FrogrController * self)2619 frogr_controller_fetch_tags_if_needed (FrogrController *self)
2620 {
2621   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2622   if (!self->fetching_tags && !self->tags_fetched)
2623     _fetch_tags (self);
2624 }
2625 
2626 void
frogr_controller_show_about_dialog(FrogrController * self)2627 frogr_controller_show_about_dialog (FrogrController *self)
2628 {
2629   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2630 
2631   /* Run the about dialog */
2632   frogr_about_dialog_show (GTK_WINDOW (self->mainview));
2633 }
2634 
2635 void
frogr_controller_show_auth_dialog(FrogrController * self)2636 frogr_controller_show_auth_dialog (FrogrController *self)
2637 {
2638   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2639 
2640   /* Do not show the authorization dialog if we are exchanging an old
2641      token for a new one, as it should re-authorize automatically */
2642   if (self->fetching_token_replacement)
2643     return;
2644 
2645   /* Run the auth dialog */
2646   frogr_auth_dialog_show (GTK_WINDOW (self->mainview), REQUEST_AUTHORIZATION);
2647 }
2648 
2649 void
frogr_controller_show_settings_dialog(FrogrController * self)2650 frogr_controller_show_settings_dialog (FrogrController *self)
2651 {
2652   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2653 
2654   /* Run the auth dialog */
2655   frogr_settings_dialog_show (GTK_WINDOW (self->mainview));
2656 }
2657 
2658 void
frogr_controller_show_details_dialog(FrogrController * self,GSList * pictures)2659 frogr_controller_show_details_dialog (FrogrController *self,
2660                                       GSList *pictures)
2661 {
2662   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2663 
2664   /* Don't show the dialog if one is to be shown already */
2665   if (_is_modal_dialog_about_to_be_shown (self))
2666     return;
2667 
2668   /* Fetch the tags list first if needed */
2669   if (frogr_config_get_tags_autocompletion (self->config) && !self->tags_fetched)
2670     {
2671       gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_TAGS));
2672       if (!self->fetching_tags)
2673         _fetch_tags (self);
2674     }
2675 
2676   /* Show the dialog when possible */
2677   self->show_details_dialog_source_id =
2678     gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE, DEFAULT_TIMEOUT,
2679                                   (GSourceFunc) _show_details_dialog_on_idle, pictures,
2680                                   (GDestroyNotify) _dispose_slist_of_objects);
2681 }
2682 
2683 void
frogr_controller_show_add_tags_dialog(FrogrController * self,GSList * pictures)2684 frogr_controller_show_add_tags_dialog (FrogrController *self,
2685                                        GSList *pictures)
2686 {
2687   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2688 
2689   /* Don't show the dialog if one is to be shown already */
2690   if (_is_modal_dialog_about_to_be_shown (self))
2691     return;
2692 
2693   /* Fetch the tags list first if needed */
2694   if (frogr_config_get_tags_autocompletion (self->config) && !self->tags_fetched)
2695     {
2696       gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_TAGS));
2697       if (!self->fetching_tags)
2698         _fetch_tags (self);
2699     }
2700 
2701   /* Show the dialog when possible */
2702   self->show_add_tags_dialog_source_id =
2703     gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE, DEFAULT_TIMEOUT,
2704                                   (GSourceFunc) _show_add_tags_dialog_on_idle, pictures,
2705                                   (GDestroyNotify) _dispose_slist_of_objects);
2706 }
2707 
2708 void
frogr_controller_show_create_new_set_dialog(FrogrController * self,GSList * pictures)2709 frogr_controller_show_create_new_set_dialog (FrogrController *self,
2710                                              GSList *pictures)
2711 {
2712   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2713 
2714   /* Don't show the dialog if one is to be shown already */
2715   if (_is_modal_dialog_about_to_be_shown (self))
2716     return;
2717 
2718   /* Fetch the sets first if needed */
2719   if (!self->photosets_fetched)
2720     {
2721       gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_PHOTOSETS));
2722       if (!self->fetching_photosets)
2723         _fetch_photosets (self);
2724     }
2725 
2726   /* Show the dialog when possible */
2727   self->show_create_new_set_dialog_source_id =
2728     gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE, DEFAULT_TIMEOUT,
2729                                   (GSourceFunc) _show_create_new_set_dialog_on_idle, pictures,
2730                                   (GDestroyNotify) _dispose_slist_of_objects);
2731 }
2732 
2733 void
frogr_controller_show_add_to_set_dialog(FrogrController * self,GSList * pictures)2734 frogr_controller_show_add_to_set_dialog (FrogrController *self,
2735                                          GSList *pictures)
2736 {
2737   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2738 
2739   /* Don't show the dialog if one is to be shown already */
2740   if (_is_modal_dialog_about_to_be_shown (self))
2741     return;
2742 
2743   /* Fetch the sets first if needed */
2744   if (!self->photosets_fetched)
2745     {
2746       gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_PHOTOSETS));
2747       if (!self->fetching_photosets)
2748         _fetch_photosets (self);
2749     }
2750 
2751   /* Show the dialog when possible */
2752   self->show_add_to_set_dialog_source_id =
2753     gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE, DEFAULT_TIMEOUT,
2754                                   (GSourceFunc) _show_add_to_set_dialog_on_idle, pictures,
2755                                   (GDestroyNotify) _dispose_slist_of_objects);
2756 }
2757 
2758 void
frogr_controller_show_add_to_group_dialog(FrogrController * self,GSList * pictures)2759 frogr_controller_show_add_to_group_dialog (FrogrController *self,
2760                                            GSList *pictures)
2761 {
2762   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2763 
2764   /* Don't show the dialog if one is to be shown already */
2765   if (_is_modal_dialog_about_to_be_shown (self))
2766     return;
2767 
2768   /* Fetch the groups first if needed */
2769   if (!self->groups_fetched)
2770 
2771     {
2772       gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_GROUPS));
2773       if (!self->fetching_groups)
2774         _fetch_groups (self);
2775     }
2776 
2777   /* Show the dialog when possible */
2778   self->show_add_to_group_dialog_source_id =
2779     gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE, DEFAULT_TIMEOUT,
2780                                   (GSourceFunc) _show_add_to_group_dialog_on_idle, pictures,
2781                                   (GDestroyNotify) _dispose_slist_of_objects);
2782 }
2783 
2784 void
frogr_controller_open_auth_url(FrogrController * self)2785 frogr_controller_open_auth_url (FrogrController *self)
2786 {
2787   CancellableOperationData *co_data = NULL;
2788 
2789   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2790 
2791   self->fetching_auth_url = TRUE;
2792 
2793   co_data = g_slice_new0 (CancellableOperationData);
2794   co_data->controller = self;
2795   co_data->cancellable = _register_new_cancellable (self);
2796   fsp_session_get_auth_url (self->session, co_data->cancellable,
2797                             _get_auth_url_cb, co_data);
2798 
2799   gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_AUTH_URL));
2800 
2801   /* Make sure we show proper feedback if connection is too slow */
2802   gdk_threads_add_timeout (MAX_AUTH_TIMEOUT, (GSourceFunc) _cancel_authorization_on_timeout, self);
2803 }
2804 
2805 void
frogr_controller_complete_auth(FrogrController * self,const gchar * verification_code)2806 frogr_controller_complete_auth (FrogrController *self, const gchar *verification_code)
2807 {
2808   CancellableOperationData *co_data = NULL;
2809 
2810   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2811 
2812   self->fetching_auth_token = TRUE;
2813 
2814   co_data = g_slice_new0 (CancellableOperationData);
2815   co_data->controller = self;
2816   co_data->cancellable = _register_new_cancellable (self);
2817   fsp_session_complete_auth (self->session, verification_code, co_data->cancellable,
2818                              _complete_auth_cb, co_data);
2819 
2820   gdk_threads_add_timeout (DEFAULT_TIMEOUT, (GSourceFunc) _show_progress_on_idle, GINT_TO_POINTER (FETCHING_AUTH_TOKEN));
2821 
2822   /* Make sure we show proper feedback if connection is too slow */
2823   gdk_threads_add_timeout (MAX_AUTH_TIMEOUT, (GSourceFunc) _cancel_authorization_on_timeout, self);
2824 }
2825 
2826 gboolean
frogr_controller_is_authorized(FrogrController * self)2827 frogr_controller_is_authorized (FrogrController *self)
2828 {
2829   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FALSE);
2830 
2831   if (!self->account)
2832     return FALSE;
2833 
2834   /* Old versions for accounts previously stored must be updated first */
2835   if (g_strcmp0 (frogr_account_get_version (self->account), ACCOUNTS_CURRENT_VERSION))
2836     return FALSE;
2837 
2838   return TRUE;;
2839 }
2840 
2841 void
frogr_controller_revoke_authorization(FrogrController * self)2842 frogr_controller_revoke_authorization (FrogrController *self)
2843 {
2844   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2845 
2846   /* Ensure there's the token/account is no longer active anywhere */
2847   fsp_session_set_token (self->session, NULL);
2848   fsp_session_set_token_secret (self->session, NULL);
2849   _set_active_account (self, NULL);
2850 }
2851 
2852 gboolean
frogr_controller_is_connected(FrogrController * self)2853 frogr_controller_is_connected (FrogrController *self)
2854 {
2855   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FALSE);
2856 
2857   /* We can't be sure 100% about having connected to flickr until we
2858      received the extra information for the current account */
2859   if (self->account)
2860     return frogr_account_has_extra_info (self->account);
2861 
2862   return FALSE;
2863 }
2864 
2865 void
frogr_controller_load_pictures(FrogrController * self,GSList * fileuris)2866 frogr_controller_load_pictures (FrogrController *self,
2867                                 GSList *fileuris)
2868 {
2869   FrogrFileLoader *loader = NULL;
2870   gulong max_picture_filesize = G_MAXULONG;
2871   gulong max_video_filesize = G_MAXULONG;
2872 
2873   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2874 
2875   if (self->account)
2876     {
2877       max_picture_filesize = frogr_account_get_max_picture_filesize (self->account);
2878       max_video_filesize = frogr_account_get_max_video_filesize (self->account);
2879     }
2880 
2881   loader = frogr_file_loader_new_from_uris (fileuris, max_picture_filesize, max_video_filesize);
2882 
2883   g_signal_connect (G_OBJECT (loader), "file-loaded",
2884                     G_CALLBACK (_on_file_loaded),
2885                     self);
2886 
2887   g_signal_connect (G_OBJECT (loader), "files-loaded",
2888                     G_CALLBACK (_on_files_loaded),
2889                     self);
2890 
2891   /* Load the pictures! */
2892   _set_state (self, FROGR_STATE_LOADING_PICTURES);
2893   frogr_file_loader_load (loader);
2894 }
2895 
2896 void
frogr_controller_upload_pictures(FrogrController * self,GSList * pictures)2897 frogr_controller_upload_pictures (FrogrController *self, GSList *pictures)
2898 {
2899   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2900   g_return_if_fail(pictures);
2901 
2902   /* Upload pictures */
2903   if (!frogr_controller_is_authorized (self))
2904     {
2905       g_autofree gchar *msg = NULL;
2906       msg = g_strdup_printf (_("You need to properly authorize %s before"
2907                                " uploading any pictures to Flickr.\n"
2908                                "Please re-authorize it."), APP_SHORTNAME);
2909 
2910       frogr_util_show_error_dialog (GTK_WINDOW (self->mainview), msg);
2911     }
2912   else if (!frogr_controller_is_connected (self))
2913     {
2914       g_autofree gchar *msg = NULL;
2915       msg = g_strdup_printf (_("You need to be connected before"
2916                                " uploading any pictures to Flickr."));
2917       frogr_util_show_error_dialog (GTK_WINDOW (self->mainview), msg);
2918     }
2919   else
2920     {
2921       UploadPicturesData *up_data = g_slice_new0 (UploadPicturesData);
2922       up_data->pictures = g_slist_copy (pictures);
2923       up_data->current = up_data->pictures;
2924       up_data->index = 0;
2925       up_data->n_pictures = g_slist_length (pictures);
2926 
2927       /* Add references */
2928       g_slist_foreach (up_data->pictures, (GFunc)g_object_ref, NULL);
2929 
2930       /* Load the pictures! */
2931       _set_state (self, FROGR_STATE_UPLOADING_PICTURES);
2932       frogr_main_view_show_progress (self->mainview, _("Uploading Pictures"), NULL);
2933       _upload_next_picture (self, up_data);
2934     }
2935 }
2936 
2937 void
frogr_controller_reorder_pictures(FrogrController * self)2938 frogr_controller_reorder_pictures (FrogrController *self)
2939 {
2940   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2941   frogr_main_view_reorder_pictures (self->mainview);
2942 }
2943 
2944 void
frogr_controller_cancel_ongoing_requests(FrogrController * self)2945 frogr_controller_cancel_ongoing_requests (FrogrController *self)
2946 {
2947   GCancellable *cancellable = NULL;
2948   GList *item = NULL;
2949 
2950   g_return_if_fail(FROGR_IS_CONTROLLER (self));
2951 
2952   for (item = self->cancellables; item; item = g_list_next (item))
2953     {
2954       cancellable = G_CANCELLABLE (item->data);
2955       if (!g_cancellable_is_cancelled (cancellable))
2956         g_cancellable_cancel (cancellable);
2957     }
2958 }
2959 
2960 gboolean
frogr_controller_open_project_from_file(FrogrController * self,const gchar * path)2961 frogr_controller_open_project_from_file (FrogrController *self, const gchar *path)
2962 {
2963   g_autoptr(JsonParser) json_parser = NULL;
2964   g_autoptr(GError) error = NULL;
2965   gboolean result = FALSE;
2966 
2967   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FALSE);
2968   g_return_val_if_fail(path, FALSE);
2969 
2970   /* Load from disk */
2971   json_parser = json_parser_new ();
2972   json_parser_load_from_file (json_parser, path, &error);
2973   if (error)
2974     {
2975       g_autofree gchar *msg = NULL;
2976 
2977       msg = g_strdup_printf (_("Error opening project file"));
2978       frogr_util_show_error_dialog (GTK_WINDOW (self->mainview), msg);
2979 
2980       DEBUG ("Error loading project file: %s", error->message);
2981     }
2982   else
2983     {
2984       FrogrModel *model = NULL;
2985       JsonNode *root_node = NULL;
2986       JsonObject *root_object = NULL;
2987       JsonObject *data_object = NULL;
2988 
2989       /* Make sure we are not fetching any data from the network at
2990          this moment, or cancel otherwise, so the model is ready */
2991       if (self->fetching_photosets || self->fetching_groups || self->fetching_tags)
2992         frogr_controller_cancel_ongoing_requests (self);
2993 
2994       /* Deserialize from the JSON data and update the model */
2995       _set_state (self, FROGR_STATE_LOADING_PICTURES);
2996 
2997       model = frogr_main_view_get_model (self->mainview);
2998 
2999       root_node = json_parser_get_root (json_parser);
3000       root_object = json_node_get_object (root_node);
3001       data_object = json_object_get_object_member (root_object, "data");
3002 
3003       frogr_main_view_update_project_path (self->mainview, path);
3004       frogr_model_deserialize (model, data_object);
3005       result = TRUE;
3006     }
3007 
3008   return result;
3009 }
3010 
3011 gboolean
frogr_controller_save_project_to_file(FrogrController * self,const gchar * path)3012 frogr_controller_save_project_to_file (FrogrController *self, const gchar *path)
3013 {
3014   FrogrModel *model = NULL;
3015   g_autoptr(JsonGenerator) json_gen = NULL;
3016   g_autoptr(JsonNode) root_node = NULL;
3017   g_autoptr(JsonObject) root_object = NULL;
3018   JsonObject *serialized_model = NULL;
3019   gint n_pictures;
3020   gint n_photosets;
3021   gint n_groups;
3022   gint n_tags;
3023   g_autoptr(GError) error = NULL;
3024 
3025   g_return_val_if_fail(FROGR_IS_CONTROLLER (self), FALSE);
3026   g_return_val_if_fail(path, FALSE);
3027 
3028   model = frogr_main_view_get_model (self->mainview);
3029 
3030   n_pictures = frogr_model_n_pictures (model);
3031   n_photosets = frogr_model_n_photosets (model);
3032   n_groups = frogr_model_n_groups (model);
3033   n_tags = frogr_model_n_local_tags (model);
3034 
3035   root_node = json_node_new (JSON_NODE_OBJECT);
3036   root_object = json_object_new ();
3037   json_object_set_string_member (root_object, "frogr-version", APP_VERSION);
3038   json_object_set_int_member (root_object, "n_pictures", n_pictures);
3039   json_object_set_int_member (root_object, "n_photosets", n_photosets);
3040   json_object_set_int_member (root_object, "n_groups", n_groups);
3041   json_object_set_int_member (root_object, "n_tags", n_tags);
3042 
3043   DEBUG ("Saving project to file %s:\n"
3044          "\tNumber of pictures: %d\n"
3045          "\tNumber of photosets: %d\n"
3046          "\tNumber of groups: %d\n"
3047          "\tNumber of tags: %d\n",
3048          path, n_pictures, n_photosets, n_groups, n_tags);
3049 
3050   serialized_model = frogr_model_serialize (model);
3051   json_object_set_object_member (root_object, "data", serialized_model);
3052   json_node_set_object (root_node, root_object);
3053 
3054   /* Create a JsonGenerator using the JsonNode as root */
3055   json_gen = json_generator_new ();
3056   json_generator_set_root (json_gen, root_node);
3057 
3058   /* Save to disk */
3059   json_generator_to_file (json_gen, path, &error);
3060 
3061   if (error)
3062     {
3063       DEBUG ("Error serializing current state to %s: %s",
3064              path, error->message);
3065       return FALSE;
3066     }
3067 
3068   frogr_main_view_update_project_path (self->mainview, path);
3069   return TRUE;
3070 }
3071 
3072 void
frogr_controller_set_use_dark_theme(FrogrController * self,gboolean value)3073 frogr_controller_set_use_dark_theme (FrogrController *self, gboolean value)
3074 {
3075   GtkSettings *gtk_settings = NULL;
3076 
3077   gtk_settings = gtk_settings_get_default ();
3078   g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", value, NULL);
3079 }
3080