[gthumb] facebook: added ability to import photos



commit b6b75dbb075e423c787d3db343480340090310f2
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun Dec 23 21:33:00 2012 +0100

    facebook: added ability to import photos
    
    [new feature]

 extensions/facebook/Makefile.am                    |    4 +-
 extensions/facebook/actions.c                      |    9 +
 extensions/facebook/actions.h                      |    1 +
 extensions/facebook/callbacks.c                    |   10 +
 extensions/facebook/data/ui/Makefile.am            |    3 +-
 .../facebook/data/ui/import-from-facebook.ui       |  300 ++++++++++
 extensions/facebook/dlg-import-from-facebook.c     |  607 ++++++++++++++++++++
 extensions/facebook/dlg-import-from-facebook.h     |   29 +
 extensions/facebook/facebook-photo.c               |  473 ++++++++++++----
 extensions/facebook/facebook-photo.h               |   58 +--
 extensions/facebook/facebook-service.c             |  123 ++---
 extensions/facebook/facebook-service.h             |    5 +-
 extensions/facebook/facebook.extension.in.in       |    2 +-
 gthumb/gth-time.c                                  |   61 ++-
 gthumb/gth-time.h                                  |   13 +-
 15 files changed, 1435 insertions(+), 263 deletions(-)
---
diff --git a/extensions/facebook/Makefile.am b/extensions/facebook/Makefile.am
index 3fb505a..9bb8e58 100644
--- a/extensions/facebook/Makefile.am
+++ b/extensions/facebook/Makefile.am
@@ -12,6 +12,8 @@ libfacebook_la_SOURCES = 			\
 	callbacks.h				\
 	dlg-export-to-facebook.c		\
 	dlg-export-to-facebook.h		\
+	dlg-import-from-facebook.c		\
+	dlg-import-from-facebook.h		\
 	facebook-album.c			\
 	facebook-album.h			\
 	facebook-album-properties-dialog.c	\
