diff options
Diffstat (limited to 'plugins/shotwell-publishing/FacebookPublishing.vala')
-rw-r--r-- | plugins/shotwell-publishing/FacebookPublishing.vala | 1392 |
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(); - } -} - -} - |