summaryrefslogtreecommitdiff
path: root/plugins/shotwell-publishing/FacebookPublishing.vala
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/shotwell-publishing/FacebookPublishing.vala')
-rw-r--r--plugins/shotwell-publishing/FacebookPublishing.vala1392
1 files changed, 0 insertions, 1392 deletions
diff --git a/plugins/shotwell-publishing/FacebookPublishing.vala b/plugins/shotwell-publishing/FacebookPublishing.vala
deleted file mode 100644
index 1633269..0000000
--- a/plugins/shotwell-publishing/FacebookPublishing.vala
+++ /dev/null
@@ -1,1392 +0,0 @@
-/* Copyright 2016 Software Freedom Conservancy Inc.
- *
- * This software is licensed under the GNU Lesser General Public License
- * (version 2.1 or later). See the COPYING file in this distribution.
- */
-
-public class FacebookService : Object, Spit.Pluggable, Spit.Publishing.Service {
- private const string ICON_FILENAME = "facebook.png";
-
- private static Gdk.Pixbuf[] icon_pixbuf_set = null;
-
- public FacebookService(GLib.File resource_directory) {
- if (icon_pixbuf_set == null)
- icon_pixbuf_set = Resources.load_from_resource
- (Resources.RESOURCE_PATH + "/" + ICON_FILENAME);
- }
-
- public int get_pluggable_interface(int min_host_interface, int max_host_interface) {
- return Spit.negotiate_interfaces(min_host_interface, max_host_interface,
- Spit.Publishing.CURRENT_INTERFACE);
- }
-
- public unowned string get_id() {
- return "org.yorba.shotwell.publishing.facebook";
- }
-
- public unowned string get_pluggable_name() {
- return "Facebook";
- }
-
- public void get_info(ref Spit.PluggableInfo info) {
- info.authors = "Lucas Beeler";
- info.copyright = _("Copyright 2016 Software Freedom Conservancy Inc.");
- info.translators = Resources.TRANSLATORS;
- info.version = _VERSION;
- info.website_name = Resources.WEBSITE_NAME;
- info.website_url = Resources.WEBSITE_URL;
- info.is_license_wordwrapped = false;
- info.license = Resources.LICENSE;
- info.icons = icon_pixbuf_set;
- }
-
- public void activation(bool enabled) {
- }
-
- public Spit.Publishing.Publisher create_publisher(Spit.Publishing.PluginHost host) {
- return new Publishing.Facebook.FacebookPublisher(this, host);
- }
-
- public Spit.Publishing.Publisher.MediaType get_supported_media() {
- return (Spit.Publishing.Publisher.MediaType.PHOTO |
- Spit.Publishing.Publisher.MediaType.VIDEO);
- }
-}
-
-namespace Publishing.Facebook {
-// global parameters for the Facebook publishing plugin -- don't touch these (unless you really,
-// truly, deep-down know what you're doing)
-public const string SERVICE_NAME = "facebook";
-internal const string USER_VISIBLE_NAME = "Facebook";
-internal const string DEFAULT_ALBUM_NAME = _("Shotwell Connect");
-internal const int EXPIRED_SESSION_STATUS_CODE = 400;
-
-internal class Album {
- public string name;
- public string id;
-
- public Album(string name, string id) {
- this.name = name;
- this.id = id;
- }
-}
-
-internal enum Resolution {
- STANDARD,
- HIGH;
-
- public string get_name() {
- switch (this) {
- case STANDARD:
- return _("Standard (720 pixels)");
-
- case HIGH:
- return _("Large (2048 pixels)");
-
- default:
- error("Unknown resolution %s", this.to_string());
- }
- }
-
- public int get_pixels() {
- switch (this) {
- case STANDARD:
- return 720;
-
- case HIGH:
- return 2048;
-
- default:
- error("Unknown resolution %s", this.to_string());
- }
- }
-}
-
-internal class PublishingParameters {
- public const int UNKNOWN_ALBUM = -1;
-
- public bool strip_metadata;
- public Album[] albums;
- public int target_album;
- public string? new_album_name; // the name of the new album being created during this
- // publishing interaction or null if publishing to an existing
- // album
-
- public string? privacy_object; // a serialized JSON object encoding the privacy settings of the
- // published resources
- public Resolution resolution;
-
- public PublishingParameters() {
- this.albums = null;
- this.privacy_object = null;
- this.target_album = UNKNOWN_ALBUM;
- this.new_album_name = null;
- this.strip_metadata = false;
- this.resolution = Resolution.HIGH;
- }
-
- public void add_album(string name, string id) {
- if (albums == null)
- albums = new Album[0];
-
- Album new_album = new Album(name, id);
- albums += new_album;
- }
-
- public void set_target_album_by_name(string? name) {
- if (name == null) {
- target_album = UNKNOWN_ALBUM;
- return;
- }
-
- for (int i = 0; i < albums.length; i++) {
-
- if (albums[i].name == name) {
- target_album = i;
- return;
- }
- }
-
- target_album = UNKNOWN_ALBUM;
- }
-
- public string? get_target_album_name() {
- if (albums == null || target_album == UNKNOWN_ALBUM)
- return null;
-
- return albums[target_album].name;
- }
-
- public string? get_target_album_id() {
- if (albums == null || target_album == UNKNOWN_ALBUM)
- return null;
-
- return albums[target_album].id;
- }
-}
-
-public class FacebookPublisher : Spit.Publishing.Publisher, GLib.Object {
- private PublishingParameters publishing_params;
- private weak Spit.Publishing.PluginHost host = null;
- private Spit.Publishing.ProgressCallback progress_reporter = null;
- private weak Spit.Publishing.Service service = null;
- private Spit.Publishing.Authenticator authenticator = null;
- private bool running = false;
- private GraphSession graph_session;
- private PublishingOptionsPane? publishing_options_pane = null;
- private Uploader? uploader = null;
- private string? uid = null;
- private string? username = null;
-
- public FacebookPublisher(Spit.Publishing.Service service,
- Spit.Publishing.PluginHost host) {
- debug("FacebookPublisher instantiated.");
-
- this.service = service;
- this.host = host;
-
- this.publishing_params = new PublishingParameters();
- this.authenticator =
- Publishing.Authenticator.Factory.get_instance().create("facebook",
- host);
-
- this.graph_session = new GraphSession();
- graph_session.authenticated.connect(on_session_authenticated);
- }
-
- private bool get_persistent_strip_metadata() {
- return host.get_config_bool("strip_metadata", false);
- }
-
- private void set_persistent_strip_metadata(bool strip_metadata) {
- host.set_config_bool("strip_metadata", strip_metadata);
- }
-
- // Part of the fix for #3232. These have to be
- // public so the legacy options pane may use them.
- public int get_persistent_default_size() {
- return host.get_config_int("default_size", 0);
- }
-
- public void set_persistent_default_size(int size) {
- host.set_config_int("default_size", size);
- }
-
- /*
- private void do_test_connection_to_endpoint() {
- debug("ACTION: testing connection to Facebook endpoint.");
- host.set_service_locked(true);
-
- host.install_static_message_pane(_("Testing connection to Facebook…"));
-
- GraphMessage endpoint_test_message = graph_session.new_endpoint_test();
- endpoint_test_message.completed.connect(on_endpoint_test_completed);
- endpoint_test_message.failed.connect(on_endpoint_test_error);
-
- graph_session.send_message(endpoint_test_message);
- }
- */
-
- private void do_fetch_user_info() {
- debug("ACTION: fetching user information.");
-
- host.set_service_locked(true);
- host.install_account_fetch_wait_pane();
-
- GraphMessage user_info_message = graph_session.new_query("/me");
-
- user_info_message.completed.connect(on_fetch_user_info_completed);
- user_info_message.failed.connect(on_fetch_user_info_error);
-
- graph_session.send_message(user_info_message);
- }
-
- private void do_fetch_album_descriptions() {
- debug("ACTION: fetching album list.");
-
- host.set_service_locked(true);
- host.install_account_fetch_wait_pane();
-
- GraphMessage albums_message = graph_session.new_query("/%s/albums".printf(uid));
-
- albums_message.completed.connect(on_fetch_albums_completed);
- albums_message.failed.connect(on_fetch_albums_error);
-
- graph_session.send_message(albums_message);
- }
-
- private void do_extract_user_info_from_json(string json) {
- debug("ACTION: extracting user info from JSON response.");
-
- try {
- Json.Parser parser = new Json.Parser();
- parser.load_from_data(json);
-
- Json.Node root = parser.get_root();
- Json.Object response_object = root.get_object();
- uid = response_object.get_string_member("id");
- username = response_object.get_string_member("name");
- } catch (Error error) {
- host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(error.message));
- return;
- }
-
- on_user_info_extracted();
- }
-
- private void do_extract_albums_from_json(string json) {
- debug("ACTION: extracting album info from JSON response.");
-
- try {
- Json.Parser parser = new Json.Parser();
- parser.load_from_data(json);
-
- Json.Node root = parser.get_root();
- Json.Object response_object = root.get_object();
- Json.Array album_list = response_object.get_array_member("data");
-
- publishing_params.albums = new Album[0];
-
- for (int i = 0; i < album_list.get_length(); i++) {
- Json.Object current_album = album_list.get_object_element(i);
- string album_id = current_album.get_string_member("id");
- string album_name = current_album.get_string_member("name");
-
- // Note that we are completely ignoring the "can_upload" flag in the list of albums
- // that we pulled from facebook eariler -- effectively, we add every album to the
- // publishing_params album list regardless of the value of its can_upload flag. In
- // the future we may wish to make adding to the publishing_params album list
- // conditional on the value of the can_upload flag being true
- publishing_params.add_album(album_name, album_id);
- }
- } catch (Error error) {
- host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(error.message));
- return;
- }
-
- on_albums_extracted();
- }
-
- private void do_create_new_album() {
- debug("ACTION: creating a new album named \"%s\".\n", publishing_params.new_album_name);
-
- host.set_service_locked(true);
- host.install_static_message_pane(_("Creating album…"));
-
- GraphMessage create_album_message = graph_session.new_create_album(
- publishing_params.new_album_name, publishing_params.privacy_object);
-
- create_album_message.completed.connect(on_create_album_completed);
- create_album_message.failed.connect(on_create_album_error);
-
- graph_session.send_message(create_album_message);
- }
-
- private void do_show_publishing_options_pane() {
- debug("ACTION: showing publishing options pane.");
-
- host.set_service_locked(false);
- Gtk.Builder builder = new Gtk.Builder();
-
- try {
- // the trailing get_path() is required, since add_from_file can't cope
- // with File objects directly and expects a pathname instead.
- builder.add_from_resource (Resources.RESOURCE_PATH + "/" +
- "facebook_publishing_options_pane.ui");
- } catch (Error e) {
- warning("Could not parse UI file! Error: %s.", e.message);
- host.post_error(
- new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
- _("A file required for publishing is unavailable. Publishing to Facebook can’t continue.")));
- return;
- }
-
- publishing_options_pane = new PublishingOptionsPane(username, publishing_params.albums,
- host.get_publishable_media_type(), this, builder, get_persistent_strip_metadata(),
- authenticator.can_logout());
- publishing_options_pane.logout.connect(on_publishing_options_pane_logout);
- publishing_options_pane.publish.connect(on_publishing_options_pane_publish);
- host.install_dialog_pane(publishing_options_pane,
- Spit.Publishing.PluginHost.ButtonMode.CANCEL);
- }
-
- private void do_logout() {
- debug("ACTION: clearing persistent session information and restaring interaction.");
- this.authenticator.logout();
-
- running = false;
- start();
- }
-
- private void do_add_new_local_album_from_json(string album_name, string json) {
- try {
- Json.Parser parser = new Json.Parser();
- parser.load_from_data(json);
-
- Json.Node root = parser.get_root();
- Json.Object response_object = root.get_object();
- string album_id = response_object.get_string_member("id");
-
- publishing_params.add_album(album_name, album_id);
- } catch (Error error) {
- host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(error.message));
- return;
- }
-
- publishing_params.set_target_album_by_name(album_name);
- do_upload();
- }
-
-
- private void on_authenticator_succeeded() {
- debug("EVENT: Authenticator login succeeded.");
-
- do_authenticate_session();
- }
-
- private void on_authenticator_failed() {
- }
-
- private void do_authenticate_session() {
- var parameter = this.authenticator.get_authentication_parameter();
- Variant access_token;
- if (!parameter.lookup_extended("AccessToken", null, out access_token)) {
- critical("Authenticator signalled success, but does not provide access token");
- assert_not_reached();
- }
- graph_session.authenticated.connect(on_session_authenticated);
- graph_session.authenticate(access_token.get_string());
- }
-
- private void do_upload() {
- debug("ACTION: uploading photos to album '%s'",
- publishing_params.target_album == PublishingParameters.UNKNOWN_ALBUM ? "(none)" :
- publishing_params.get_target_album_name());
-
- host.set_service_locked(true);
-
- progress_reporter = host.serialize_publishables(publishing_params.resolution.get_pixels(),
- publishing_params.strip_metadata);
-
- // Serialization is a long and potentially cancellable operation, so before we use
- // the publishables, make sure that the publishing interaction is still running. If it
- // isn't the publishing environment may be partially torn down so do a short-circuit
- // return
- if (!is_running())
- return;
-
- Spit.Publishing.Publishable[] publishables = host.get_publishables();
- uploader = new Uploader(graph_session, publishing_params, publishables);
-
- uploader.upload_complete.connect(on_upload_complete);
- uploader.upload_error.connect(on_upload_error);
-
- uploader.upload(on_upload_status_updated);
- }
-
- private void do_show_success_pane() {
- debug("ACTION: showing success pane.");
-
- host.set_service_locked(false);
- host.install_success_pane();
- }
-
- private void on_generic_error(Spit.Publishing.PublishingError error) {
- if (error is Spit.Publishing.PublishingError.EXPIRED_SESSION)
- do_logout();
- else
- host.post_error(error);
- }
-
-#if 0
- private void on_endpoint_test_completed(GraphMessage message) {
- message.completed.disconnect(on_endpoint_test_completed);
- message.failed.disconnect(on_endpoint_test_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: endpoint test transaction detected that the Facebook endpoint is alive.");
-
- do_hosted_web_authentication();
- }
-
- private void on_endpoint_test_error(GraphMessage message,
- Spit.Publishing.PublishingError error) {
- message.completed.disconnect(on_endpoint_test_completed);
- message.failed.disconnect(on_endpoint_test_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: endpoint test transaction failed to detect a connection to the Facebook " +
- "endpoint" + error.message);
-
- on_generic_error(error);
- }
-#endif
-
- private void on_session_authenticated() {
- graph_session.authenticated.disconnect(on_session_authenticated);
-
- if (!is_running())
- return;
-
- assert(graph_session.is_authenticated());
- debug("EVENT: an authenticated session has become available.");
-
- do_fetch_user_info();
- }
-
- private void on_fetch_user_info_completed(GraphMessage message) {
- message.completed.disconnect(on_fetch_user_info_completed);
- message.failed.disconnect(on_fetch_user_info_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: user info fetch completed; response = '%s'.", message.get_response_body());
-
- do_extract_user_info_from_json(message.get_response_body());
- }
-
- private void on_fetch_user_info_error(GraphMessage message,
- Spit.Publishing.PublishingError error) {
- message.completed.disconnect(on_fetch_user_info_completed);
- message.failed.disconnect(on_fetch_user_info_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: fetching user info generated and error.");
-
- on_generic_error(error);
- }
-
- private void on_user_info_extracted() {
- if (!is_running())
- return;
-
- debug("EVENT: user info extracted from JSON response: uid = %s; name = %s.", uid, username);
-
- do_fetch_album_descriptions();
- }
-
- private void on_fetch_albums_completed(GraphMessage message) {
- message.completed.disconnect(on_fetch_albums_completed);
- message.failed.disconnect(on_fetch_albums_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: album descriptions fetch transaction completed; response = '%s'.",
- message.get_response_body());
-
- do_extract_albums_from_json(message.get_response_body());
- }
-
- private void on_fetch_albums_error(GraphMessage message,
- Spit.Publishing.PublishingError err) {
- message.completed.disconnect(on_fetch_albums_completed);
- message.failed.disconnect(on_fetch_albums_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: album description fetch attempt generated an error.");
-
- on_generic_error(err);
- }
-
- private void on_albums_extracted() {
- if (!is_running())
- return;
-
- debug("EVENT: successfully extracted %d albums from JSON response",
- publishing_params.albums.length);
-
- do_show_publishing_options_pane();
- }
-
- private void on_publishing_options_pane_logout() {
- publishing_options_pane.publish.disconnect(on_publishing_options_pane_publish);
- publishing_options_pane.logout.disconnect(on_publishing_options_pane_logout);
-
- if (!is_running())
- return;
-
- debug("EVENT: user clicked 'Logout' in publishing options pane.");
-
- do_logout();
- }
-
- private void on_publishing_options_pane_publish(string? target_album, string privacy_setting,
- Resolution resolution, bool strip_metadata) {
- publishing_options_pane.publish.disconnect(on_publishing_options_pane_publish);
- publishing_options_pane.logout.disconnect(on_publishing_options_pane_logout);
-
- if (!is_running())
- return;
-
- debug("EVENT: user clicked 'Publish' in publishing options pane.");
-
- publishing_params.strip_metadata = strip_metadata;
- set_persistent_strip_metadata(strip_metadata);
- publishing_params.resolution = resolution;
- set_persistent_default_size(resolution);
- publishing_params.privacy_object = privacy_setting;
-
- if (target_album != null) {
- // we are publishing at least one photo so we need the name of an album to which
- // we'll upload the photo(s)
- publishing_params.set_target_album_by_name(target_album);
- if (publishing_params.target_album != PublishingParameters.UNKNOWN_ALBUM) {
- do_upload();
- } else {
- publishing_params.new_album_name = target_album;
- do_create_new_album();
- }
- } else {
- // we're publishing only videos and we don't need an album name
- do_upload();
- }
- }
-
- private void on_create_album_completed(GraphMessage message) {
- message.completed.disconnect(on_create_album_completed);
- message.failed.disconnect(on_create_album_error);
-
- assert(publishing_params.new_album_name != null);
-
- if (!is_running())
- return;
-
- debug("EVENT: created new album resource on remote host; response body = %s.\n",
- message.get_response_body());
-
- do_add_new_local_album_from_json(publishing_params.new_album_name,
- message.get_response_body());
- }
-
- private void on_create_album_error(GraphMessage message, Spit.Publishing.PublishingError err) {
- message.completed.disconnect(on_create_album_completed);
- message.failed.disconnect(on_create_album_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: attempt to create new album generated an error.");
-
- on_generic_error(err);
- }
-
- private void on_upload_status_updated(int file_number, double completed_fraction) {
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload %.2f percent complete.", 100.0 * completed_fraction);
-
- assert(progress_reporter != null);
-
- progress_reporter(file_number, completed_fraction);
- }
-
- private void on_upload_complete(Uploader uploader, int num_published) {
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload complete; %d items published.", num_published);
-
- do_show_success_pane();
- }
-
- private void on_upload_error(Uploader uploader, Spit.Publishing.PublishingError err) {
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload error = '%s'.", err.message);
-
- host.post_error(err);
- }
-
- public Spit.Publishing.Service get_service() {
- return service;
- }
-
- public string get_service_name() {
- return SERVICE_NAME;
- }
-
- public string get_user_visible_name() {
- return USER_VISIBLE_NAME;
- }
-
- public void start() {
- if (is_running())
- return;
-
- debug("FacebookPublisher: starting interaction.");
-
- running = true;
-
- // reset all publishing parameters to their default values -- in case this start is
- // actually a restart
- publishing_params = new PublishingParameters();
-
- this.authenticator.authenticated.connect(on_authenticator_succeeded);
- this.authenticator.authentication_failed.connect(on_authenticator_failed);
- this.authenticator.authenticate();
- }
-
- public void stop() {
- debug("FacebookPublisher: stop( ) invoked.");
-
- if (graph_session != null)
- graph_session.stop_transactions();
-
- host = null;
- running = false;
- }
-
- public bool is_running() {
- return running;
- }
-}
-
-internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
- private Gtk.Builder builder;
- private Gtk.Box pane_widget = null;
- private Gtk.RadioButton use_existing_radio = null;
- private Gtk.RadioButton create_new_radio = null;
- private Gtk.ComboBoxText existing_albums_combo = null;
- private Gtk.ComboBoxText visibility_combo = null;
- private Gtk.Entry new_album_entry = null;
- private Gtk.CheckButton strip_metadata_check = null;
- private Gtk.Button publish_button = null;
- private Gtk.Button logout_button = null;
- private Gtk.Label how_to_label = null;
- private Album[] albums = null;
- private FacebookPublisher publisher = null;
- private PrivacyDescription[] privacy_descriptions;
-
- private Resolution[] possible_resolutions;
- private Gtk.ComboBoxText resolution_combo = null;
-
- private Spit.Publishing.Publisher.MediaType media_type;
-
- private const string HEADER_LABEL_TEXT = _("You are logged into Facebook as %s.\n\n");
- private const string PHOTOS_LABEL_TEXT = _("Where would you like to publish the selected photos?");
- private const string RESOLUTION_LABEL_TEXT = _("Upload _size:");
- private const int CONTENT_GROUP_SPACING = 32;
- private const int STANDARD_ACTION_BUTTON_WIDTH = 128;
-
- public signal void logout();
- public signal void publish(string? target_album, string privacy_setting,
- Resolution target_resolution, bool strip_metadata);
-
- private class PrivacyDescription {
- public string description;
- public string privacy_setting;
-
- public PrivacyDescription(string description, string privacy_setting) {
- this.description = description;
- this.privacy_setting = privacy_setting;
- }
- }
-
- public PublishingOptionsPane(string username, Album[] albums,
- Spit.Publishing.Publisher.MediaType media_type, FacebookPublisher publisher,
- Gtk.Builder builder, bool strip_metadata, bool can_logout) {
-
- this.builder = builder;
- assert(builder != null);
- assert(builder.get_objects().length() > 0);
-
- this.albums = albums;
- this.privacy_descriptions = create_privacy_descriptions();
-
- this.possible_resolutions = create_resolution_list();
- this.publisher = publisher;
-
- // we'll need to know if the user is importing video or not when sorting out visibility.
- this.media_type = media_type;
-
- pane_widget = (Gtk.Box) builder.get_object("facebook_pane_box");
- pane_widget.set_border_width(16);
-
- use_existing_radio = (Gtk.RadioButton) this.builder.get_object("use_existing_radio");
- create_new_radio = (Gtk.RadioButton) this.builder.get_object("create_new_radio");
- existing_albums_combo = (Gtk.ComboBoxText) this.builder.get_object("existing_albums_combo");
- visibility_combo = (Gtk.ComboBoxText) this.builder.get_object("visibility_combo");
- publish_button = (Gtk.Button) this.builder.get_object("publish_button");
- logout_button = (Gtk.Button) this.builder.get_object("logout_button");
- if (!can_logout) {
- logout_button.parent.remove (logout_button);
- }
- new_album_entry = (Gtk.Entry) this.builder.get_object("new_album_entry");
- resolution_combo = (Gtk.ComboBoxText) this.builder.get_object("resolution_combo");
- how_to_label = (Gtk.Label) this.builder.get_object("how_to_label");
- strip_metadata_check = (Gtk.CheckButton) this.builder.get_object("strip_metadata_check");
-
- create_new_radio.clicked.connect(on_create_new_toggled);
- use_existing_radio.clicked.connect(on_use_existing_toggled);
-
- string label_text = HEADER_LABEL_TEXT.printf(username);
- if ((media_type & Spit.Publishing.Publisher.MediaType.PHOTO) != 0)
- label_text += PHOTOS_LABEL_TEXT;
- how_to_label.set_label(label_text);
- strip_metadata_check.set_active(strip_metadata);
-
- setup_visibility_combo();
- visibility_combo.set_active(0);
-
- publish_button.clicked.connect(on_publish_button_clicked);
- logout_button.clicked.connect(on_logout_button_clicked);
-
- setup_resolution_combo();
- resolution_combo.set_active(publisher.get_persistent_default_size());
- resolution_combo.changed.connect(on_size_changed);
-
- // Ticket #3175, part 2: make sure this widget starts out sensitive
- // if it needs to by checking whether we're starting with a video
- // or a new gallery.
- visibility_combo.set_sensitive(
- (create_new_radio != null && create_new_radio.active) ||
- ((media_type & Spit.Publishing.Publisher.MediaType.VIDEO) != 0));
-
- // if publishing only videos, disable all photo-specific controls
- if (media_type == Spit.Publishing.Publisher.MediaType.VIDEO) {
- strip_metadata_check.set_active(false);
- strip_metadata_check.set_sensitive(false);
- resolution_combo.set_sensitive(false);
- use_existing_radio.set_sensitive(false);
- create_new_radio.set_sensitive(false);
- existing_albums_combo.set_sensitive(false);
- new_album_entry.set_sensitive(false);
- }
- }
-
- private bool publishing_photos() {
- return (media_type & Spit.Publishing.Publisher.MediaType.PHOTO) != 0;
- }
-
- private void setup_visibility_combo() {
- foreach (PrivacyDescription p in privacy_descriptions)
- visibility_combo.append_text(p.description);
- }
-
- private void setup_resolution_combo() {
- foreach (Resolution res in possible_resolutions)
- resolution_combo.append_text(res.get_name());
- }
-
- private void on_use_existing_toggled() {
- if (use_existing_radio.active) {
- existing_albums_combo.set_sensitive(true);
- new_album_entry.set_sensitive(false);
-
- // Ticket #3175 - if we're not adding a new gallery
- // or a video, then we shouldn't be allowed tof
- // choose visibility, since it has no effect.
- visibility_combo.set_sensitive((media_type & Spit.Publishing.Publisher.MediaType.VIDEO) != 0);
-
- existing_albums_combo.grab_focus();
- }
- }
-
- private void on_create_new_toggled() {
- if (create_new_radio.active) {
- existing_albums_combo.set_sensitive(false);
- new_album_entry.set_sensitive(true);
- new_album_entry.grab_focus();
-
- // Ticket #3175 - if we're creating a new gallery, make sure this is
- // active, since it may have possibly been set inactive.
- visibility_combo.set_sensitive(true);
- }
- }
-
- private void on_size_changed() {
- publisher.set_persistent_default_size(resolution_combo.get_active());
- }
-
- private void on_logout_button_clicked() {
- logout();
- }
-
- private void on_publish_button_clicked() {
- string album_name;
- string privacy_setting = privacy_descriptions[visibility_combo.get_active()].privacy_setting;
-
- Resolution resolution_setting;
-
- if (publishing_photos()) {
- resolution_setting = possible_resolutions[resolution_combo.get_active()];
- if (use_existing_radio.active) {
- album_name = existing_albums_combo.get_active_text();
- } else {
- album_name = new_album_entry.get_text();
- }
- } else {
- resolution_setting = Resolution.STANDARD;
- album_name = null;
- }
-
- publish(album_name, privacy_setting, resolution_setting, strip_metadata_check.get_active());
- }
-
- private PrivacyDescription[] create_privacy_descriptions() {
- PrivacyDescription[] result = new PrivacyDescription[0];
-
- result += new PrivacyDescription(_("Just me"), "{ 'value' : 'SELF' }");
- result += new PrivacyDescription(_("Friends"), "{ 'value' : 'ALL_FRIENDS' }");
- result += new PrivacyDescription(_("Everyone"), "{ 'value' : 'EVERYONE' }");
-
- return result;
- }
-
- private Resolution[] create_resolution_list() {
- Resolution[] result = new Resolution[0];
-
- result += Resolution.STANDARD;
- result += Resolution.HIGH;
-
- return result;
- }
-
- public void installed() {
- if (publishing_photos()) {
- if (albums.length == 0) {
- create_new_radio.set_active(true);
- new_album_entry.set_text(DEFAULT_ALBUM_NAME);
- existing_albums_combo.set_sensitive(false);
- use_existing_radio.set_sensitive(false);
- } else {
- int default_album_seq_num = -1;
- int ticker = 0;
- foreach (Album album in albums) {
- existing_albums_combo.append_text(album.name);
- if (album.name == DEFAULT_ALBUM_NAME)
- default_album_seq_num = ticker;
- ticker++;
- }
- if (default_album_seq_num != -1) {
- existing_albums_combo.set_active(default_album_seq_num);
- use_existing_radio.set_active(true);
- new_album_entry.set_sensitive(false);
- }
- else {
- create_new_radio.set_active(true);
- existing_albums_combo.set_active(0);
- existing_albums_combo.set_sensitive(false);
- new_album_entry.set_text(DEFAULT_ALBUM_NAME);
- }
- }
- }
-
- publish_button.grab_focus();
- }
-
- private void notify_logout() {
- logout();
- }
-
- private void notify_publish(string? target_album, string privacy_setting, Resolution target_resolution) {
- publish(target_album, privacy_setting, target_resolution, strip_metadata_check.get_active());
- }
-
- public Gtk.Widget get_widget() {
- return pane_widget;
- }
-
- public Spit.Publishing.DialogPane.GeometryOptions get_preferred_geometry() {
- return Spit.Publishing.DialogPane.GeometryOptions.NONE;
- }
-
- public void on_pane_installed() {
- logout.connect(notify_logout);
- publish.connect(notify_publish);
-
- installed();
- }
-
- public void on_pane_uninstalled() {
- logout.disconnect(notify_logout);
- publish.disconnect(notify_publish);
- }
-}
-
-internal enum Endpoint {
- DEFAULT,
- VIDEO,
- TEST_CONNECTION;
-
- public string to_uri() {
- switch (this) {
- case DEFAULT:
- return "https://graph.facebook.com/";
-
- case VIDEO:
- return "https://graph-video.facebook.com/";
-
- case TEST_CONNECTION:
- return "https://www.facebook.com/";
-
- default:
- assert_not_reached();
- }
- }
-}
-
-internal abstract class GraphMessage {
- public signal void completed();
- public signal void failed(Spit.Publishing.PublishingError err);
- public signal void data_transmitted(int bytes_sent_so_far, int total_bytes);
-
- public abstract string get_uri();
- public abstract string get_response_body();
-}
-
-internal class GraphSession {
- private abstract class GraphMessageImpl : GraphMessage {
- public Publishing.RESTSupport.HttpMethod method;
- public string uri;
- public string access_token;
- public Soup.Message soup_message;
- public weak GraphSession host_session;
- public int bytes_so_far;
-
- protected GraphMessageImpl(GraphSession host_session, Publishing.RESTSupport.HttpMethod method,
- string relative_uri, string access_token, Endpoint endpoint = Endpoint.DEFAULT) {
- this.method = method;
- this.access_token = access_token;
- this.host_session = host_session;
- this.bytes_so_far = 0;
-
- string endpoint_uri = endpoint.to_uri();
- try {
- Regex starting_slashes = new Regex("^/+");
- this.uri = endpoint_uri + starting_slashes.replace(relative_uri, -1, 0, "");
- } catch (RegexError err) {
- assert_not_reached();
- }
- }
-
- public virtual bool prepare_for_transmission() {
- return true;
- }
-
- public override string get_uri() {
- return uri;
- }
-
- public override string get_response_body() {
- return (string) soup_message.response_body.data;
- }
-
- public void on_wrote_body_data(Soup.Buffer chunk) {
- bytes_so_far += (int) chunk.length;
-
- data_transmitted(bytes_so_far, (int) soup_message.request_body.length);
- }
- }
-
- private class GraphQueryMessage : GraphMessageImpl {
- public GraphQueryMessage(GraphSession host_session, string relative_uri,
- string access_token) {
- base(host_session, Publishing.RESTSupport.HttpMethod.GET, relative_uri, access_token);
-
- Soup.URI destination_uri = new Soup.URI(uri + "?access_token=" + access_token);
- soup_message = new Soup.Message.from_uri(method.to_string(), destination_uri);
- soup_message.wrote_body_data.connect(on_wrote_body_data);
- }
- }
-
- private class GraphEndpointProbeMessage : GraphMessageImpl {
- public GraphEndpointProbeMessage(GraphSession host_session) {
- base(host_session, Publishing.RESTSupport.HttpMethod.GET, "/", "",
- Endpoint.TEST_CONNECTION);
-
- soup_message = new Soup.Message.from_uri(method.to_string(), new Soup.URI(uri));
- soup_message.wrote_body_data.connect(on_wrote_body_data);
- }
- }
-
- private class GraphUploadMessage : GraphMessageImpl {
- private MappedFile mapped_file = null;
- private Spit.Publishing.Publishable publishable;
-
- public GraphUploadMessage(GraphSession host_session, string access_token,
- string relative_uri, Spit.Publishing.Publishable publishable,
- bool suppress_titling, string? resource_privacy = null) {
- base(host_session, Publishing.RESTSupport.HttpMethod.POST, relative_uri, access_token,
- (publishable.get_media_type() == Spit.Publishing.Publisher.MediaType.VIDEO) ?
- Endpoint.VIDEO : Endpoint.DEFAULT);
-
- // Video uploads require a privacy string at the per-resource level. Since they aren't
- // placed in albums, they can't inherit their privacy settings from their containing
- // album like photos do
- assert(publishable.get_media_type() != Spit.Publishing.Publisher.MediaType.VIDEO ||
- resource_privacy != null);
-
- this.publishable = publishable;
-
- // attempt to map the binary payload from disk into memory
- try {
- this.mapped_file = new MappedFile(publishable.get_serialized_file().get_path(),
- false);
- } catch (FileError e) {
- return;
- }
-
- this.soup_message = new Soup.Message.from_uri(method.to_string(), new Soup.URI(uri));
- soup_message.wrote_body_data.connect(on_wrote_body_data);
-
- unowned uint8[] payload = (uint8[]) mapped_file.get_contents();
- payload.length = (int) mapped_file.get_length();
-
- Soup.Buffer image_data = new Soup.Buffer(Soup.MemoryUse.TEMPORARY, payload);
-
- Soup.Multipart mp_envelope = new Soup.Multipart("multipart/form-data");
-
- mp_envelope.append_form_string("access_token", access_token);
-
- if (publishable.get_media_type() == Spit.Publishing.Publisher.MediaType.VIDEO)
- mp_envelope.append_form_string("privacy", resource_privacy);
-
- //Get photo title and post it as message on FB API
- string publishable_title = publishable.get_param_string("title");
- if (!suppress_titling && publishable_title != null)
- mp_envelope.append_form_string("name", publishable_title);
-
- //Set 'message' data field with EXIF comment field. Title has precedence.
- string publishable_comment = publishable.get_param_string("comment");
- if (!suppress_titling && publishable_comment != null)
- mp_envelope.append_form_string("message", publishable_comment);
-
- //Sets correct date of the picture
- if (!suppress_titling)
- mp_envelope.append_form_string("backdated_time", publishable.get_exposure_date_time().to_string());
-
- string source_file_mime_type =
- (publishable.get_media_type() == Spit.Publishing.Publisher.MediaType.VIDEO) ?
- "video" : "image/jpeg";
- mp_envelope.append_form_file("source", publishable.get_serialized_file().get_basename(),
- source_file_mime_type, image_data);
-
- mp_envelope.to_message(soup_message.request_headers, soup_message.request_body);
- }
-
- public override bool prepare_for_transmission() {
- if (mapped_file == null) {
- failed(new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
- "File %s is unavailable.".printf(publishable.get_serialized_file().get_path())));
- return false;
- } else {
- return true;
- }
- }
- }
-
- private class GraphCreateAlbumMessage : GraphMessageImpl {
- public GraphCreateAlbumMessage(GraphSession host_session, string access_token,
- string album_name, string album_privacy) {
- base(host_session, Publishing.RESTSupport.HttpMethod.POST, "/me/albums", access_token);
-
- assert(album_privacy != null && album_privacy != "");
-
- this.soup_message = new Soup.Message.from_uri(method.to_string(), new Soup.URI(uri));
-
- Soup.Multipart mp_envelope = new Soup.Multipart("multipart/form-data");
-
- mp_envelope.append_form_string("access_token", access_token);
- mp_envelope.append_form_string("name", album_name);
- mp_envelope.append_form_string("privacy", album_privacy);
-
- mp_envelope.to_message(soup_message.request_headers, soup_message.request_body);
- }
- }
-
- public signal void authenticated();
-
- private Soup.Session soup_session;
- private string? access_token;
- private GraphMessage? current_message;
-
- public GraphSession() {
- this.soup_session = new Soup.Session ();
- this.soup_session.request_unqueued.connect (on_request_unqueued);
- this.soup_session.timeout = 15;
- this.access_token = null;
- this.current_message = null;
- this.soup_session.ssl_use_system_ca_file = true;
- }
-
- ~GraphSession() {
- soup_session.request_unqueued.disconnect (on_request_unqueued);
- }
-
- private void manage_message(GraphMessage msg) {
- assert(current_message == null);
-
- current_message = msg;
- }
-
- private void unmanage_message(GraphMessage msg) {
- assert(current_message != null);
-
- current_message = null;
- }
-
- private void on_request_unqueued(Soup.Message msg) {
- assert(current_message != null);
- GraphMessageImpl real_message = (GraphMessageImpl) current_message;
- assert(real_message.soup_message == msg);
-
- // these error types are always recoverable given the unique behavior of the Facebook
- // endpoint, so try again
- if (msg.status_code == Soup.KnownStatusCode.IO_ERROR ||
- msg.status_code == Soup.KnownStatusCode.MALFORMED ||
- msg.status_code == Soup.KnownStatusCode.TRY_AGAIN) {
- real_message.bytes_so_far = 0;
- soup_session.queue_message(msg, null);
- return;
- }
-
- unmanage_message(real_message);
- msg.wrote_body_data.disconnect(real_message.on_wrote_body_data);
-
- Spit.Publishing.PublishingError? error = null;
- switch (msg.status_code) {
- case Soup.KnownStatusCode.OK:
- case Soup.KnownStatusCode.CREATED: // HTTP code 201 (CREATED) signals that a new
- // resource was created in response to a PUT
- // or POST
- break;
-
- case EXPIRED_SESSION_STATUS_CODE:
- error = new Spit.Publishing.PublishingError.EXPIRED_SESSION(
- "OAuth Access Token has Expired. Logout user.");
- break;
-
- case Soup.KnownStatusCode.CANT_RESOLVE:
- case Soup.KnownStatusCode.CANT_RESOLVE_PROXY:
- error = new Spit.Publishing.PublishingError.NO_ANSWER(
- "Unable to resolve %s (error code %u)", real_message.get_uri(), msg.status_code);
- break;
-
- case Soup.KnownStatusCode.CANT_CONNECT:
- case Soup.KnownStatusCode.CANT_CONNECT_PROXY:
- error = new Spit.Publishing.PublishingError.NO_ANSWER(
- "Unable to connect to %s (error code %u)", real_message.get_uri(), msg.status_code);
- break;
-
- default:
- // status codes below 100 are used by Soup, 100 and above are defined HTTP
- // codes
- if (msg.status_code >= 100) {
- error = new Spit.Publishing.PublishingError.NO_ANSWER(
- "Service %s returned HTTP status code %u %s", real_message.get_uri(),
- msg.status_code, msg.reason_phrase);
- } else {
- debug(msg.reason_phrase);
- error = new Spit.Publishing.PublishingError.NO_ANSWER(
- "Failure communicating with %s (error code %u)", real_message.get_uri(),
- msg.status_code);
- }
- break;
- }
-
- // All valid communication with Facebook involves body data in the response
- if (error == null)
- if (msg.response_body.data == null || msg.response_body.data.length == 0)
- error = new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
- "No response data from %s", real_message.get_uri());
-
- if (error == null)
- real_message.completed();
- else
- real_message.failed(error);
- }
-
- public void authenticate(string access_token) {
- this.access_token = access_token;
- authenticated();
- }
-
- public bool is_authenticated() {
- return access_token != null;
- }
-
-#if 0
- public GraphMessage new_endpoint_test() {
- return new GraphEndpointProbeMessage(this);
- }
-#endif
-
- public GraphMessage new_query(string resource_path) {
- return new GraphQueryMessage(this, resource_path, access_token);
- }
-
- public GraphMessage new_upload(string resource_path, Spit.Publishing.Publishable publishable,
- bool suppress_titling, string? resource_privacy = null) {
- return new GraphUploadMessage(this, access_token, resource_path, publishable,
- suppress_titling, resource_privacy);
- }
-
- public GraphMessage new_create_album(string album_name, string privacy) {
- return new GraphSession.GraphCreateAlbumMessage(this, access_token, album_name, privacy);
- }
-
- public void send_message(GraphMessage message) {
- GraphMessageImpl real_message = (GraphMessageImpl) message;
-
- debug("making HTTP request to URI: " + real_message.soup_message.uri.to_string(false));
-
- if (real_message.prepare_for_transmission()) {
- manage_message(message);
- soup_session.queue_message(real_message.soup_message, null);
- }
- }
-
- public void stop_transactions() {
- soup_session.abort();
- }
-}
-
-internal class Uploader {
- private int current_file;
- private Spit.Publishing.Publishable[] publishables;
- private GraphSession session;
- private PublishingParameters publishing_params;
- private unowned Spit.Publishing.ProgressCallback? status_updated = null;
-
- public signal void upload_complete(int num_photos_published);
- public signal void upload_error(Spit.Publishing.PublishingError err);
-
- public Uploader(GraphSession session, PublishingParameters publishing_params,
- Spit.Publishing.Publishable[] publishables) {
- this.current_file = 0;
- this.publishables = publishables;
- this.session = session;
- this.publishing_params = publishing_params;
- }
-
- private void send_current_file() {
- Spit.Publishing.Publishable publishable = publishables[current_file];
- GLib.File? file = publishable.get_serialized_file();
-
- // if the current publishable hasn't been serialized, then skip it
- if (file == null) {
- current_file++;
- return;
- }
-
- string resource_uri =
- (publishable.get_media_type() == Spit.Publishing.Publisher.MediaType.PHOTO) ?
- "/%s/photos".printf(publishing_params.get_target_album_id()) : "/me/videos";
- string? resource_privacy =
- (publishable.get_media_type() == Spit.Publishing.Publisher.MediaType.VIDEO) ?
- publishing_params.privacy_object : null;
- GraphMessage upload_message = session.new_upload(resource_uri, publishable,
- publishing_params.strip_metadata, resource_privacy);
-
- upload_message.data_transmitted.connect(on_chunk_transmitted);
- upload_message.completed.connect(on_message_completed);
- upload_message.failed.connect(on_message_failed);
-
- session.send_message(upload_message);
- }
-
- private void send_files() {
- current_file = 0;
- send_current_file();
- }
-
- private void on_chunk_transmitted(int bytes_written_so_far, int total_bytes) {
- double file_span = 1.0 / publishables.length;
- double this_file_fraction_complete = ((double) bytes_written_so_far) / total_bytes;
- double fraction_complete = (current_file * file_span) + (this_file_fraction_complete *
- file_span);
-
- if (status_updated != null)
- status_updated(current_file + 1, fraction_complete);
- }
-
- private void on_message_completed(GraphMessage message) {
- message.data_transmitted.disconnect(on_chunk_transmitted);
- message.completed.disconnect(on_message_completed);
- message.failed.disconnect(on_message_failed);
-
- current_file++;
- if (current_file < publishables.length) {
- send_current_file();
- } else {
- upload_complete(current_file);
- }
- }
-
- private void on_message_failed(GraphMessage message, Spit.Publishing.PublishingError error) {
- message.data_transmitted.disconnect(on_chunk_transmitted);
- message.completed.disconnect(on_message_completed);
- message.failed.disconnect(on_message_failed);
-
- upload_error(error);
- }
-
- public void upload(Spit.Publishing.ProgressCallback? status_updated = null) {
- this.status_updated = status_updated;
-
- if (publishables.length > 0)
- send_files();
- }
-}
-
-}
-