@@ -26,7 +28,7 @@ libfacebook_la_SOURCES = 			\
 
 libfacebook_la_CFLAGS = $(GTHUMB_CFLAGS) $(LIBSOUP_CFLAGS) $(LIBSECRET_CFLAGS) $(WEBKIT2_CFLAGS) $(JSON_GLIB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
 libfacebook_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
-libfacebook_la_LIBADD = $(GTHUMB_LIBS) $(LIBSOUP_LIBS) $(LIBSECRET_LIBS) $(WEBKIT2_LIBS) $(JSON_GLIB_LIBS) ../export_tools/libexport_tools.la ../oauth/liboauth.la
+libfacebook_la_LIBADD = $(GTHUMB_LIBS) $(LIBSOUP_LIBS) $(LIBSECRET_LIBS) $(WEBKIT2_LIBS) $(JSON_GLIB_LIBS) ../export_tools/libexport_tools.la ../oauth/liboauth.la ../importer/libimporter.la
 libfacebook_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
 
 extensioninidir = $(extensiondir)
diff --git a/extensions/facebook/actions.c b/extensions/facebook/actions.c
index 69939f4..d3162e6 100644
--- a/extensions/facebook/actions.c
+++ b/extensions/facebook/actions.c
@@ -24,6 +24,7 @@
 #include <glib/gi18n.h>
 #include <gthumb.h>
 #include "dlg-export-to-facebook.h"
+#include "dlg-import-from-facebook.h"
 
 
 void
@@ -42,3 +43,11 @@ gth_browser_activate_action_export_facebook (GtkAction  *action,
 	_g_object_list_unref (file_list);
 	_gtk_tree_path_list_free (items);
 }
+
+
+void
+gth_browser_activate_action_import_facebook (GtkAction  *action,
+					     GthBrowser *browser)
+{
+	dlg_import_from_facebook (browser);
+}
diff --git a/extensions/facebook/actions.h b/extensions/facebook/actions.h
index f708c85..c184b9a 100644
--- a/extensions/facebook/actions.h
+++ b/extensions/facebook/actions.h
@@ -27,5 +27,6 @@
 #define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
 
 DEFINE_ACTION(gth_browser_activate_action_export_facebook)
+DEFINE_ACTION(gth_browser_activate_action_import_facebook)
 
 #endif /* ACTIONS_H */
diff --git a/extensions/facebook/callbacks.c b/extensions/facebook/callbacks.c
index bed6103..1c8b087 100644
--- a/extensions/facebook/callbacks.c
+++ b/extensions/facebook/callbacks.c
@@ -34,6 +34,11 @@ static const char *ui_info =
 "<ui>"
 "  <menubar name='MenuBar'>"
 "    <menu name='File' action='FileMenu'>"
+"      <menu name='Import' action='ImportMenu'>"
+"        <placeholder name='Web_Services'>"
+"          <menuitem action='File_Import_Facebook'/>"
+"        </placeholder>"
+"      </menu>"
 "      <menu name='Export' action='ExportMenu'>"
 "        <placeholder name='Web_Services'>"
 "          <menuitem action='File_Export_Facebook'/>"
@@ -50,6 +55,11 @@ static const char *ui_info =
 
 
 static GthActionEntryExt action_entries[] = {
+	{ "File_Import_Facebook", "site-facebook",
+	  N_("Face_book..."), NULL,
+	  N_("Download photos from Facebook"),
+	  GTH_ACTION_FLAG_ALWAYS_SHOW_IMAGE,
+	  G_CALLBACK (gth_browser_activate_action_import_facebook) },
 	{ "File_Export_Facebook", "site-facebook",
 	  N_("Face_book..."), NULL,
 	  N_("Upload photos to Facebook"),
diff --git a/extensions/facebook/data/ui/Makefile.am b/extensions/facebook/data/ui/Makefile.am
index c288b4b..da02cb0 100644
--- a/extensions/facebook/data/ui/Makefile.am
+++ b/extensions/facebook/data/ui/Makefile.am
@@ -2,7 +2,8 @@ uidir = $(pkgdatadir)/ui
 ui_DATA = 					\
 	export-to-facebook.ui			\
 	facebook-album-properties.ui		\
-	facebook-export-completed.ui
+	facebook-export-completed.ui		\
+	import-from-facebook.ui
 
 EXTRA_DIST = $(ui_DATA)
 
diff --git a/extensions/facebook/data/ui/import-from-facebook.ui b/extensions/facebook/data/ui/import-from-facebook.ui
new file mode 100644
index 0000000..82617d7
--- /dev/null
+++ b/extensions/facebook/data/ui/import-from-facebook.ui
@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkListStore" id="account_liststore">
+    <columns>
+      <!-- column-name account -->
+      <column type="GObject"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="album_liststore">
+    <columns>
+      <!-- column-name data -->
+      <column type="GObject"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+      <!-- column-name icon -->
+      <column type="gchararray"/>
+      <!-- column-name size -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkImage" id="download_image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-goto-bottom</property>
+  </object>
+  <object class="GtkDialog" id="import_dialog">
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Import from Picasa Web Album</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox7">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">5</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area7">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="close_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="download_button">
+                <property name="label" translatable="yes">_Import</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">download_image</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkTable" id="table1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="n_rows">2</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">6</property>
+                    <property name="row_spacing">5</property>
+                    <child>
+                      <object class="GtkVBox" id="vbox4">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">3</property>
+                        <child>
+                          <object class="GtkHBox" id="hbox4">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkComboBox" id="account_combobox">
+                                <property name="width_request">300</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="model">account_liststore</property>
+                                <child>
+                                  <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                                  <attributes>
+                                    <attribute name="text">1</attribute>
+                                  </attributes>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="edit_accounts_button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="tooltip_text" translatable="yes">Edit accounts</property>
+                                <child>
+                                  <object class="GtkImage" id="image2">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="icon_name">view-list-symbolic</property>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label3">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">A_ccount:</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Album:</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="album_combobox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="model">album_liststore</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="images_box">
+                <property name="width_request">460</property>
+                <property name="height_request">250</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">3</property>
+                <child>
+                  <object class="GtkLabel" id="images_info_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHBox" id="hbox2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">_Destination:</property>
+                    <property name="use_underline">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHBox" id="destination_button_box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">close_button</action-widget>
+      <action-widget response="-5">download_button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/extensions/facebook/dlg-import-from-facebook.c b/extensions/facebook/dlg-import-from-facebook.c
new file mode 100644
index 0000000..7197bfd
--- /dev/null
+++ b/extensions/facebook/dlg-import-from-facebook.c
@@ -0,0 +1,607 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 The Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#define GDK_PIXBUF_ENABLE_BACKEND
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include <extensions/importer/importer.h>
+#include <extensions/oauth/oauth.h>
+#include "dlg-import-from-facebook.h"
+#include "facebook-album.h"
+#include "facebook-photo.h"
+#include "facebook-service.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (data->builder, (x)))
+#define FAKE_SIZE 100000
+#define THUMBNAIL_SIZE 110
+
+
+enum {
+	ACCOUNT_DATA_COLUMN,
+	ACCOUNT_NAME_COLUMN
+};
+
+
+enum {
+	ALBUM_DATA_COLUMN,
+	ALBUM_NAME_COLUMN,
+	ALBUM_ICON_COLUMN,
+	ALBUM_SIZE_COLUMN
+};
+
+
+typedef struct {
+	GthBrowser           *browser;
+	GthFileData          *location;
+	GtkBuilder           *builder;
+	GtkWidget            *dialog;
+	GtkWidget            *preferences_dialog;
+	GtkWidget            *progress_dialog;
+	FacebookService      *service;
+	GtkWidget            *file_list;
+	GList                *albums;
+	FacebookAlbum        *album;
+	GList                *photos;
+	GCancellable         *cancellable;
+} DialogData;
+
+
+static void
+import_dialog_destroy_cb (GtkWidget  *widget,
+			  DialogData *data)
+{
+	if (data->service != NULL)
+		gth_task_completed (GTH_TASK (data->service), NULL);
+	_g_object_unref (data->cancellable);
+	_g_object_unref (data->service);
+	_g_object_list_unref (data->albums);
+	_g_object_unref (data->album);
+	_g_object_list_unref (data->photos);
+	gtk_widget_destroy (data->progress_dialog);
+	_g_object_unref (data->builder);
+	_g_object_unref (data->location);
+	g_free (data);
+}
+
+
+static GList *
+get_files_to_download (DialogData *data)
+{
+	GthFileView *file_view;
+	GList       *selected;
+	GList       *file_list;
+
+	file_view = (GthFileView *) gth_file_list_get_view (GTH_FILE_LIST (data->file_list));
+	selected = gth_file_selection_get_selected (GTH_FILE_SELECTION (file_view));
+	if (selected != NULL)
+		file_list = gth_file_list_get_files (GTH_FILE_LIST (data->file_list), selected);
+	else
+		file_list = gth_file_store_get_visibles (GTH_FILE_STORE (gth_file_view_get_model (file_view)));
+
+	_gtk_tree_path_list_free (selected);
+
+	return file_list;
+}
+
+
+static void
+import_dialog_response_cb (GtkDialog *dialog,
+			   int        response_id,
+			   gpointer   user_data)
+{
+	DialogData *data = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gth_file_list_cancel (GTH_FILE_LIST (data->file_list), (DataFunc) gtk_widget_destroy, data->dialog);
+		break;
+
+	case GTK_RESPONSE_OK:
+		{
+			GtkTreeIter    iter;
+			FacebookAlbum *album;
+			GList         *file_list;
+
+			if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("album_combobox")), &iter)) {
+				gtk_widget_set_sensitive (GET_WIDGET ("download_button"), FALSE);
+				return;
+			}
+
+			gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("album_liststore")), &iter,
+					    ALBUM_DATA_COLUMN, &album,
+					    -1);
+
+			file_list = get_files_to_download (data);
+			if (file_list != NULL) {
+				GSettings           *settings;
+				GFile               *destination;
+				gboolean             single_subfolder;
+				GthSubfolderType     subfolder_type;
+				GthSubfolderFormat   subfolder_format;
+				char                *custom_format;
+				GthTask             *task;
+
+				settings = g_settings_new (GTHUMB_IMPORTER_SCHEMA);
+				destination = gth_import_preferences_get_destination ();
+				subfolder_type = g_settings_get_enum (settings, PREF_IMPORTER_SUBFOLDER_TYPE);
+				subfolder_format = g_settings_get_enum (settings, PREF_IMPORTER_SUBFOLDER_FORMAT);
+				single_subfolder = g_settings_get_boolean (settings, PREF_IMPORTER_SUBFOLDER_SINGLE);
+				custom_format = g_settings_get_string (settings, PREF_IMPORTER_SUBFOLDER_CUSTOM_FORMAT);
+
+				task = gth_import_task_new (data->browser,
+							    file_list,
+							    destination,
+							    subfolder_type,
+							    subfolder_format,
+							    single_subfolder,
+							    custom_format,
+							    (album->name != NULL ? album->name : ""),
+							    NULL,
+							    FALSE,
+							    FALSE,
+							    FALSE);
+				gth_browser_exec_task (data->browser, task, FALSE);
+				gtk_widget_destroy (data->dialog);
+
+				g_object_unref (task);
+				_g_object_unref (destination);
+				g_object_unref (settings);
+			}
+
+			_g_object_list_unref (file_list);
+			g_object_unref (album);
+		}
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+update_account_list (DialogData *data)
+{
+	int           current_account_idx;
+	OAuthAccount *current_account;
+	int           idx;
+	GList        *scan;
+	GtkTreeIter   iter;
+
+	gtk_list_store_clear (GTK_LIST_STORE (GET_WIDGET ("account_liststore")));
+
+	current_account_idx = 0;
+	current_account = web_service_get_current_account (WEB_SERVICE (data->service));
+	for (scan = web_service_get_accounts (WEB_SERVICE (data->service)), idx = 0; scan; scan = scan->next, idx++) {
+		OAuthAccount *account = scan->data;
+
+		if (oauth_account_cmp (current_account, account) == 0)
+			current_account_idx = idx;
+
+		gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter);
+		gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter,
+				    ACCOUNT_DATA_COLUMN, account,
+				    ACCOUNT_NAME_COLUMN, account->name,
+				    -1);
+	}
+	gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("account_combobox")), current_account_idx);
+}
+
+
+static void
+get_albums_ready_cb (GObject      *source_object,
+		     GAsyncResult *res,
+		     gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
+	GList      *scan;
+
+	_g_object_list_unref (data->albums);
+	data->albums = facebook_service_get_albums_finish (data->service, res, &error);
+	if (error != NULL) {
+		if (data->service != NULL)
+			gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
+		_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->browser), _("Could not connect to the server"), error);
+		g_clear_error (&error);
+		gtk_widget_destroy (data->dialog);
+		return;
+	}
+
+	gtk_list_store_clear (GTK_LIST_STORE (GET_WIDGET ("album_liststore")));
+	for (scan = data->albums; scan; scan = scan->next) {
+		FacebookAlbum *album = scan->data;
+		char          *n_photos;
+		GtkTreeIter    iter;
+
+		n_photos = g_strdup_printf ("(%d)", album->count);
+
+		gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("album_liststore")), &iter);
+		gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("album_liststore")), &iter,
+				    ALBUM_DATA_COLUMN, album,
+				    ALBUM_ICON_COLUMN, "file-catalog",
+				    ALBUM_NAME_COLUMN, album->name,
+				    ALBUM_SIZE_COLUMN, n_photos,
+				    -1);
+
+		g_free (n_photos);
+	}
+
+	gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
+
+	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (data->browser));
+	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+	gtk_window_present (GTK_WINDOW (data->dialog));
+}
+
+
+static void
+authentication_ready_cb (WebService *service,
+			 DialogData *data)
+{
+	update_account_list (data);
+	facebook_service_get_albums (data->service,
+				     data->cancellable,
+				     get_albums_ready_cb,
+				     data);
+}
+
+
+static void
+authentication_accounts_changed_cb (WebService *service,
+				    DialogData *data)
+{
+	update_account_list (data);
+}
+
+
+static void
+edit_accounts_button_clicked_cb (GtkButton  *button,
+				 DialogData *data)
+{
+	web_service_edit_accounts (WEB_SERVICE (data->service), GTK_WINDOW (data->dialog));
+}
+
+
+static void
+account_combobox_changed_cb (GtkComboBox *widget,
+			     gpointer     user_data)
+{
+	DialogData   *data = user_data;
+	GtkTreeIter   iter;
+	OAuthAccount *account;
+
+	if (! gtk_combo_box_get_active_iter (widget, &iter))
+		return;
+
+	gtk_tree_model_get (gtk_combo_box_get_model (widget),
+			    &iter,
+			    ACCOUNT_DATA_COLUMN, &account,
+			    -1);
+
+	if (oauth_account_cmp (account, web_service_get_current_account (WEB_SERVICE (data->service))) != 0)
+		web_service_connect (WEB_SERVICE (data->service), account);
+
+	g_object_unref (account);
+}
+
+
+static void
+update_selection_status (DialogData *data)
+{
+	GList *file_list;
+	int    n_selected;
+	char  *text_selected;
+
+	file_list = get_files_to_download (data);
+	n_selected = g_list_length (file_list);
+	text_selected = g_strdup_printf (g_dngettext (NULL, "%d file", "%d files", n_selected), n_selected);
+	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("images_info_label")), text_selected);
+
+	g_free (text_selected);
+	_g_object_list_unref (file_list);
+}
+
+
+static void
+list_photos_ready_cb (GObject      *source_object,
+		      GAsyncResult *result,
+		      gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
+	GList      *list;
+	GList      *scan;
+
+	gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
+
+	_g_object_list_unref (data->photos);
+	data->photos = facebook_service_list_photos_finish (data->service, result, &error);
+	if (error != NULL) {
+		if (data->service != NULL)
+			gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not get the photo list"), error);
+		g_clear_error (&error);
+		gtk_widget_destroy (data->dialog);
+		return;
+	}
+
+	list = NULL;
+	for (scan = data->photos; scan; scan = scan->next) {
+		FacebookPhoto *photo = scan->data;
+		GthFileData   *file_data;
+
+		file_data = gth_file_data_new_for_uri (facebook_photo_get_original_url (photo), "image/jpeg");
+		g_file_info_set_file_type (file_data->info, G_FILE_TYPE_REGULAR);
+		g_file_info_set_size (file_data->info, FAKE_SIZE); /* set a fake size to make the progress dialog work correctly */
+		g_file_info_set_attribute_object (file_data->info, "facebook::object", G_OBJECT (photo));
+
+		list = g_list_prepend (list, file_data);
+	}
+	gth_file_list_set_files (GTH_FILE_LIST (data->file_list), list);
+	update_selection_status (data);
+	gtk_widget_set_sensitive (GET_WIDGET ("download_button"), list != NULL);
+
+	_g_object_list_unref (list);
+}
+
+
+static void
+album_combobox_changed_cb (GtkComboBox *widget,
+			   gpointer     user_data)
+{
+	DialogData  *data = user_data;
+	GtkTreeIter  iter;
+
+	if (! gtk_combo_box_get_active_iter (widget, &iter)) {
+		gth_file_list_clear (GTH_FILE_LIST (data->file_list), _("No album selected"));
+		return;
+	}
+
+	_g_object_unref (data->album);
+	gtk_tree_model_get (gtk_combo_box_get_model (widget),
+			    &iter,
+			    ALBUM_DATA_COLUMN, &data->album,
+			    -1);
+
+	gth_import_preferences_dialog_set_event (GTH_IMPORT_PREFERENCES_DIALOG (data->preferences_dialog), data->album->name);
+
+	gth_task_dialog (GTH_TASK (data->service), FALSE, NULL);
+	facebook_service_list_photos (data->service,
+				      data->album,
+				      -1,
+				      NULL,
+				      data->cancellable,
+				      list_photos_ready_cb,
+				      data);
+}
+
+
+static GthImage *
+facebook_thumbnail_loader (GInputStream  *istream,
+			   GthFileData   *file_data,
+			   int            requested_size,
+			   int           *original_width,
+			   int           *original_height,
+			   gpointer       user_data,
+			   GCancellable  *cancellable,
+			   GError       **error)
+{
+	GthImage      *image = NULL;
+	FacebookPhoto *photo;
+	const char    *uri;
+
+	photo = (FacebookPhoto *) g_file_info_get_attribute_object (file_data->info, "facebook::object");
+
+	uri = facebook_photo_get_thumbnail_url (photo, requested_size);
+	if (uri == NULL)
+		uri = facebook_photo_get_original_url (photo);
+
+	if (uri != NULL) {
+		GFile *file;
+		void  *buffer;
+		gsize  size;
+
+		file = g_file_new_for_uri (uri);
+		if (_g_file_load_in_buffer (file, &buffer, &size, cancellable, error)) {
+			GInputStream *stream;
+			GdkPixbuf    *pixbuf;
+
+			stream = g_memory_input_stream_new_from_data (buffer, size, g_free);
+			pixbuf = gdk_pixbuf_new_from_stream (stream, cancellable, error);
+			if (pixbuf != NULL) {
+				GdkPixbuf *rotated;
+
+				rotated = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+				g_object_unref (pixbuf);
+				pixbuf = rotated;
+
+				image = gth_image_new_for_pixbuf (pixbuf);
+			}
+
+			g_object_unref (pixbuf);
+			g_object_unref (stream);
+		}
+
+		g_object_unref (file);
+	}
+	else
+		*error = g_error_new_literal (GTH_ERROR, 0, "cannot generate the thumbnail");
+
+	return image;
+}
+
+
+static int
+facebook_photo_position_func (GthFileData *a,
+			      GthFileData *b)
+{
+	FacebookPhoto *photo_a;
+	FacebookPhoto *photo_b;
+
+	photo_a = (FacebookPhoto *) g_file_info_get_attribute_object (a->info, "facebook::object");
+	photo_b = (FacebookPhoto *) g_file_info_get_attribute_object (b->info, "facebook::object");
+
+	if (photo_a->position == photo_b->position)
+		return strcmp (photo_a->id, photo_b->id);
+	else if (photo_a->position > photo_b->position)
+		return 1;
+	else
+		return -1;
+}
+
+
+static void
+file_list_selection_changed_cb (GthFileView *file_view,
+				gpointer     user_data)
+{
+	update_selection_status ((DialogData *) user_data);
+}
+
+
+void
+dlg_import_from_facebook (GthBrowser *browser)
+{
+	DialogData     *data;
+	GthThumbLoader *thumb_loader;
+	char           *title;
+
+	data = g_new0 (DialogData, 1);
+	data->browser = browser;
+	data->location = gth_file_data_dup (gth_browser_get_location_data (browser));
+	data->builder = _gtk_builder_new_from_file ("import-from-facebook.ui", "facebook");
+	data->dialog = _gtk_builder_get_widget (data->builder, "import_dialog");
+	data->cancellable = g_cancellable_new ();
+
+	{
+		GtkCellLayout   *cell_layout;
+		GtkCellRenderer *renderer;
+
+		cell_layout = GTK_CELL_LAYOUT (GET_WIDGET ("album_combobox"));
+
+		renderer = gtk_cell_renderer_pixbuf_new ();
+		gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+		gtk_cell_layout_set_attributes (cell_layout, renderer,
+						"icon-name", ALBUM_ICON_COLUMN,
+						NULL);
+
+		renderer = gtk_cell_renderer_text_new ();
+		gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+		gtk_cell_layout_set_attributes (cell_layout, renderer,
+						"text", ALBUM_NAME_COLUMN,
+						NULL);
+
+		renderer = gtk_cell_renderer_text_new ();
+		gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+		gtk_cell_layout_set_attributes (cell_layout, renderer,
+						"text", ALBUM_SIZE_COLUMN,
+						NULL);
+	}
+
+	/* Set the widget data */
+
+	data->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_MODE_NORMAL, FALSE);
+	thumb_loader = gth_file_list_get_thumb_loader (GTH_FILE_LIST (data->file_list));
+	gth_thumb_loader_set_use_cache (thumb_loader, FALSE);
+	gth_thumb_loader_set_loader_func (thumb_loader, facebook_thumbnail_loader);
+	gth_file_list_set_thumb_size (GTH_FILE_LIST (data->file_list), THUMBNAIL_SIZE);
+	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->file_list), TRUE);
+	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (data->file_list), TRUE);
+	gth_file_list_set_caption (GTH_FILE_LIST (data->file_list), "none");
+	gth_file_list_set_sort_func (GTH_FILE_LIST (data->file_list), facebook_photo_position_func, FALSE);
+	gth_file_list_clear (GTH_FILE_LIST (data->file_list), _("No album selected"));
+	gtk_widget_show (data->file_list);
+	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("images_box")), data->file_list, TRUE, TRUE, 0);
+
+	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (GET_WIDGET ("album_liststore")), ALBUM_NAME_COLUMN, GTK_SORT_ASCENDING);
+
+	gtk_widget_set_sensitive (GET_WIDGET ("download_button"), FALSE);
+
+	data->preferences_dialog = gth_import_preferences_dialog_new ();
+	gtk_window_set_transient_for (GTK_WINDOW (data->preferences_dialog), GTK_WINDOW (data->dialog));
+
+	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("destination_button_box")),
+			    gth_import_destination_button_new (GTH_IMPORT_PREFERENCES_DIALOG (data->preferences_dialog)),
+			    TRUE,
+			    TRUE,
+			    0);
+	gtk_widget_show_all (GET_WIDGET ("destination_button_box"));
+
+	title = g_strdup_printf (_("Import from %s"), _("Facebook"));
+	gtk_window_set_title (GTK_WINDOW (data->dialog), title);
+	g_free (title);
+
+	_gtk_window_resize_to_fit_screen_height (data->dialog, 500);
+
+	/* Set the signals handlers. */
+
+	g_signal_connect (data->dialog,
+			  "destroy",
+			  G_CALLBACK (import_dialog_destroy_cb),
+			  data);
+	g_signal_connect (data->dialog,
+			  "delete-event",
+			  G_CALLBACK (gtk_true),
+			  NULL);
+	g_signal_connect (data->dialog,
+			  "response",
+			  G_CALLBACK (import_dialog_response_cb),
+			  data);
+	g_signal_connect (GET_WIDGET ("edit_accounts_button"),
+			  "clicked",
+			  G_CALLBACK (edit_accounts_button_clicked_cb),
+			  data);
+	g_signal_connect (GET_WIDGET ("account_combobox"),
+			  "changed",
+			  G_CALLBACK (account_combobox_changed_cb),
+			  data);
+	g_signal_connect (GET_WIDGET ("album_combobox"),
+			  "changed",
+			  G_CALLBACK (album_combobox_changed_cb),
+			  data);
+	g_signal_connect (gth_file_list_get_view (GTH_FILE_LIST (data->file_list)),
+			  "file-selection-changed",
+			  G_CALLBACK (file_list_selection_changed_cb),
+			  data);
+
+	update_selection_status (data);
+	gth_import_preferences_dialog_set_event (GTH_IMPORT_PREFERENCES_DIALOG (data->preferences_dialog), "");
+
+	data->service = facebook_service_new (data->cancellable,
+					      GTK_WIDGET (data->browser),
+					      data->dialog);
+	g_signal_connect (data->service,
+			  "account-ready",
+			  G_CALLBACK (authentication_ready_cb),
+			  data);
+	g_signal_connect (data->service,
+			  "accounts-changed",
+			  G_CALLBACK (authentication_accounts_changed_cb),
+			  data);
+
+	data->progress_dialog = gth_progress_dialog_new (GTK_WINDOW (data->browser));
+	gth_progress_dialog_add_task (GTH_PROGRESS_DIALOG (data->progress_dialog), GTH_TASK (data->service));
+
+	web_service_autoconnect (WEB_SERVICE (data->service));
+}
diff --git a/extensions/facebook/dlg-import-from-facebook.h b/extensions/facebook/dlg-import-from-facebook.h
new file mode 100644
index 0000000..fc0a97d
--- /dev/null
+++ b/extensions/facebook/dlg-import-from-facebook.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 The Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DLG_IMPORT_FROM_FACEBOOK_H
+#define DLG_IMPORT_FROM_FACEBOOK_H
+
+#include <gthumb.h>
+
+void dlg_import_from_facebook (GthBrowser *browser);
+
+#endif /* DLG_IMPORT_FROM_FACEBOOK_H */
diff --git a/extensions/facebook/facebook-photo.c b/extensions/facebook/facebook-photo.c
index 052739d..4f75b7a 100644
--- a/extensions/facebook/facebook-photo.c
+++ b/extensions/facebook/facebook-photo.c
@@ -3,7 +3,7 @@
 /*
  *  GThumb
  *
- *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *  Copyright (C) 2010-2012 Free Software Foundation, Inc.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -22,202 +22,435 @@
 #include <config.h>
 #include <stdlib.h>
 #include <string.h>
+#include <json-glib/json-glib.h>
 #include <gthumb.h>
 #include "facebook-photo.h"
 
 
-static void facebook_photo_dom_domizable_interface_init (DomDomizableInterface *iface);
+static void facebook_photo_json_serializable_interface_init (JsonSerializableIface *iface);
 
 
 G_DEFINE_TYPE_WITH_CODE (FacebookPhoto,
 			 facebook_photo,
 			 G_TYPE_OBJECT,
-			 G_IMPLEMENT_INTERFACE (DOM_TYPE_DOMIZABLE,
-					        facebook_photo_dom_domizable_interface_init))
+			 G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
+					 	facebook_photo_json_serializable_interface_init))
 
 
-static void
-facebook_photo_finalize (GObject *obj)
+enum {
+        PROP_0,
+        PROP_ID,
+        PROP_PICTURE,
+        PROP_SOURCE,
+        PROP_WIDTH,
+        PROP_HEIGHT,
+        PROP_LINK,
+        PROP_CREATED_TIME,
+        PROP_UPDATED_TIME,
+        PROP_IMAGES
+};
+
+
+/* -- facebook_image -- */
+
+
+static FacebookImage *
+facebook_image_new (void)
 {
-	FacebookPhoto *self;
+	FacebookImage *image;
 
-	self = FACEBOOK_PHOTO (obj);
+	image = g_new (FacebookImage, 1);
+	image->source = NULL;
+	image->width = 0;
+	image->height = 0;
 
-	g_free (self->id);
-	g_free (self->secret);
-	g_free (self->server);
-	g_free (self->title);
+	return image;
+}
 
-	G_OBJECT_CLASS (facebook_photo_parent_class)->finalize (obj);
+
+static FacebookImage *
+facebook_image_copy (FacebookImage *source)
+{
+	FacebookImage *dest;
+
+	dest = facebook_image_new ();
+	_g_strset (&dest->source, source->source);
+	dest->width = source->width;
+	dest->height = source->height;
+
+	return dest;
 }
 
 
 static void
-facebook_photo_class_init (FacebookPhotoClass *klass)
+facebook_image_free (FacebookImage *image)
 {
-	G_OBJECT_CLASS (klass)->finalize = facebook_photo_finalize;
+	g_free (image->source);
+	g_free (image);
 }
 
 
-static DomElement*
-facebook_photo_create_element (DomDomizable *base,
-				DomDocument  *doc)
+/* -- facebook_image_list -- */
+
+
+#define FACEBOOK_TYPE_IMAGE_LIST (facebook_image_list_get_type ())
+
+
+static GList *
+facebook_image_list_copy (GList *source)
 {
-	FacebookPhoto *self;
-	DomElement  *element;
-
-	self = FACEBOOK_PHOTO (base);
-
-	element = dom_document_create_element (doc, "photo", NULL);
-	if (self->id != NULL)
-		dom_element_set_attribute (element, "id", self->id);
-	if (self->secret != NULL)
-		dom_element_set_attribute (element, "secret", self->secret);
-	if (self->server != NULL)
-		dom_element_set_attribute (element, "server", self->server);
-	if (self->title != NULL)
-		dom_element_set_attribute (element, "title", self->title);
-	if (self->is_primary)
-		dom_element_set_attribute (element, "isprimary", "1");
-
-	return element;
+	return g_list_copy_deep (source, (GCopyFunc) facebook_image_copy, NULL);
 }
 
 
 static void
-facebook_photo_load_from_element (DomDomizable *base,
-				DomElement   *element)
+facebook_image_list_free (GList *images)
 {
-	FacebookPhoto *self;
-
-	if ((element == NULL) || (g_strcmp0 (element->tag_name, "photo") != 0))
-		return;
-
-	self = FACEBOOK_PHOTO (base);
-
-	facebook_photo_set_id (self, dom_element_get_attribute (element, "id"));
-	facebook_photo_set_secret (self, dom_element_get_attribute (element, "secret"));
-	facebook_photo_set_server (self, dom_element_get_attribute (element, "server"));
-	facebook_photo_set_title (self, dom_element_get_attribute (element, "title"));
-	facebook_photo_set_is_primary (self, dom_element_get_attribute (element, "isprimary"));
-	facebook_photo_set_url_sq (self, dom_element_get_attribute (element, "url_sq"));
-	facebook_photo_set_url_t (self, dom_element_get_attribute (element, "url_t"));
-	facebook_photo_set_url_s (self, dom_element_get_attribute (element, "url_s"));
-	facebook_photo_set_url_m (self, dom_element_get_attribute (element, "url_m"));
-	facebook_photo_set_url_o (self, dom_element_get_attribute (element, "url_o"));
+	g_list_foreach (images, (GFunc) facebook_image_free, NULL);
+	g_list_free (images);
 }
 
 
+G_DEFINE_BOXED_TYPE (GList, facebook_image_list, facebook_image_list_copy, facebook_image_list_free)
+
+
+/* -- facebook_photo -- */
+
+
 static void
-facebook_photo_dom_domizable_interface_init (DomDomizableInterface *iface)
+facebook_photo_set_property (GObject      *object,
+			     guint         property_id,
+			     const GValue *value,
+			     GParamSpec   *pspec)
 {
-	iface->create_element = facebook_photo_create_element;
-	iface->load_from_element = facebook_photo_load_from_element;
+	FacebookPhoto *self;
+
+	self = FACEBOOK_PHOTO (object);
+
+	switch (property_id) {
+	case PROP_ID:
+		_g_strset (&self->id, g_value_get_string (value));
+		break;
+	case PROP_PICTURE:
+		_g_strset (&self->picture, g_value_get_string (value));
+		break;
+	case PROP_SOURCE:
+		_g_strset (&self->source, g_value_get_string (value));
+		break;
+	case PROP_WIDTH:
+		self->width = g_value_get_int (value);
+		break;
+	case PROP_HEIGHT:
+		self->height = g_value_get_int (value);
+		break;
+	case PROP_LINK:
+		_g_strset (&self->link, g_value_get_string (value));
+		break;
+	case PROP_CREATED_TIME:
+		gth_datetime_free (self->created_time);
+		self->created_time = g_value_dup_boxed (value);
+		break;
+	case PROP_UPDATED_TIME:
+		gth_datetime_free (self->updated_time);
+		self->updated_time = g_value_dup_boxed (value);
+		break;
+	case PROP_IMAGES:
+		facebook_image_list_free (self->images);
+		self->images = g_value_dup_boxed (value);
+		break;
+	default:
+		break;
+	}
 }
 
 
 static void
-facebook_photo_init (FacebookPhoto *self)
+facebook_photo_get_property (GObject    *object,
+			     guint       property_id,
+			     GValue     *value,
+			     GParamSpec *pspec)
 {
-	/* void */
+	FacebookPhoto *self;
+
+	self = FACEBOOK_PHOTO (object);
+
+	switch (property_id) {
+	case PROP_ID:
+		g_value_set_string (value, self->id);
+		break;
+	case PROP_PICTURE:
+		g_value_set_string (value, self->picture);
+		break;
+	case PROP_SOURCE:
+		g_value_set_string (value, self->source);
+		break;
+	case PROP_WIDTH:
+		g_value_set_int (value, self->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_int (value, self->height);
+		break;
+	case PROP_LINK:
+		g_value_set_string (value, self->link);
+		break;
+	case PROP_CREATED_TIME:
+		g_value_set_boxed (value, self->created_time);
+		break;
+	case PROP_UPDATED_TIME:
+		g_value_set_boxed (value, self->updated_time);
+		break;
+	case PROP_IMAGES:
+		g_value_set_boxed (value, self->images);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
 }
 
 
-FacebookPhoto *
-facebook_photo_new (void)
+static void
+facebook_photo_finalize (GObject *obj)
 {
-	return g_object_new (FACEBOOK_TYPE_PHOTO, NULL);
-}
+	FacebookPhoto *self;
 
+	self = FACEBOOK_PHOTO (obj);
 
-void
-facebook_photo_set_id (FacebookPhoto *self,
-		     const char  *value)
-{
-	_g_strset (&self->id, value);
+	g_free (self->id);
+	g_free (self->picture);
+	g_free (self->source);
+	g_free (self->link);
+	gth_datetime_free (self->created_time);
+	gth_datetime_free (self->updated_time);
+	facebook_image_list_free (self->images);
+
+	G_OBJECT_CLASS (facebook_photo_parent_class)->finalize (obj);
 }
 
 
-void
-facebook_photo_set_secret (FacebookPhoto *self,
-			 const char  *value)
+static void
+facebook_photo_class_init (FacebookPhotoClass *klass)
 {
-	_g_strset (&self->secret, value);
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = facebook_photo_finalize;
+	object_class->set_property = facebook_photo_set_property;
+	object_class->get_property = facebook_photo_get_property;
+
+	/* properties */
+
+	g_object_class_install_property (object_class,
+					 PROP_ID,
+					 g_param_spec_string ("id",
+                                                              "ID",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_PICTURE,
+					 g_param_spec_string ("picture",
+                                                              "Picture",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+				 	 PROP_SOURCE,
+					 g_param_spec_string ("source",
+                                                              "Source",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_WIDTH,
+					 g_param_spec_int ("width",
+                                                           "Width",
+                                                           "",
+                                                           0,
+                                                           G_MAXINT,
+                                                           0,
+                                                           G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_HEIGHT,
+					 g_param_spec_int ("height",
+                                                           "Height",
+                                                           "",
+                                                           0,
+                                                           G_MAXINT,
+                                                           0,
+                                                           G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+				 	 PROP_LINK,
+					 g_param_spec_string ("link",
+                                                              "Link",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+				 	 PROP_CREATED_TIME,
+					 g_param_spec_boxed ("created-time",
+                                                             "Created time",
+                                                             "",
+                                                             GTH_TYPE_DATETIME,
+                                                             G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+				 	 PROP_UPDATED_TIME,
+				 	 g_param_spec_boxed ("updated-time",
+				 			     "Updated time",
+				 			     "",
+				 			     GTH_TYPE_DATETIME,
+				 			     G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+				 	 PROP_IMAGES,
+				 	 g_param_spec_boxed ("images",
+				 			     "Images",
+				 			     "",
+				 			     FACEBOOK_TYPE_IMAGE_LIST,
+				 			     G_PARAM_READWRITE));
 }
 
 
-void
-facebook_photo_set_server (FacebookPhoto *self,
-			 const char  *value)
+static gboolean
+facebook_photo_deserialize_property (JsonSerializable *serializable,
+                                     const gchar      *property_name,
+                                     GValue           *value,
+                                     GParamSpec       *pspec,
+                                     JsonNode         *property_node)
 {
-	_g_strset (&self->server, value);
-}
+	FacebookPhoto *self = FACEBOOK_PHOTO (serializable);
 
+	if (pspec->value_type == GTH_TYPE_DATETIME) {
+		GTimeVal timeval;
 
-void
-facebook_photo_set_title (FacebookPhoto *self,
-			const char  *value)
-{
-	_g_strset (&self->title, value);
-}
+		if (g_time_val_from_iso8601 (json_node_get_string (property_node), &timeval)) {
+			GthDateTime *datetime;
 
+			datetime = gth_datetime_new ();
+			gth_datetime_from_timeval (datetime, &timeval);
+			g_object_set (self, property_name, datetime, NULL);
 
-void
-facebook_photo_set_is_primary (FacebookPhoto *self,
-			     const char  *value)
-{
-	self->is_primary = (g_strcmp0 (value, "1") == 0);
-}
+			gth_datetime_free (datetime);
 
+			return TRUE;
+		}
 
-void
-facebook_photo_set_url_sq (FacebookPhoto *self,
-			 const char  *value)
-{
-	_g_strset (&self->url_sq, value);
+		return FALSE;
+	}
+
+	if (pspec->value_type == FACEBOOK_TYPE_IMAGE_LIST) {
+		GList     *images = NULL;
+		JsonArray *array;
+		int        i;
+
+		array = json_node_get_array (property_node);
+		for (i = 0; i < json_array_get_length (array); i++) {
+			JsonObject *image_obj;
+
+			image_obj = json_array_get_object_element (array, i);
+			if (image_obj != NULL) {
+				FacebookImage *image;
+
+				image = facebook_image_new ();
+				_g_strset (&image->source, json_object_get_string_member (image_obj, "source"));
+				image->width = json_object_get_int_member (image_obj, "width");
+				image->height = json_object_get_int_member (image_obj, "height");
+
+				images = g_list_prepend (images, image);
+			}
+		}
+
+		images = g_list_reverse (images);
+		g_object_set (self, property_name, images, NULL);
+
+		facebook_image_list_free (images);
+
+		return TRUE;
+	}
+
+	return json_serializable_default_deserialize_property (serializable,
+							       property_name,
+							       value,
+							       pspec,
+							       property_node);
 }
 
 
-void
-facebook_photo_set_url_t (FacebookPhoto *self,
-			const char  *value)
+static void
+facebook_photo_json_serializable_interface_init (JsonSerializableIface *iface)
 {
-	_g_strset (&self->url_t, value);
+	iface->deserialize_property = facebook_photo_deserialize_property;
 }
 
 
-void
-facebook_photo_set_url_s (FacebookPhoto *self,
-			const char  *value)
+static void
+facebook_photo_init (FacebookPhoto *self)
 {
-	_g_strset (&self->url_s, value);
+	self->id = NULL;
+	self->picture = NULL;
+	self->source = NULL;
+	self->width = 0;
+	self->height = 0;
+	self->link = NULL;
+	self->created_time = NULL;
+	self->updated_time = NULL;
+	self->images = NULL;
 }
 
 
-void
-facebook_photo_set_url_m (FacebookPhoto *self,
-			const char  *value)
+FacebookPhoto *
+facebook_photo_new (void)
 {
-	_g_strset (&self->url_m, value);
+	return g_object_new (FACEBOOK_TYPE_PHOTO, NULL);
 }
 
 
-void
-facebook_photo_set_url_o (FacebookPhoto *self,
-			const char  *value)
+const char *
+facebook_photo_get_original_url	(FacebookPhoto *photo)
 {
-	_g_strset (&self->url_o, value);
+	char   *url;
+	GList  *scan;
+	glong   max_size;
+
+	url = photo->source;
+	max_size = photo->width * photo->height;
+
+	for (scan = photo->images; scan; scan = scan->next) {
+		FacebookImage *image = scan->data;
+		glong          image_size;
+
+		image_size = image->width * image->height;
+		if (image_size > max_size) {
+			max_size = image_size;
+			url = image->source;
+		}
+	}
+
+	return url;
 }
 
 
-void
-facebook_photo_set_original_format (FacebookPhoto *self,
-				  const char  *value)
+const char *
+facebook_photo_get_thumbnail_url (FacebookPhoto *photo,
+				  int            requested_size)
 {
-	_g_strset (&self->original_format, value);
-
-	g_free (self->mime_type);
-	self->mime_type = NULL;
-	if (self->original_format != NULL)
-		self->mime_type = g_strconcat ("image/", self->original_format, NULL);
+	char   *url;
+	GList  *scan;
+	glong   min_delta;
+
+	url = photo->picture;
+	requested_size = requested_size * requested_size;
+	min_delta = 0;
+
+	for (scan = photo->images; scan; scan = scan->next) {
+		FacebookImage *image = scan->data;
+		glong          image_delta;
+
+		image_delta = labs ((image->width * image->height) - requested_size);
+		if ((scan == photo->images) || (image_delta < min_delta)) {
+			min_delta = image_delta;
+			url = image->source;
+		}
+	}
+
+	return url;
 }
diff --git a/extensions/facebook/facebook-photo.h b/extensions/facebook/facebook-photo.h
index fdf6e04..89d0dc1 100644
--- a/extensions/facebook/facebook-photo.h
+++ b/extensions/facebook/facebook-photo.h
@@ -38,53 +38,37 @@ typedef struct _FacebookPhoto FacebookPhoto;
 typedef struct _FacebookPhotoClass FacebookPhotoClass;
 typedef struct _FacebookPhotoPrivate FacebookPhotoPrivate;
 
+typedef struct {
+	char *source;
+	int   width;
+	int   height;
+} FacebookImage;
+
 struct _FacebookPhoto {
 	GObject parent_instance;
 	FacebookPhotoPrivate *priv;
 
-	char            *id;
-	char            *secret;
-	char            *server;
-	char            *title;
-	gboolean         is_primary;
-	char            *url_sq;
-	char            *url_t;
-	char            *url_s;
-	char            *url_m;
-	char            *url_o;
-	char            *original_format;
-	char            *mime_type;
-	int              position;
+	char        *id;
+	char        *picture;
+	char        *source;
+	int          width;
+	int          height;
+	char        *link;
+	GthDateTime *created_time;
+	GthDateTime *updated_time;
+	GList       *images; /* FacebookImage list */
+	int          position;
 };
 
 struct _FacebookPhotoClass {
 	GObjectClass parent_class;
 };
 
-GType             facebook_photo_get_type             (void);
-FacebookPhoto *     facebook_photo_new                  (void);
-void              facebook_photo_set_id               (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_secret           (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_server           (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_title            (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_is_primary       (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_url_sq           (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_url_t            (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_url_s            (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_url_m            (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_url_o            (FacebookPhoto *self,
-					             const char  *value);
-void              facebook_photo_set_original_format  (FacebookPhoto *self,
-					             const char  *value);
+GType             facebook_photo_get_type		(void);
+FacebookPhoto *   facebook_photo_new			(void);
+const char *      facebook_photo_get_original_url	(FacebookPhoto *photo);
+const char *      facebook_photo_get_thumbnail_url	(FacebookPhoto *photo,
+							 int            requested_size);
 
 G_END_DECLS
 
diff --git a/extensions/facebook/facebook-service.c b/extensions/facebook/facebook-service.c
index d73cee6..ca2ddb1 100644
--- a/extensions/facebook/facebook-service.c
+++ b/extensions/facebook/facebook-service.c
@@ -955,119 +955,97 @@ facebook_service_upload_photos_finish (FacebookService  *self,
 }
 
 
-#if 0
-
 /* -- facebook_service_list_photos -- */
 
 
 static void
-list_photos_ready_cb (SoupSession *session,
-		      SoupMessage *msg,
-		      gpointer     user_data)
+facebook_service_list_photos_ready_cb (SoupSession *session,
+				       SoupMessage *msg,
+				       gpointer     user_data)
 {
-	FacebookService      *self = user_data;
+	FacebookService    *self = user_data;
 	GSimpleAsyncResult *result;
-	SoupBuffer         *body;
-	DomDocument        *doc = NULL;
+	JsonNode           *node;
 	GError             *error = NULL;
 
 	result = _web_service_get_result (WEB_SERVICE (self));
 
-	if (msg->status_code != 200) {
-		g_simple_async_result_set_error (result,
-						 SOUP_HTTP_ERROR,
-						 msg->status_code,
-						 "%s",
-						 soup_status_get_phrase (msg->status_code));
-		g_simple_async_result_complete_in_idle (result);
-		return;
-	}
-
-	body = soup_message_body_flatten (msg->response_body);
-	if (facebook_utils_parse_response (body, &doc, &error)) {
-		DomElement *response;
-		DomElement *node;
+	if (facebook_utils_parse_response (msg, &node, &error)) {
 		GList      *photos = NULL;
+		JsonObject *obj;
+		JsonObject *obj_photos;
+		JsonArray  *data;
+		int         i;
 
-		response = DOM_ELEMENT (doc)->first_child;
-		for (node = response->first_child; node; node = node->next_sibling) {
-			if (g_strcmp0 (node->tag_name, "photoset") == 0) {
-				DomElement *child;
-				int         position;
-
-				position = 0;
-				for (child = node->first_child; child; child = child->next_sibling) {
-					if (g_strcmp0 (child->tag_name, "photo") == 0) {
-						FacebookPhoto *photo;
-
-						photo = facebook_photo_new ();
-						dom_domizable_load_from_element (DOM_DOMIZABLE (photo), child);
-						photo->position = position++;
-						photos = g_list_prepend (photos, photo);
-					}
-				}
-			}
+		obj = json_node_get_object (node);
+		obj_photos = json_object_get_object_member (obj, "photos");
+		data = json_object_get_array_member (obj_photos, "data");
+		for (i = 0; i < json_array_get_length (data); i++) {
+			JsonNode      *photo_node;
+			FacebookPhoto *photo;
+
+			photo_node = json_array_get_element (data, i);
+			photo = (FacebookPhoto *) json_gobject_deserialize (FACEBOOK_TYPE_PHOTO, photo_node);
+			photo->position = i;
+			photos = g_list_prepend (photos, photo);
 		}
 
 		photos = g_list_reverse (photos);
 		g_simple_async_result_set_op_res_gpointer (result, photos, (GDestroyNotify) _g_object_list_unref);
 
-		g_object_unref (doc);
+		json_node_free (node);
 	}
 	else
 		g_simple_async_result_set_from_error (result, error);
 
 	g_simple_async_result_complete_in_idle (result);
-
-	soup_buffer_free (body);
 }
 
 
 void
-facebook_service_list_photos (FacebookService       *self,
-			    FacebookPhotoset      *photoset,
-			    const char          *extras,
-			    int                  per_page,
-			    int                  page,
-			    GCancellable        *cancellable,
-			    GAsyncReadyCallback  callback,
-			    gpointer             user_data)
+facebook_service_list_photos (FacebookService     *self,
+			      FacebookAlbum       *album,
+			      int                  limit,
+			      const char          *after,
+			      GCancellable        *cancellable,
+			      GAsyncReadyCallback  callback,
+			      gpointer             user_data)
 {
+	char        *uri;
 	GHashTable  *data_set;
-	char        *s;
 	SoupMessage *msg;
 
-	g_return_if_fail (photoset != NULL);
+	g_return_if_fail (album != NULL);
 
-	gth_task_progress (GTH_TASK (self->priv->conn), _("Getting the photo list"), NULL, TRUE, 0.0);
+	gth_task_progress (GTH_TASK (self),
+			   _("Getting the photo list"),
+			   NULL,
+			   TRUE,
+			   0.0);
 
+	uri = g_strdup_printf ("https://graph.facebook.com/%s";, album->id);
 	data_set = g_hash_table_new (g_str_hash, g_str_equal);
-	g_hash_table_insert (data_set, "method", "facebook.photosets.getPhotos");
-	g_hash_table_insert (data_set, "photoset_id", photoset->id);
-	if (extras != NULL)
-		g_hash_table_insert (data_set, "extras", (char *) extras);
-	if (per_page > 0) {
-		s = g_strdup_printf ("%d", per_page);
-		g_hash_table_insert (data_set, "per_page", s);
-		g_free (s);
+	g_hash_table_insert (data_set, "fields", "photos");
+	if (limit > 0) {
+		char *s_limit = g_strdup_printf ("%d", limit);
+		g_hash_table_insert (data_set, "limit", s_limit);
+		g_free (s_limit);
 	}
-	if (page > 0) {
-		s = g_strdup_printf ("%d", page);
-		g_hash_table_insert (data_set, "page", s);
-		g_free (s);
-	}
-	facebook_connection_add_api_sig (self->priv->conn, data_set);
-	msg = soup_form_request_new_from_hash ("GET", "http://api.facebook.com/services/rest";, data_set);
-	_web_service_send_message (self,
+	if (after != NULL)
+		g_hash_table_insert (data_set, "after", (gpointer) after);
+	_facebook_service_add_access_token (self, data_set);
+	msg = soup_form_request_new_from_hash ("GET", uri, data_set);
+	_web_service_send_message (WEB_SERVICE (self),
 				   msg,
 				   cancellable,
 				   callback,
 				   user_data,
 				   facebook_service_list_photos,
-				   list_photos_ready_cb,
+				   facebook_service_list_photos_ready_cb,
 				   self);
 
 	g_hash_table_destroy (data_set);
+	g_free (uri);
 }
 
 
@@ -1081,6 +1059,3 @@ facebook_service_list_photos_finish (FacebookService  *self,
 	else
 		return _g_object_list_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
 }
-
-#endif
-
diff --git a/extensions/facebook/facebook-service.h b/extensions/facebook/facebook-service.h
index 800bf84..efe8e28 100644
--- a/extensions/facebook/facebook-service.h
+++ b/extensions/facebook/facebook-service.h
@@ -79,14 +79,15 @@ void              facebook_service_upload_photos              (FacebookService
 GList *           facebook_service_upload_photos_finish       (FacebookService      *self,
 						               GAsyncResult         *result,
 						               GError              **error);
-#if 0
 void              facebook_service_list_photos                (FacebookService      *self,
 							       FacebookAlbum        *album,
+							       int                   limit,
+							       const char           *after,
+							       GCancellable         *cancellable,
 							       GAsyncReadyCallback   callback,
 							       gpointer              user_data);
 GList *           facebook_service_list_photos_finish         (FacebookService      *self,
 							       GAsyncResult         *result,
 							       GError              **error);
-#endif
 
 #endif /* FACEBOOK_SERVICE_H */
diff --git a/extensions/facebook/facebook.extension.in.in b/extensions/facebook/facebook.extension.in.in
index 4d77d95..afdadc9 100644
--- a/extensions/facebook/facebook.extension.in.in
+++ b/extensions/facebook/facebook.extension.in.in
@@ -11,4 +11,4 @@ Category=Exporter
 Type=module
 File=%LIBRARY%
 API=%GTHUMB_API_VERSION%
-Requires=export_tools;oauth
+Requires=importer;export_tools;oauth
diff --git a/gthumb/gth-time.c b/gthumb/gth-time.c
index a68da94..d76ee11 100644
--- a/gthumb/gth-time.c
+++ b/gthumb/gth-time.c
@@ -18,7 +18,7 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
- 
+
 #include <config.h>
 #include <stdlib.h>
 #include "glib-utils.h"
@@ -31,14 +31,29 @@
 #define INVALID_USEC (1000000)
 
 
-GthTime * 
+static GthDateTime *
+gth_datetime_copy_for_boxed (GthDateTime *source)
+{
+	GthDateTime *dest;
+
+	dest = gth_datetime_new ();
+	gth_datetime_copy (source, dest);
+
+	return dest;
+}
+
+
+G_DEFINE_BOXED_TYPE (GthDateTime, gth_datetime, gth_datetime_copy_for_boxed, gth_datetime_free)
+
+
+GthTime *
 gth_time_new (void)
 {
 	GthTime *time;
-	
+
 	time = g_new (GthTime, 1);
 	gth_time_clear (time);
-	
+
 	return time;
 }
 
@@ -60,17 +75,17 @@ gth_time_clear (GthTime *time)
 }
 
 
-gboolean 
+gboolean
 gth_time_valid (GthTime *time)
-{ 
+{
 	return (time->hour < INVALID_HOUR) && (time->min < INVALID_MIN) && (time->sec < INVALID_SEC) && (time->usec < INVALID_USEC);
 }
 
 
 void
-gth_time_set_hms (GthTime *time, 
-	 	  guint8   hour, 
-		  guint8   min, 
+gth_time_set_hms (GthTime *time,
+	 	  guint8   hour,
+		  guint8   min,
 		  guint8   sec,
 		  guint    usec)
 {
@@ -183,46 +198,46 @@ gth_datetime_from_exif_date (GthDateTime *dt,
 
 	val = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
 	year = val;
-	
+
 	if (*exif_date != ':')
 		return FALSE;
-	
+
 	/* MM */
-	
+
 	exif_date++;
 	month = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
-      
+
 	if (*exif_date != ':')
 		return FALSE;
 
 	/* DD */
-      
+
 	exif_date++;
 	day = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
-	
+
   	if (*exif_date != ' ')
 		return FALSE;
-  	
+
 	g_date_set_dmy (dt->date, day, month, year);
 
   	/* hh */
-  	
+
   	val = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
   	dt->time->hour = val;
-  	
+
   	if (*exif_date != ':')
 		return FALSE;
-  	
+
   	/* mm */
-  	
+
 	exif_date++;
 	dt->time->min = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
-      
+
 	if (*exif_date != ':')
 		return FALSE;
-      
+
       	/* ss */
-      
+
 	exif_date++;
 	dt->time->sec = strtoul (exif_date, (char **)&exif_date, 10);
 
diff --git a/gthumb/gth-time.h b/gthumb/gth-time.h
index 310aa96..c509654 100644
--- a/gthumb/gth-time.h
+++ b/gthumb/gth-time.h
@@ -18,7 +18,7 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-  
+
 #ifndef GTH_TIME_H
 #define GTH_TIME_H
 
@@ -26,6 +26,10 @@
 
 G_BEGIN_DECLS
 
+
+#define GTH_TYPE_DATETIME (gth_datetime_get_type ())
+
+
 typedef struct {
 	guint8 hour;
 	guint8 min;
@@ -39,13 +43,14 @@ typedef struct {
 } GthDateTime;
 
 
+GType         gth_datetime_get_type	   (void);
 GthTime *     gth_time_new     		   (void);
 void          gth_time_free    		   (GthTime     *time);
 void          gth_time_clear   		   (GthTime     *time);
 gboolean      gth_time_valid   		   (GthTime     *time);
-void          gth_time_set_hms 		   (GthTime     *time, 
-	 		 		    guint8       hour, 
-		 			    guint8       min, 
+void          gth_time_set_hms 		   (GthTime     *time,
+	 		 		    guint8       hour,
+		 			    guint8       min,
 					    guint8       sec,
 				 	    guint        usec);
 GthDateTime * gth_datetime_new             (void);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]