summaryrefslogtreecommitdiff
path: root/plugins/shotwell-publishing
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff.email>2023-06-14 20:35:58 +0200
committerJörg Frings-Fürst <debian@jff.email>2023-06-14 20:35:58 +0200
commitd443a3c2509889533ca812c163056bace396b586 (patch)
treee94ffc0d9c054ca4efb8fb327e18dfac88e15dc7 /plugins/shotwell-publishing
parentbb9797c14470641b082ebf635e2ae3cfd5f27a3b (diff)
New upstream version 0.32.1upstream/0.32.1
Diffstat (limited to 'plugins/shotwell-publishing')
-rw-r--r--plugins/shotwell-publishing/FacebookPublishing.vala1392
-rw-r--r--plugins/shotwell-publishing/FlickrPublishing.vala246
-rw-r--r--plugins/shotwell-publishing/PhotosPublisher.vala322
-rw-r--r--plugins/shotwell-publishing/PhotosPublishingPane.vala20
-rw-r--r--plugins/shotwell-publishing/PhotosService.vala24
-rw-r--r--plugins/shotwell-publishing/PhotosUploader.vala25
-rw-r--r--plugins/shotwell-publishing/PiwigoPublishing.vala445
-rw-r--r--plugins/shotwell-publishing/TumblrPublishing.vala152
-rw-r--r--plugins/shotwell-publishing/YouTubePublishing.vala215
-rw-r--r--plugins/shotwell-publishing/YoutubeUploader.vala76
-rw-r--r--plugins/shotwell-publishing/facebook.pngbin916 -> 0 bytes
-rw-r--r--plugins/shotwell-publishing/facebook_publishing_options_pane.ui223
-rw-r--r--plugins/shotwell-publishing/flickr_publishing_options_pane.ui8
-rw-r--r--plugins/shotwell-publishing/meson.build8
-rw-r--r--plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml12
-rw-r--r--plugins/shotwell-publishing/piwigo_publishing_options_pane.ui27
-rw-r--r--plugins/shotwell-publishing/shotwell-publishing.vala20
-rw-r--r--plugins/shotwell-publishing/tumblr.svg245
-rw-r--r--plugins/shotwell-publishing/tumblr_publishing_options_pane.ui13
-rw-r--r--plugins/shotwell-publishing/youtube_publishing_options_pane.ui21
20 files changed, 960 insertions, 2534 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();
- }
-}
-
-}
-
diff --git a/plugins/shotwell-publishing/FlickrPublishing.vala b/plugins/shotwell-publishing/FlickrPublishing.vala
index 5a80284..8c7e9a1 100644
--- a/plugins/shotwell-publishing/FlickrPublishing.vala
+++ b/plugins/shotwell-publishing/FlickrPublishing.vala
@@ -5,15 +5,8 @@
*/
public class FlickrService : Object, Spit.Pluggable, Spit.Publishing.Service {
- private const string ICON_FILENAME = "flickr.png";
- private static Gdk.Pixbuf[] icon_pixbuf_set = null;
-
- public FlickrService(GLib.File resource_directory) {
- if (icon_pixbuf_set == null)
- icon_pixbuf_set = Resources.load_from_resource
- (Resources.RESOURCE_PATH + "/" + ICON_FILENAME);
- }
+ public FlickrService() {}
public int get_pluggable_interface(int min_host_interface, int max_host_interface) {
return Spit.negotiate_interfaces(min_host_interface, max_host_interface,
@@ -21,23 +14,19 @@ public class FlickrService : Object, Spit.Pluggable, Spit.Publishing.Service {
}
public unowned string get_id() {
- return "org.yorba.shotwell.publishing.flickr";
+ return "org.gnome.shotwell.publishing.flickr";
}
public unowned string get_pluggable_name() {
return "Flickr";
}
- public void get_info(ref Spit.PluggableInfo info) {
+ public Spit.PluggableInfo get_info() {
+ var info = new Spit.PluggableInfo();
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;
+
+ return info;
}
public void activation(bool enabled) {
@@ -80,10 +69,12 @@ internal class VisibilitySpecification {
// not a struct because we want reference semantics
internal class PublishingParameters {
public UserKind user_kind;
- public int64 quota_free_bytes;
+ public int64 max_images_count;
+ public uint64 uploaded_images_count;
public int photo_major_axis_size;
public string username;
public VisibilitySpecification visibility_specification;
+ public bool strip_metadata;
public PublishingParameters() {
}
@@ -154,30 +145,7 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
parameters.username = session.get_username();
- do_fetch_account_info();
- }
-
- private void on_account_fetch_txn_completed(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_account_fetch_txn_completed);
- txn.network_error.disconnect(on_account_fetch_txn_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: account fetch transaction response received over the network");
- do_parse_account_info_from_xml(txn.get_response());
- }
-
- private void on_account_fetch_txn_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError err) {
- txn.completed.disconnect(on_account_fetch_txn_completed);
- txn.network_error.disconnect(on_account_fetch_txn_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: account fetch transaction caused a network error");
- host.post_error(err);
+ do_fetch_account_info.begin();
}
private void on_account_info_available() {
@@ -196,7 +164,7 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
return;
debug("EVENT: user clicked the 'Publish' button in the publishing options pane");
- do_publish(strip_metadata);
+ do_publish.begin(strip_metadata);
}
private void on_publishing_options_pane_logout() {
@@ -222,45 +190,19 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
progress_reporter(file_number, completed_fraction);
}
- private void on_upload_complete(Publishing.RESTSupport.BatchUploader uploader,
- int num_published) {
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload complete; %d items published.", num_published);
-
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- do_show_success_pane();
- }
-
- private void on_upload_error(Publishing.RESTSupport.BatchUploader uploader,
- Spit.Publishing.PublishingError err) {
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload error = '%s'.", err.message);
-
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- host.post_error(err);
- }
-
- private void do_fetch_account_info() {
+ private async void do_fetch_account_info() {
debug("ACTION: running network transaction to fetch account information");
host.set_service_locked(true);
host.install_account_fetch_wait_pane();
AccountInfoFetchTransaction txn = new AccountInfoFetchTransaction(session);
- txn.completed.connect(on_account_fetch_txn_completed);
- txn.network_error.connect(on_account_fetch_txn_error);
-
try {
- txn.execute();
- } catch (Spit.Publishing.PublishingError err) {
+ yield txn.execute_async();
+ debug("EVENT: account fetch transaction response received over the network");
+ do_parse_account_info_from_xml(txn.get_response());
+ } catch (Error err) {
+ debug("EVENT: account fetch transaction caused a network error");
host.post_error(err);
}
}
@@ -275,9 +217,8 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
string is_pro_str = response_doc.get_property_value(user_node, "ispro");
- Xml.Node* bandwidth_node = response_doc.get_named_child(user_node, "bandwidth");
-
- string remaining_kb_str = response_doc.get_property_value(bandwidth_node, "remainingkb");
+ string max_images_str = response_doc.get_property_value(user_node, "upload_limit");
+ string uploaded_images_str = response_doc.get_property_value(user_node, "upload_count");
UserKind user_kind;
if (is_pro_str == "0")
@@ -287,10 +228,9 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
else
throw new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
"Unable to determine if user has free or pro account");
-
- var quota_bytes_left = int64.parse(remaining_kb_str) * 1024;
- parameters.quota_free_bytes = quota_bytes_left;
+ parameters.max_images_count = int64.parse(max_images_str);
+ parameters.uploaded_images_count = int64.parse(uploaded_images_str);
parameters.user_kind = user_kind;
} catch (Spit.Publishing.PublishingError err) {
@@ -353,7 +293,7 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
return a.get_exposure_date_time().compare(b.get_exposure_date_time());
}
- private void do_publish(bool strip_metadata) {
+ private async void do_publish(bool strip_metadata) {
set_persistent_strip_metadata(strip_metadata);
debug("ACTION: uploading media items to remote server.");
@@ -377,9 +317,14 @@ public class FlickrPublisher : Spit.Publishing.Publisher, GLib.Object {
sorted_list.sort(flickr_date_time_compare_func);
Uploader uploader = new Uploader(session, sorted_list.to_array(), parameters, strip_metadata);
- uploader.upload_complete.connect(on_upload_complete);
- uploader.upload_error.connect(on_upload_error);
- uploader.upload(on_upload_status_updated);
+ try {
+ var num_published = yield uploader.upload_async(on_upload_status_updated);
+ debug("EVENT: uploader reports upload complete; %d items published.", num_published);
+ do_show_success_pane();
+ } catch (Error err) {
+ debug("EVENT: uploader reports upload error = '%s'.", err.message);
+ host.post_error(err);
+ }
}
private void do_show_success_pane() {
@@ -506,7 +451,7 @@ private class UploadTransaction : Publishing.RESTSupport.OAuth1.UploadTransactio
public UploadTransaction(Publishing.RESTSupport.OAuth1.Session session, PublishingParameters parameters,
Spit.Publishing.Publishable publishable) {
- base(session, publishable, "https://api.flickr.com/services/upload");
+ base(session, publishable, "https://up.flickr.com/services/upload");
this.parameters = parameters;
@@ -514,6 +459,18 @@ private class UploadTransaction : Publishing.RESTSupport.OAuth1.UploadTransactio
add_argument("is_friend", ("%d".printf(parameters.visibility_specification.friends_level)));
add_argument("is_family", ("%d".printf(parameters.visibility_specification.family_level)));
+ if (!parameters.strip_metadata) {
+ var title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_TITLE);
+ if (title != null && title != "") {
+ add_argument("title", title);
+ }
+
+ var comment = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_COMMENT);
+ if (comment != null && comment != "") {
+ add_argument("description", comment);
+ }
+ }
+
GLib.HashTable<string, string> disposition_table =
new GLib.HashTable<string, string>(GLib.str_hash, GLib.str_equal);
string? filename = publishable.get_publishing_name();
@@ -522,7 +479,7 @@ private class UploadTransaction : Publishing.RESTSupport.OAuth1.UploadTransactio
/// TODO: This may need to be revisited to send the title separately; please see
/// http://www.flickr.com/services/api/upload.api.html for more details.
- disposition_table.insert("filename", Soup.URI.encode(
+ disposition_table.insert("filename", GLib.Uri.escape_string(
publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME), null));
disposition_table.insert("name", "photo");
@@ -530,9 +487,9 @@ private class UploadTransaction : Publishing.RESTSupport.OAuth1.UploadTransactio
set_binary_disposition_table(disposition_table);
}
- public override void execute() throws Spit.Publishing.PublishingError {
+ public override async void execute_async() throws Spit.Publishing.PublishingError {
this.authorize();
- base.execute();
+ yield base.execute_async();
}
}
@@ -606,19 +563,19 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
string upload_label_text = _("You are logged into Flickr as %s.\n\n").printf(parameters.username);
if (parameters.user_kind == UserKind.FREE) {
- upload_label_text += _("Your free Flickr account limits how much data you can upload per month.\nThis month you have %s remaining in your upload quota.").printf(GLib.format_size(parameters.quota_free_bytes, FormatSizeFlags.LONG_FORMAT | FormatSizeFlags.IEC_UNITS));
+ upload_label_text += _("Your free Flickr account limits how many photos you can upload to the service.\nYou have uploaded %llu out of your %lld file limit.").printf(parameters.uploaded_images_count, parameters.max_images_count);
} else {
- upload_label_text += _("Your Flickr Pro account entitles you to unlimited uploads.");
+ upload_label_text += ngettext("Your Flickr Pro account entitles you to unlimited uploads. You have currently uploaded a file", "Your Flickr Pro account entitles you to unlimited uploads. You have currently uploaded %d files", (int) parameters.uploaded_images_count).printf((int) parameters.uploaded_images_count);
}
upload_info_label.set_label(upload_label_text);
- string visibility_label_text = _("Photos _visible to:");
+ string visibility_label_text = _("Photos _visible to");
if ((media_type == Spit.Publishing.Publisher.MediaType.VIDEO)) {
- visibility_label_text = _("Videos _visible to:");
+ visibility_label_text = _("Videos _visible to");
} else if ((media_type == (Spit.Publishing.Publisher.MediaType.PHOTO |
Spit.Publishing.Publisher.MediaType.VIDEO))) {
- visibility_label_text = _("Photos and videos _visible to:");
+ visibility_label_text = _("Photos and videos _visible to");
}
visibility_label.set_label(visibility_label_text);
@@ -646,6 +603,7 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
}
private void on_publish_clicked() {
+ parameters.strip_metadata = strip_metadata_check.get_active();
parameters.visibility_specification =
visibilities[visibility_combo.get_active()].specification;
@@ -765,52 +723,62 @@ internal class Uploader : Publishing.RESTSupport.BatchUploader {
if (!publishable_metadata.has_iptc())
return;
- if (publishable_metadata.has_tag("Iptc.Application2.Caption"))
- publishable_metadata.set_tag_string("Iptc.Application2.Caption",
- Publishing.RESTSupport.asciify_string(publishable_metadata.get_tag_string(
- "Iptc.Application2.Caption")));
-
- if (publishable_metadata.has_tag("Iptc.Application2.Headline"))
- publishable_metadata.set_tag_string("Iptc.Application2.Headline",
- Publishing.RESTSupport.asciify_string(publishable_metadata.get_tag_string(
- "Iptc.Application2.Headline")));
-
- if (publishable_metadata.has_tag("Iptc.Application2.Keywords")) {
- Gee.Set<string> keyword_set = new Gee.HashSet<string>();
- string[] iptc_keywords = publishable_metadata.get_tag_multiple("Iptc.Application2.Keywords");
- if (iptc_keywords != null)
- foreach (string keyword in iptc_keywords)
- keyword_set.add(keyword);
-
- string[] xmp_keywords = publishable_metadata.get_tag_multiple("Xmp.dc.subject");
- if (xmp_keywords != null)
- foreach (string keyword in xmp_keywords)
- keyword_set.add(keyword);
-
- string[] all_keywords = keyword_set.to_array();
- // append a null pointer to the end of all_keywords -- this is a necessary workaround
- // for http://trac.yorba.org/ticket/3264. See also http://trac.yorba.org/ticket/3257,
- // which describes the user-visible behavior seen in the Flickr Connector as a result
- // of the former bug.
- all_keywords += null;
-
- string[] no_keywords = new string[1];
- // append a null pointer to the end of no_keywords -- this is a necessary workaround
- // for http://trac.yorba.org/ticket/3264. See also http://trac.yorba.org/ticket/3257,
- // which describes the user-visible behavior seen in the Flickr Connector as a result
- // of the former bug.
- no_keywords[0] = null;
-
- publishable_metadata.set_tag_multiple("Xmp.dc.subject", all_keywords);
- publishable_metadata.set_tag_multiple("Iptc.Application2.Keywords", no_keywords);
-
- try {
- publishable_metadata.save_file(publishable.get_serialized_file().get_path());
- } catch (GLib.Error err) {
- warning("couldn't write metadata to file '%s' for upload preprocessing.",
- publishable.get_serialized_file().get_path());
+ try {
+ if (publishable_metadata.try_has_tag("Iptc.Application2.Caption"))
+ publishable_metadata.try_set_tag_string("Iptc.Application2.Caption",
+ Publishing.RESTSupport.asciify_string(publishable_metadata.try_get_tag_string(
+ "Iptc.Application2.Caption")));
+ } catch (Error err) {}
+
+ try {
+ if (publishable_metadata.try_has_tag("Iptc.Application2.Headline"))
+ publishable_metadata.try_set_tag_string("Iptc.Application2.Headline",
+ Publishing.RESTSupport.asciify_string(publishable_metadata.try_get_tag_string(
+ "Iptc.Application2.Headline")));
+ } catch (Error error) {}
+
+ try {
+ if (publishable_metadata.try_has_tag("Iptc.Application2.Keywords")) {
+ Gee.Set<string> keyword_set = new Gee.HashSet<string>();
+ string[] iptc_keywords = publishable_metadata.try_get_tag_multiple("Iptc.Application2.Keywords");
+ if (iptc_keywords != null)
+ foreach (string keyword in iptc_keywords)
+ keyword_set.add(keyword);
+
+ string[] xmp_keywords = publishable_metadata.try_get_tag_multiple("Xmp.dc.subject");
+ if (xmp_keywords != null)
+ foreach (string keyword in xmp_keywords)
+ keyword_set.add(keyword);
+
+ string[] all_keywords = keyword_set.to_array();
+ // append a null pointer to the end of all_keywords -- this is a necessary workaround
+ // https://bugzilla.gnome.org/show_bug.cgi?id=712479. See also
+ // https://bugzilla.gnome.org/show_bug.cgi?id=717438 which describes the user-visible
+ // behavior seen in the Flickr Connector as a result of the former bug.
+ all_keywords += null;
+
+ string[] no_keywords = new string[1];
+ // append a null pointer to the end of no_keywords -- this is a necessary workaround
+ // for similar reasons as above.
+ no_keywords[0] = null;
+
+ try {
+ publishable_metadata.try_set_tag_multiple("Xmp.dc.subject", all_keywords);
+ } catch (Error error) {
+ }
+ try {
+ publishable_metadata.try_set_tag_multiple("Iptc.Application2.Keywords", no_keywords);
+ } catch (Error error) {
+ }
+
+ try {
+ publishable_metadata.save_file(publishable.get_serialized_file().get_path());
+ } catch (GLib.Error err) {
+ warning("couldn't write metadata to file '%s' for upload preprocessing.",
+ publishable.get_serialized_file().get_path());
+ }
}
- }
+ } catch (Error error) {}
}
protected override Publishing.RESTSupport.Transaction create_transaction(
diff --git a/plugins/shotwell-publishing/PhotosPublisher.vala b/plugins/shotwell-publishing/PhotosPublisher.vala
index 879f5fc..b592317 100644
--- a/plugins/shotwell-publishing/PhotosPublisher.vala
+++ b/plugins/shotwell-publishing/PhotosPublisher.vala
@@ -127,7 +127,7 @@ private class MediaCreationTransaction : Publishing.RESTSupport.GooglePublisher.
this.titles = titles;
}
- public override void execute () throws Spit.Publishing.PublishingError {
+ public override async void execute_async () throws Spit.Publishing.PublishingError {
var builder = new Json.Builder();
builder.begin_object();
builder.set_member_name("albumId");
@@ -149,7 +149,7 @@ private class MediaCreationTransaction : Publishing.RESTSupport.GooglePublisher.
builder.end_object();
set_custom_payload(Json.to_string (builder.get_root (), false), "application/json");
- base.execute();
+ yield base.execute_async();
}
}
@@ -163,7 +163,7 @@ private class AlbumCreationTransaction : Publishing.RESTSupport.GooglePublisher.
this.title = title;
}
- public override void execute () throws Spit.Publishing.PublishingError {
+ public override async void execute_async() throws Spit.Publishing.PublishingError {
var builder = new Json.Builder();
builder.begin_object();
builder.set_member_name("album");
@@ -174,58 +174,18 @@ private class AlbumCreationTransaction : Publishing.RESTSupport.GooglePublisher.
builder.end_object();
set_custom_payload(Json.to_string (builder.get_root (), false), "application/json");
- base.execute();
+ yield base.execute_async();
}
}
private class AlbumDirectoryTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
private const string ENDPOINT_URL = "https://photoslibrary.googleapis.com/v1/albums";
- private Album[] albums = new Album[0];
- public AlbumDirectoryTransaction(Publishing.RESTSupport.GoogleSession session) {
+ public AlbumDirectoryTransaction(Publishing.RESTSupport.GoogleSession session, string? token) {
base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
- this.completed.connect(on_internal_continue_pagination);
- }
-
- public Album[] get_albums() {
- return this.albums;
- }
-
- private void on_internal_continue_pagination() {
- try {
- debug(this.get_response());
- var json = Json.from_string (this.get_response());
- var object = json.get_object ();
- if (!object.has_member ("albums")) {
- return;
- }
- var pagination_token_node = object.get_member ("nextPageToken");
- var response_albums = object.get_member ("albums").get_array();
- response_albums.foreach_element( (a, b, element) => {
- var album = element.get_object();
- var title = album.get_member("title");
- var is_writable = album.get_member("isWriteable");
- if (title != null && is_writable != null && is_writable.get_boolean())
- albums += new Album(title.get_string(), album.get_string_member("id"));
- });
-
- if (pagination_token_node != null) {
- this.set_argument ("pageToken", pagination_token_node.get_string ());
- Signal.stop_emission_by_name (this, "completed");
- Idle.add(() => {
- try {
- this.execute();
- } catch (Spit.Publishing.PublishingError error) {
- this.network_error(error);
- }
-
- return false;
- });
- }
- } catch (Error error) {
- critical ("Got error %s while trying to parse response, delegating", error.message);
- this.network_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(error.message));
+ if (token != null) {
+ add_argument("pageToken", token);
}
}
}
@@ -264,61 +224,70 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
}
protected override void on_login_flow_complete() {
+ do_publishing_process.begin();
+ }
+
+ private async void do_publishing_process() {
debug("EVENT: OAuth login flow complete.");
this.publishing_parameters.set_user_name (this.authenticator.get_authentication_parameter()["UserName"].get_string());
get_host().install_account_fetch_wait_pane();
get_host().set_service_locked(true);
+ var albums = new Album[0];
- AlbumDirectoryTransaction txn = new AlbumDirectoryTransaction(get_session());
- txn.completed.connect(on_initial_album_fetch_complete);
- txn.network_error.connect(on_initial_album_fetch_error);
-
+ AlbumDirectoryTransaction txn = new AlbumDirectoryTransaction(get_session(), null);
try {
- txn.execute();
- } catch (Spit.Publishing.PublishingError error) {
- on_initial_album_fetch_error(txn, error);
- }
- }
-
- private void on_initial_album_fetch_complete(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_initial_album_fetch_complete);
- txn.network_error.disconnect(on_initial_album_fetch_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: finished fetching album information.");
-
- display_account_information((AlbumDirectoryTransaction)txn);
- }
-
- private void on_initial_album_fetch_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError error) {
- txn.completed.disconnect(on_initial_album_fetch_complete);
- txn.network_error.disconnect(on_initial_album_fetch_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: fetching album information failed; response = '%s'.",
- txn.get_response());
-
- if (txn.get_status_code() == 403 || txn.get_status_code() == 404) {
- do_logout();
- } else {
- // If we get any other kind of error, we can't recover, so just post it to the user
- get_host().post_error(error);
+ string? pagination_token = null;
+ do {
+ yield txn.execute_async();
+
+ if (!is_running())
+ return;
+
+ var json = Json.from_string (txn.get_response());
+ var object = json.get_object ();
+ if (!object.has_member ("albums")) {
+ throw new Spit.Publishing.PublishingError.MALFORMED_RESPONSE("Album fetch did not contain expected data");
+ }
+
+ if (object.has_member("nextPageToken")) {
+ pagination_token = object.get_member ("nextPageToken").get_string();
+ } else {
+ pagination_token = null;
+ }
+
+ var response_albums = object.get_member ("albums").get_array();
+ response_albums.foreach_element( (a, b, element) => {
+ var album = element.get_object();
+ var title = album.get_member("title");
+ var is_writable = album.get_member("isWriteable");
+ if (title != null && is_writable != null && is_writable.get_boolean())
+ albums += new Album(title.get_string(), album.get_string_member("id"));
+ });
+
+ if (pagination_token != null) {
+ debug("Not finished fetching all albums, getting more... %s", pagination_token);
+ txn = new AlbumDirectoryTransaction(get_session(), pagination_token);
+ }
+ } while (pagination_token != null);
+
+ debug("EVENT: finished fetching album information.");
+ this.publishing_parameters.set_albums(albums);
+
+ show_publishing_options_pane();
+ } catch (Error err) {
+ debug("EVENT: fetching album information failed; response = '%s'.",
+ txn.get_response());
+
+ if (txn.get_status_code() == 403 || txn.get_status_code() == 404) {
+ do_logout();
+ } else {
+ // If we get any other kind of error, we can't recover, so just post it to the user
+ get_host().post_error(err);
+ }
}
}
- private void display_account_information(AlbumDirectoryTransaction txn) {
- debug("ACTION: parsing album information");
- this.publishing_parameters.set_albums(txn.get_albums());
-
- show_publishing_options_pane();
- }
-
private void show_publishing_options_pane() {
debug("ACTION: showing publishing options pane.");
@@ -348,65 +317,43 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
save_parameters_to_configuration_system(publishing_parameters);
if (publishing_parameters.get_target_album_entry_id () != null) {
- do_upload();
+ do_upload.begin();
} else {
- do_create_album();
+ do_create_album.begin();
}
}
- private void do_create_album() {
+ private async void do_create_album() {
debug("ACTION: Creating album");
assert(publishing_parameters.get_target_album_entry_id () == null);
get_host().set_service_locked(true);
var txn = new AlbumCreationTransaction(get_session(), publishing_parameters.get_target_album_name());
- txn.completed.connect(on_album_create_complete);
- txn.network_error.connect(on_album_create_error);
try {
- txn.execute();
- } catch (Spit.Publishing.PublishingError error) {
- on_album_create_error(txn, error);
- }
- }
-
- private void on_album_create_complete(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_album_create_complete);
- txn.network_error.disconnect(on_album_create_error);
+ yield txn.execute_async();
- if (!is_running())
- return;
+ if (!is_running())
+ return;
- debug("EVENT: finished creating album information: %s", txn.get_response());
+ debug("EVENT: finished creating album information: %s", txn.get_response());
- try {
var node = Json.from_string(txn.get_response());
var object = node.get_object();
publishing_parameters.set_target_album_entry_id (object.get_string_member ("id"));
- do_upload();
- } catch (Error error) {
- on_album_create_error(txn, new Spit.Publishing.PublishingError.MALFORMED_RESPONSE (error.message));
- }
- }
-
- private void on_album_create_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError error) {
- txn.completed.disconnect(on_initial_album_fetch_complete);
- txn.network_error.disconnect(on_initial_album_fetch_error);
+ yield do_upload();
+ } catch (Error err) {
+ debug("EVENT: creating album failed; response = '%s'.",
+ txn.get_response());
- if (!is_running())
- return;
-
- debug("EVENT: creating album failed; response = '%s'.",
- txn.get_response());
-
- if (txn.get_status_code() == 403 || txn.get_status_code() == 404) {
- do_logout();
- } else {
- // If we get any other kind of error, we can't recover, so just post it to the user
- get_host().post_error(error);
+ if (txn.get_status_code() == 403 || txn.get_status_code() == 404) {
+ do_logout();
+ } else {
+ // If we get any other kind of error, we can't recover, so just post it to the user
+ get_host().post_error(err);
+ }
}
}
@@ -420,7 +367,7 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
}
}
- private void do_upload() {
+ private async void do_upload() {
debug("ACTION: uploading media items to remote server.");
get_host().set_service_locked(true);
@@ -439,10 +386,17 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
Spit.Publishing.Publishable[] publishables = get_host().get_publishables();
Uploader uploader = new Uploader(get_session(), publishables, publishing_parameters);
- uploader.upload_complete.connect(on_upload_complete);
- uploader.upload_error.connect(on_upload_error);
+ try {
+ yield uploader.upload_async(on_upload_status_updated);
+ yield do_media_creation_batch(uploader);
+ } catch (Error err) {
+ if (!is_running())
+ return;
+
+ debug("EVENT: uploader reports upload error = '%s'.", err.message);
- uploader.upload(on_upload_status_updated);
+ get_host().post_error(err);
+ }
}
private void on_upload_status_updated(int file_number, double completed_fraction) {
@@ -456,85 +410,39 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
progress_reporter(file_number, completed_fraction);
}
- private void on_upload_complete(Publishing.RESTSupport.BatchUploader uploader,
- int num_published) {
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload complete; %d items published.", num_published);
-
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- do_media_creation_batch(uploader);
- }
-
- private void do_media_creation_batch(Publishing.RESTSupport.BatchUploader uploader) {
+ private async void do_media_creation_batch(Publishing.RESTSupport.BatchUploader uploader) {
var u = (Uploader) uploader;
- if (creation_offset >= u.upload_tokens.length) {
- on_media_creation_complete();
- return;
- }
+ while (creation_offset < u.upload_tokens.length) {
+ var end = creation_offset + MAX_BATCH_SIZE < u.upload_tokens.length ?
+ creation_offset + MAX_BATCH_SIZE : u.upload_tokens.length;
+
+ var txn = new MediaCreationTransaction(get_session(),
+ u.upload_tokens[creation_offset:end],
+ u.titles[creation_offset:end],
+ publishing_parameters.get_target_album_entry_id());
- var end = creation_offset + MAX_BATCH_SIZE < u.upload_tokens.length ?
- creation_offset + MAX_BATCH_SIZE : u.upload_tokens.length;
-
- var txn = new MediaCreationTransaction(get_session(),
- u.upload_tokens[creation_offset:end],
- u.titles[creation_offset:end],
- publishing_parameters.get_target_album_entry_id());
-
- txn.completed.connect(() => {
- do_media_creation_batch(uploader);
- });
-
- txn.network_error.connect(on_media_creation_error);
-
- try {
creation_offset = end;
- txn.execute();
- } catch (Spit.Publishing.PublishingError error) {
- on_media_creation_error(txn, error);
+ try {
+ yield txn.execute_async();
+ if (!is_running())
+ return;
+
+ debug("EVENT: Media creation reports success.");
+
+ get_host().set_service_locked(false);
+ get_host().install_success_pane();
+ } catch (Spit.Publishing.PublishingError err) {
+ if (!is_running())
+ return;
+
+ debug("EVENT: Media creation reports error: %s", err.message);
+
+ get_host().post_error(err);
+ }
}
}
- private void on_upload_error(Publishing.RESTSupport.BatchUploader uploader,
- Spit.Publishing.PublishingError err) {
- if (!is_running())
- return;
-
- debug("EVENT: uploader reports upload error = '%s'.", err.message);
-
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- get_host().post_error(err);
- }
-
- private void on_media_creation_complete() {
- if (!is_running())
- return;
-
- debug("EVENT: Media creation reports success.");
-
- get_host().set_service_locked(false);
- get_host().install_success_pane();
- }
-
- private void on_media_creation_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError err) {
- txn.completed.disconnect(on_media_creation_complete);
- txn.network_error.disconnect(on_media_creation_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: Media creation reports error: %s", err.message);
-
- get_host().post_error(err);
- }
-
public override bool is_running() {
return running;
}
diff --git a/plugins/shotwell-publishing/PhotosPublishingPane.vala b/plugins/shotwell-publishing/PhotosPublishingPane.vala
index d1b00d6..fe2dfbd 100644
--- a/plugins/shotwell-publishing/PhotosPublishingPane.vala
+++ b/plugins/shotwell-publishing/PhotosPublishingPane.vala
@@ -22,25 +22,25 @@ internal class PublishingOptionsPane : Gtk.Box, Spit.Publishing.DialogPane {
};
[GtkChild]
- private Gtk.Button logout_button;
+ private unowned Gtk.Button logout_button;
[GtkChild]
- private Gtk.Button publish_button;
+ private unowned Gtk.Button publish_button;
[GtkChild]
- private Gtk.RadioButton existing_album_radio;
+ private unowned Gtk.RadioButton existing_album_radio;
[GtkChild]
- private Gtk.ComboBoxText existing_albums_combo;
+ private unowned Gtk.ComboBoxText existing_albums_combo;
[GtkChild]
- private Gtk.ComboBoxText size_combo;
+ private unowned Gtk.ComboBoxText size_combo;
[GtkChild]
- private Gtk.Label publish_to_label;
+ private unowned Gtk.Label publish_to_label;
[GtkChild]
- private Gtk.Label login_identity_label;
+ private unowned Gtk.Label login_identity_label;
[GtkChild]
- private Gtk.CheckButton strip_metadata_check;
+ private unowned Gtk.CheckButton strip_metadata_check;
[GtkChild]
- private Gtk.RadioButton new_album_radio;
+ private unowned Gtk.RadioButton new_album_radio;
[GtkChild]
- private Gtk.Entry new_album_entry;
+ private unowned Gtk.Entry new_album_entry;
public signal void publish();
public signal void logout();
diff --git a/plugins/shotwell-publishing/PhotosService.vala b/plugins/shotwell-publishing/PhotosService.vala
index 8e328f4..c68510b 100644
--- a/plugins/shotwell-publishing/PhotosService.vala
+++ b/plugins/shotwell-publishing/PhotosService.vala
@@ -8,15 +8,7 @@
namespace Publishing.GooglePhotos {
public class Service : Object, Spit.Pluggable, Spit.Publishing.Service {
- private const string ICON_FILENAME = "google-photos.svg";
-
- private static Gdk.Pixbuf[] icon_pixbuf_set = null;
-
- static construct {
- icon_pixbuf_set = Resources.load_from_resource(Resources.RESOURCE_PATH + "/" + ICON_FILENAME);
- }
-
- public Service(GLib.File resource_directory) {}
+ public Service() {}
public int get_pluggable_interface(int min_host_interface, int max_host_interface) {
return Spit.negotiate_interfaces(min_host_interface, max_host_interface,
@@ -31,16 +23,14 @@ public class Service : Object, Spit.Pluggable, Spit.Publishing.Service {
return "Google Photos";
}
- public void get_info(ref Spit.PluggableInfo info) {
+ public Spit.PluggableInfo get_info() {
+ var info = new Spit.PluggableInfo();
+
info.authors = "Jens Georg";
info.copyright = _("Copyright 2019 Jens Georg <mail@jensge.org>");
- 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;
+ info.icon_name = "google-photos";
+
+ return info;
}
public Spit.Publishing.Publisher create_publisher(Spit.Publishing.PluginHost host) {
diff --git a/plugins/shotwell-publishing/PhotosUploader.vala b/plugins/shotwell-publishing/PhotosUploader.vala
index 83137ee..68b8e41 100644
--- a/plugins/shotwell-publishing/PhotosUploader.vala
+++ b/plugins/shotwell-publishing/PhotosUploader.vala
@@ -11,7 +11,7 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
private PublishingParameters parameters;
private Publishing.RESTSupport.GoogleSession session;
private Spit.Publishing.Publishable publishable;
- private MappedFile mapped_file;
+ private InputStream mapped_file;
public UploadTransaction(Publishing.RESTSupport.GoogleSession session,
PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
@@ -28,13 +28,16 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
return this.publishable;
}
- public override void execute() throws Spit.Publishing.PublishingError {
+ public override async void execute_async() throws Spit.Publishing.PublishingError {
var basename = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ int64 mapped_file_size = -1;
// attempt to map the binary image data from disk into memory
try {
- mapped_file = new MappedFile(publishable.get_serialized_file().get_path(), false);
- } catch (FileError e) {
+ mapped_file = publishable.get_serialized_file().read(null);
+ var info = ((FileInputStream)mapped_file).query_info("standard::size", null);
+ mapped_file_size = info.get_size();
+ } catch (Error e) {
string msg = "Google Photos: couldn't read data from %s: %s".printf(
publishable.get_serialized_file().get_path(), e.message);
warning("%s", msg);
@@ -42,14 +45,6 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(msg);
}
- unowned uint8[] photo_data = (uint8[]) mapped_file.get_contents();
- photo_data.length = (int) mapped_file.get_length();
-
- // bind the binary image data read from disk into a Soup.Buffer object so that we
- // can attach it to the multipart request, then actaully append the buffer
- // to the multipart request. Then, set the MIME type for this part.
- Soup.Buffer bindable_data = new Soup.Buffer(Soup.MemoryUse.TEMPORARY, photo_data);
-
// create a message that can be sent over the wire whose payload is the multipart container
// that we've been building up
var outbound_message = new Soup.Message ("POST", get_endpoint_url());
@@ -58,12 +53,12 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
outbound_message.request_headers.append("X-Goog-Upload-File-Name", basename);
outbound_message.request_headers.append("X-Goog-Upload-Protocol", "raw");
outbound_message.request_headers.set_content_type("application/octet-stream", null);
- outbound_message.request_body.append_buffer (bindable_data);
- set_message(outbound_message);
+ outbound_message.set_request_body(null, mapped_file, (ssize_t)mapped_file_size);
+ set_message(outbound_message, (ulong)mapped_file_size);
// send the message and get its response
set_is_executed(true);
- send();
+ yield send_async();
}
}
diff --git a/plugins/shotwell-publishing/PiwigoPublishing.vala b/plugins/shotwell-publishing/PiwigoPublishing.vala
index d311ac5..9bf0013 100644
--- a/plugins/shotwell-publishing/PiwigoPublishing.vala
+++ b/plugins/shotwell-publishing/PiwigoPublishing.vala
@@ -1,18 +1,28 @@
-/* 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.
- */
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2016 Software Freedom Conservancy Inc.
-public class PiwigoService : Object, Spit.Pluggable, Spit.Publishing.Service {
- private const string ICON_FILENAME = "piwigo.svg";
+internal class Publishing.Piwigo.Account : Spit.Publishing.Account, Object {
+ public string server_uri;
+ public string user;
- private static Gdk.Pixbuf[] icon_pixbuf_set = null;
-
- public PiwigoService(GLib.File resource_directory) {
- if (icon_pixbuf_set == null)
- icon_pixbuf_set = Resources.load_from_resource
- (Resources.RESOURCE_PATH + "/" + ICON_FILENAME);
+ public Account(string server_uri, string user) {
+ this.server_uri = server_uri;
+ this.user = user;
+ }
+
+ public string display_name() {
+ try {
+ var uri = Uri.parse(server_uri, UriFlags.NONE);
+ return user + "@" + uri.get_host();
+ } catch (Error err) {
+ debug("Failed to parse uri in Piwigo account. %s", err.message);
+ return user + "@" + server_uri;
+ }
+ }
+}
+
+public class PiwigoService : Object, Spit.Pluggable, Spit.Publishing.Service {
+ public PiwigoService() {
}
public int get_pluggable_interface(int min_host_interface, int max_host_interface) {
@@ -21,35 +31,68 @@ public class PiwigoService : Object, Spit.Pluggable, Spit.Publishing.Service {
}
public unowned string get_id() {
- return "org.yorba.shotwell.publishing.piwigo";
+ return "org.gnome.shotwell.publishing.piwigo";
}
public unowned string get_pluggable_name() {
return "Piwigo";
}
- public void get_info(ref Spit.PluggableInfo info) {
+ public Spit.PluggableInfo get_info() {
+ var info = new Spit.PluggableInfo();
+
info.authors = "Bruno Girin";
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;
+ info.icon_name = "piwigo";
+
+ return info;
}
public void activation(bool enabled) {
}
public Spit.Publishing.Publisher create_publisher(Spit.Publishing.PluginHost host) {
- return new Publishing.Piwigo.PiwigoPublisher(this, host);
+ return new Publishing.Piwigo.PiwigoPublisher(this, host, null);
+ }
+
+ public Spit.Publishing.Publisher create_publisher_with_account(Spit.Publishing.PluginHost host,
+ Spit.Publishing.Account? account) {
+ return new Publishing.Piwigo.PiwigoPublisher(this, host, account);
}
public Spit.Publishing.Publisher.MediaType get_supported_media() {
return (Spit.Publishing.Publisher.MediaType.PHOTO);
}
+
+ public Gee.List<Spit.Publishing.Account>? get_accounts(string profile_id) {
+ var list = new Gee.ArrayList<Spit.Publishing.Account>();
+
+ // Always add the empty default account to allow new logins
+ list.add(new Spit.Publishing.DefaultAccount());
+
+ // Collect information from saved logins
+ var schema = new Secret.Schema (Publishing.Piwigo.PiwigoPublisher.PASSWORD_SCHEME, Secret.SchemaFlags.NONE,
+ Publishing.Piwigo.PiwigoPublisher.SCHEMA_KEY_PROFILE_ID, Secret.SchemaAttributeType.STRING,
+ "url", Secret.SchemaAttributeType.STRING,
+ "user", Secret.SchemaAttributeType.STRING);
+
+ var attributes = new HashTable<string, string>(str_hash, str_equal);
+ attributes[Publishing.Piwigo.PiwigoPublisher.SCHEMA_KEY_PROFILE_ID] = profile_id;
+ try {
+ var entries = Secret.password_searchv_sync(schema, attributes, Secret.SearchFlags.ALL, null);
+
+ foreach (var entry in entries) {
+ var found_attributes = entry.get_attributes();
+ list.add(new Publishing.Piwigo.Account(found_attributes["url"], found_attributes["user"]));
+ }
+ } catch (Error err) {
+ warning("Failed to look up accounts for Piwigo: %s", err.message);
+ }
+
+ return list;
+ }
}
namespace Publishing.Piwigo {
@@ -117,12 +160,16 @@ internal class PublishingParameters {
public SizeEntry photo_size = null;
public bool title_as_comment = false;
public bool no_upload_tags = false;
+ public bool no_upload_ratings = false;
public PublishingParameters() {
}
}
public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
+ internal const string PASSWORD_SCHEME = "org.gnome.Shotwell.Piwigo";
+ internal const string SCHEMA_KEY_PROFILE_ID = "shotwell-profile-id";
+
private Spit.Publishing.Service service;
private Spit.Publishing.PluginHost host;
private bool running = false;
@@ -131,13 +178,26 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
private Category[] categories = null;
private PublishingParameters parameters = null;
private Spit.Publishing.ProgressCallback progress_reporter = null;
+ private Secret.Schema? schema = null;
+ private Publishing.Piwigo.Account? account = null;
public PiwigoPublisher(Spit.Publishing.Service service,
- Spit.Publishing.PluginHost host) {
+ Spit.Publishing.PluginHost host,
+ Spit.Publishing.Account? account) {
debug("PiwigoPublisher instantiated.");
this.service = service;
this.host = host;
session = new Session();
+
+ // This should only ever be the default account which we don't care about
+ if (account is Publishing.Piwigo.Account) {
+ this.account = (Publishing.Piwigo.Account)account;
+ }
+
+ this.schema = new Secret.Schema (PASSWORD_SCHEME, Secret.SchemaFlags.NONE,
+ SCHEMA_KEY_PROFILE_ID, Secret.SchemaAttributeType.STRING,
+ "url", Secret.SchemaAttributeType.STRING,
+ "user", Secret.SchemaAttributeType.STRING);
}
// Publisher interface implementation
@@ -164,14 +224,16 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
if (session.is_authenticated()) {
debug("PiwigoPublisher: session is authenticated.");
- do_fetch_categories();
+ do_fetch_categories.begin();
} else {
debug("PiwigoPublisher: session is not authenticated.");
string? persistent_url = get_persistent_url();
string? persistent_username = get_persistent_username();
- string? persistent_password = get_persistent_password();
+ string? persistent_password = get_persistent_password(persistent_url, persistent_username);
+
+ // This will only be null if either of the other two was null or the password did not exist
if (persistent_url != null && persistent_username != null && persistent_password != null)
- do_network_login(persistent_url, persistent_username,
+ do_network_login.begin(persistent_url, persistent_username,
persistent_password, get_remember_password());
else
do_show_authentication_pane();
@@ -185,27 +247,65 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
// Session and persistent data
public string? get_persistent_url() {
- return host.get_config_string("url", null);
+ if (account != null) {
+ return account.server_uri;
+ }
+
+ return null;
}
private void set_persistent_url(string url) {
- host.set_config_string("url", url);
+ // Do nothing
}
public string? get_persistent_username() {
- return host.get_config_string("username", null);
+ if (account != null) {
+ return account.user;
+ }
+
+ return null;
}
private void set_persistent_username(string username) {
- host.set_config_string("username", username);
+ // Do nothing
}
- public string? get_persistent_password() {
- return host.get_config_string("password", null);
+ public string? get_persistent_password(string? url, string? user) {
+ if (url != null && user != null) {
+ try {
+ var pw = Secret.password_lookup_sync(this.schema, null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ "url", url, "user", user);
+
+ return pw;
+ } catch (Error err) {
+ critical("Failed to lookup the password for url %s and user %s: %s", url, user, err.message);
+
+ return null;
+ }
+ }
+
+ return null;
}
- private void set_persistent_password(string? password) {
- host.set_config_string("password", password);
+ private void set_persistent_password(string? url, string? user, string? password) {
+ try {
+ if (password == null) {
+ // remove
+ Secret.password_clear_sync(this.schema, null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ "url", url, "user", user);
+ } else {
+ Secret.password_store_sync(this.schema, Secret.COLLECTION_DEFAULT,
+ "Shotwell publishing (Piwigo account %s@%s)".printf(user, url),
+ password,
+ null,
+ SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
+ "url", url, "user", user);
+ }
+ } catch (Error err) {
+ critical("Failed to store password for %s@%s: %s", user, url, err.message);
+ }
}
public bool get_remember_password() {
@@ -256,6 +356,14 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
host.set_config_bool("last-no-upload-tags", no_upload_tags);
}
+ private bool get_last_no_upload_ratings() {
+ return host.get_config_bool("last-no-upload-ratings", false);
+ }
+
+ private void set_last_no_upload_ratings(bool no_upload_ratings) {
+ host.set_config_bool("last-no-upload-ratings", no_upload_ratings);
+ }
+
private bool get_metadata_removal_choice() {
return host.get_config_bool("strip_metadata", false);
}
@@ -290,9 +398,14 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
private void do_show_ssl_downgrade_pane (SessionLoginTransaction trans,
string url) {
- var uri = new Soup.URI (url);
+ string host_name = "";
+ try {
+ host_name = GLib.Uri.parse (url, GLib.UriFlags.NONE).get_host();
+ } catch (Error err) {
+ debug("Failed to parse URL: %s", err.message);
+ }
host.set_service_locked (false);
- var ssl_pane = new SSLErrorPane (trans, uri.get_host ());
+ var ssl_pane = new SSLErrorPane (trans, host_name);
ssl_pane.proceed.connect (() => {
debug ("SSL: User wants us to retry with broken certificate");
this.session = new Session ();
@@ -300,9 +413,9 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
string? persistent_url = get_persistent_url();
string? persistent_username = get_persistent_username();
- string? persistent_password = get_persistent_password();
+ string? persistent_password = get_persistent_password(persistent_url, persistent_username);
if (persistent_url != null && persistent_username != null && persistent_password != null)
- do_network_login(persistent_url, persistent_username,
+ do_network_login.begin(persistent_url, persistent_username,
persistent_password, get_remember_password());
else
do_show_authentication_pane();
@@ -330,7 +443,7 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
if (!running)
return;
- do_network_login(url, username, password, remember_password);
+ do_network_login.begin(url, username, password, remember_password);
}
/**
@@ -344,24 +457,24 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
* @param username the name of the Piwigo user used to login
* @param password the password of the Piwigo user used to login
*/
- private void do_network_login(string url, string username, string password, bool remember_password) {
+ private async void do_network_login(string url, string username, string password, bool remember_password) {
debug("ACTION: logging in");
host.set_service_locked(true);
host.install_login_wait_pane();
set_remember_password(remember_password);
- if (remember_password)
- set_persistent_password(password);
- else
- set_persistent_password(null);
+ if (remember_password) {
+ set_persistent_password(url, username, password);
+ } else {
+ set_persistent_password(url, username, null);
+ }
SessionLoginTransaction login_trans = new SessionLoginTransaction(
session, normalise_url(url), username, password);
- login_trans.network_error.connect(on_login_network_error);
- login_trans.completed.connect(on_login_network_complete);
try {
- login_trans.execute();
+ yield login_trans.execute_async();
+ on_login_network_complete(login_trans);
} catch (Spit.Publishing.PublishingError err) {
if (err is Spit.Publishing.PublishingError.SSL_FAILED) {
debug ("ERROR: SSL connection problems");
@@ -405,8 +518,6 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
*/
private void on_login_network_complete(Publishing.RESTSupport.Transaction txn) {
debug("EVENT: on_login_network_complete");
- txn.completed.disconnect(on_login_network_complete);
- txn.network_error.disconnect(on_login_network_error);
try {
Publishing.RESTSupport.XmlDocument.parse_string(
@@ -435,34 +546,9 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
debug("Setting session pwg_id to %s", pwg_id);
session.set_pwg_id(pwg_id);
- do_fetch_session_status(endpoint_url, pwg_id);
- }
-
- /**
- * Event triggered when a network login action fails due to a network error.
- *
- * This event triggered as a result of a network error during the login
- * transaction. As a result, it assumes that the service URL entered in the
- * authentication dialog is incorrect and re-presents the authentication
- * dialog with FAILED_RETRY_URL mode.
- *
- * @param bad_txn the received REST transaction
- * @param err the received error
- */
- private void on_login_network_error(
- Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_login_network_error");
- bad_txn.completed.disconnect(on_login_network_complete);
- bad_txn.network_error.disconnect(on_login_network_error);
-
- if (session.is_authenticated()) // ignore these events if the session is already auth'd
- return;
-
- do_show_authentication_pane(AuthenticationPane.Mode.FAILED_RETRY_URL);
+ do_fetch_session_status.begin(endpoint_url, pwg_id);
}
-
+
/**
* Action to fetch the session status for a known Piwigo user.
*
@@ -473,29 +559,27 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
* persisted. In this case, it will log the user in and confirm the
* identity.
*/
- private void do_fetch_session_status(string url = "", string pwg_id = "") {
+ private async void do_fetch_session_status(string url = "", string pwg_id = "") {
debug("ACTION: fetching session status");
host.set_service_locked(true);
host.install_account_fetch_wait_pane();
if (!session.is_authenticated()) {
SessionGetStatusTransaction status_txn = new SessionGetStatusTransaction.unauthenticated(session, url, pwg_id);
- status_txn.network_error.connect(on_session_get_status_error);
- status_txn.completed.connect(on_session_get_status_complete);
try {
- status_txn.execute();
+ yield status_txn.execute_async();
+ on_session_get_status_complete(status_txn);
} catch (Spit.Publishing.PublishingError err) {
debug("ERROR: do_fetch_session_status, not authenticated");
do_show_error(err);
}
} else {
SessionGetStatusTransaction status_txn = new SessionGetStatusTransaction(session);
- status_txn.network_error.connect(on_session_get_status_error);
- status_txn.completed.connect(on_session_get_status_complete);
try {
- status_txn.execute();
+ yield status_txn.execute_async();
+ on_session_get_status_complete(status_txn);
} catch (Spit.Publishing.PublishingError err) {
debug("ERROR: do_fetch_session_status, authenticated");
do_show_error(err);
@@ -512,8 +596,6 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
*/
private void on_session_get_status_complete(Publishing.RESTSupport.Transaction txn) {
debug("EVENT: on_session_get_status_complete");
- txn.completed.disconnect(on_session_get_status_complete);
- txn.network_error.disconnect(on_session_get_status_error);
if (!session.is_authenticated()) {
string endpoint_url = txn.get_endpoint_url();
@@ -533,7 +615,7 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
session.authenticate(endpoint_url, username, pwg_id);
set_persistent_url(session.get_pwg_url());
set_persistent_username(session.get_username());
- do_fetch_categories();
+ do_fetch_categories.begin();
} catch (Spit.Publishing.PublishingError err2) {
debug("ERROR: on_session_get_status_complete, inner");
do_show_error(err2);
@@ -548,43 +630,30 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
// This should never happen as the session should not be
// authenticated at that point so this call is a safeguard
// against the interaction not happening properly.
- do_fetch_categories();
+ do_fetch_categories.begin();
}
}
/**
- * Event triggered when the get session status fails due to a network error.
- */
- private void on_session_get_status_error(
- Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_session_get_status_error");
- bad_txn.completed.disconnect(on_session_get_status_complete);
- bad_txn.network_error.disconnect(on_session_get_status_error);
- on_network_error(bad_txn, err);
- }
-
- /**
* Action that fetches all available categories from the Piwigo service.
*
* This action fetches all categories from the Piwigo service in order
* to populate the publishing pane presented to the user.
*/
- private void do_fetch_categories() {
+ private async void do_fetch_categories() {
debug("ACTION: fetching categories");
host.set_service_locked(true);
host.install_account_fetch_wait_pane();
CategoriesGetListTransaction cat_trans = new CategoriesGetListTransaction(session);
- cat_trans.network_error.connect(on_category_fetch_error);
- cat_trans.completed.connect(on_category_fetch_complete);
try {
- cat_trans.execute();
+ yield cat_trans.execute_async();
+ on_category_fetch_complete(cat_trans);
} catch (Spit.Publishing.PublishingError err) {
debug("ERROR: do_fetch_categories");
do_show_error(err);
+ return;
}
}
@@ -597,8 +666,6 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
*/
private void on_category_fetch_complete(Publishing.RESTSupport.Transaction txn) {
debug("EVENT: on_category_fetch_complete");
- txn.completed.disconnect(on_category_fetch_complete);
- txn.network_error.disconnect(on_category_fetch_error);
debug("PiwigoConnector: list of categories: %s", txn.get_response());
// Empty the categories
if (categories != null) {
@@ -654,20 +721,6 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
}
/**
- * Event triggered when the fetch categories transaction fails due to a
- * network error.
- */
- private void on_category_fetch_error(
- Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_category_fetch_error");
- bad_txn.completed.disconnect(on_category_fetch_complete);
- bad_txn.network_error.disconnect(on_category_fetch_error);
- on_network_error(bad_txn, err);
- }
-
- /**
* Action that shows the publishing options pane.
*
* This action method shows the publishing options pane.
@@ -678,8 +731,8 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
host.set_service_locked(false);
PublishingOptionsPane opts_pane = new PublishingOptionsPane(
this, categories, get_last_category(), get_last_permission_level(), get_last_photo_size(),
- get_last_title_as_comment(), get_last_no_upload_tags(), get_metadata_removal_choice());
- opts_pane.logout.connect(on_publishing_options_pane_logout_clicked);
+ get_last_title_as_comment(), get_last_no_upload_tags(), get_last_no_upload_ratings(), get_metadata_removal_choice());
+ opts_pane.logout.connect(() => { on_publishing_options_pane_logout_clicked.begin(); });
opts_pane.publish.connect(on_publishing_options_pane_publish_clicked);
host.install_dialog_pane(opts_pane, Spit.Publishing.PluginHost.ButtonMode.CLOSE);
host.set_dialog_default_widget(opts_pane.get_default_widget());
@@ -688,14 +741,12 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
/**
* Event triggered when the user clicks logout in the publishing options pane.
*/
- private void on_publishing_options_pane_logout_clicked() {
+ private async void on_publishing_options_pane_logout_clicked() {
debug("EVENT: on_publishing_options_pane_logout_clicked");
- SessionLogoutTransaction logout_trans = new SessionLogoutTransaction(session);
- logout_trans.network_error.connect(on_logout_network_error);
- logout_trans.completed.connect(on_logout_network_complete);
try {
- logout_trans.execute();
+ yield new SessionLogoutTransaction(session).execute_async();
+ on_logout_network_complete();
} catch (Spit.Publishing.PublishingError err) {
debug("ERROR: on_publishing_options_pane_logout_clicked");
do_show_error(err);
@@ -708,29 +759,14 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
* This event de-authenticates the session and shows the authentication
* pane again.
*/
- private void on_logout_network_complete(Publishing.RESTSupport.Transaction txn) {
+ private void on_logout_network_complete() {
debug("EVENT: on_logout_network_complete");
- txn.completed.disconnect(on_logout_network_complete);
- txn.network_error.disconnect(on_logout_network_error);
session.deauthenticate();
do_show_authentication_pane(AuthenticationPane.Mode.INTRO);
}
-
- /**
- * Event triggered when the logout action fails due to a network error.
- */
- private void on_logout_network_error(
- Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_logout_network_error");
- bad_txn.completed.disconnect(on_logout_network_complete);
- bad_txn.network_error.disconnect(on_logout_network_error);
- on_network_error(bad_txn, err);
- }
-
+
/**
* Event triggered when the user clicks publish in the publishing options pane.
*
@@ -748,9 +784,9 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
this.strip_metadata = strip_metadata;
if (parameters.category.is_local()) {
- do_create_category(parameters.category);
+ do_create_category.begin(parameters.category);
} else {
- do_upload(this.strip_metadata);
+ do_upload.begin(this.strip_metadata);
}
}
@@ -760,12 +796,12 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
* This actions runs a REST transaction to create a new category in the
* Piwigo library. It displays a wait pane with an information message
* while the transaction is running. This action should only be called with
- * a local cateogory, i.e. one that does not exist on the server and does
+ * a local category, i.e. one that does not exist on the server and does
* not yet have an ID.
*
* @param category the new category to create on the server
*/
- private void do_create_category(Category category) {
+ private async void do_create_category(Category category) {
debug("ACTION: creating a new category: %s".printf(category.name));
assert(category.is_local());
@@ -774,11 +810,10 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
CategoriesAddTransaction creation_trans = new CategoriesAddTransaction(
session, category.name.strip(), int.parse(category.uppercats), category.comment);
- creation_trans.network_error.connect(on_category_add_error);
- creation_trans.completed.connect(on_category_add_complete);
try {
- creation_trans.execute();
+ yield creation_trans.execute_async();
+ on_category_add_complete(creation_trans);
} catch (Spit.Publishing.PublishingError err) {
debug("ERROR: do_create_category");
do_show_error(err);
@@ -794,8 +829,6 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
*/
private void on_category_add_complete(Publishing.RESTSupport.Transaction txn) {
debug("EVENT: on_category_add_complete");
- txn.completed.disconnect(on_category_add_complete);
- txn.network_error.disconnect(on_category_add_error);
// Parse the response
try {
@@ -808,30 +841,17 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
string id_string = id_node->get_content();
int id = int.parse(id_string);
parameters.category.id = id;
- do_upload(strip_metadata);
+ do_upload.begin(strip_metadata);
} catch (Spit.Publishing.PublishingError err) {
debug("ERROR: on_category_add_complete");
do_show_error(err);
}
}
-
- /**
- * Event triggered when the add category action fails due to a network error.
- */
- private void on_category_add_error(
- Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_category_add_error");
- bad_txn.completed.disconnect(on_category_add_complete);
- bad_txn.network_error.disconnect(on_category_add_error);
- on_network_error(bad_txn, err);
- }
-
+
/**
* Upload action: the big one, the one we've been waiting for!
*/
- private void do_upload(bool strip_metadata) {
+ private async void do_upload(bool strip_metadata) {
this.strip_metadata = strip_metadata;
debug("ACTION: uploading pictures");
@@ -842,25 +862,27 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
set_last_photo_size(parameters.photo_size.id);
set_last_title_as_comment(parameters.title_as_comment);
set_last_no_upload_tags(parameters.no_upload_tags);
+ set_last_no_upload_ratings(parameters.no_upload_ratings);
set_metadata_removal_choice(strip_metadata);
progress_reporter = host.serialize_publishables(parameters.photo_size.id, this.strip_metadata);
Spit.Publishing.Publishable[] publishables = host.get_publishables();
Uploader uploader = new Uploader(session, publishables, parameters);
- uploader.upload_complete.connect(on_upload_complete);
- uploader.upload_error.connect(on_upload_error);
- uploader.upload(on_upload_status_updated);
+ try {
+ var num_published = yield uploader.upload_async(on_upload_status_updated);
+ on_upload_complete(num_published);
+ } catch (Spit.Publishing.PublishingError err) {
+ do_show_error(err);
+ }
}
/**
* Event triggered when the batch uploader reports that at least one of the
* network transactions encapsulating uploads has completed successfully
*/
- private void on_upload_complete(Publishing.RESTSupport.BatchUploader uploader, int num_published) {
+ private void on_upload_complete(int num_published) {
debug("EVENT: on_upload_complete");
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
// TODO: should a message be displayed to the user if num_published is zero?
@@ -869,22 +891,7 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
do_show_success_pane();
}
-
- /**
- * Event triggered when the batch uploader reports that at least one of the
- * network transactions encapsulating uploads has caused a network error
- */
- private void on_upload_error(
- Publishing.RESTSupport.BatchUploader uploader,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_upload_error");
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- do_show_error(err);
- }
-
+
/**
* Event triggered when upload progresses and the status needs to be updated.
*/
@@ -910,17 +917,6 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
}
/**
- * Helper event to handle network errors.
- */
- private void on_network_error(
- Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err
- ) {
- debug("EVENT: on_network_error");
- do_show_error(err);
- }
-
- /**
* Action to display an error to the user.
*/
private void do_show_error(Spit.Publishing.PublishingError e) {
@@ -1029,8 +1025,9 @@ internal class SSLErrorPane : Shotwell.Plugins.Common.BuilderPane {
base.constructed ();
var label = this.get_builder ().get_object ("main_text") as Gtk.Label;
+ var bold_host = "<b>%s</b>".printf(host);
// %s is the host name that we tried to connect to
- label.set_text (_("This does not look like the real <b>%s</b>. Attackers might be trying to steal or alter information going to or from this site (for example, private messages, credit card information, or passwords).").printf (host));
+ label.set_text (_("This does not look like the real %s. Attackers might be trying to steal or alter information going to or from this site (for example, private messages, credit card information, or passwords).").printf(bold_host));
label.use_markup = true;
label = this.get_builder ().get_object ("ssl_errors") as Gtk.Label;
@@ -1136,7 +1133,7 @@ internal class AuthenticationPane : Shotwell.Plugins.Common.BuilderPane {
username_entry.set_text(persistent_username);
}
password_entry = builder.get_object ("password_entry") as Gtk.Entry;
- string? persistent_password = publisher.get_persistent_password();
+ string? persistent_password = publisher.get_persistent_password(persistent_url, persistent_username);
if (persistent_password != null) {
password_entry.set_text(persistent_password);
}
@@ -1205,6 +1202,7 @@ internal class PublishingOptionsPane : Shotwell.Plugins.Common.BuilderPane {
private Gtk.CheckButton strip_metadata_check = null;
private Gtk.CheckButton title_as_comment_check = null;
private Gtk.CheckButton no_upload_tags_check = null;
+ private Gtk.CheckButton no_upload_ratings_check = null;
private Gtk.Button logout_button;
private Gtk.Button publish_button;
private Gtk.TextView album_comment;
@@ -1218,6 +1216,7 @@ internal class PublishingOptionsPane : Shotwell.Plugins.Common.BuilderPane {
public int last_photo_size { private get; construct; }
public bool last_title_as_comment { private get; construct; }
public bool last_no_upload_tags { private get; construct; }
+ public bool last_no_upload_ratings { private get; construct; }
public bool strip_metadata_enabled { private get; construct; }
public Gee.List<Category> existing_categories { private get; construct; }
public string default_comment { private get; construct; }
@@ -1232,6 +1231,7 @@ internal class PublishingOptionsPane : Shotwell.Plugins.Common.BuilderPane {
int last_photo_size,
bool last_title_as_comment,
bool last_no_upload_tags,
+ bool last_no_upload_ratings,
bool strip_metadata_enabled) {
Object (resource_path : Resources.RESOURCE_PATH +
"/piwigo_publishing_options_pane.ui",
@@ -1242,6 +1242,7 @@ internal class PublishingOptionsPane : Shotwell.Plugins.Common.BuilderPane {
last_photo_size : last_photo_size,
last_title_as_comment : last_title_as_comment,
last_no_upload_tags : last_no_upload_tags,
+ last_no_upload_ratings : last_no_upload_ratings,
strip_metadata_enabled : strip_metadata_enabled,
existing_categories : new Gee.ArrayList<Category>.wrap (categories,
Category.equal),
@@ -1275,6 +1276,9 @@ internal class PublishingOptionsPane : Shotwell.Plugins.Common.BuilderPane {
no_upload_tags_check = builder.get_object("no_upload_tags_check") as Gtk.CheckButton;
no_upload_tags_check.set_active(last_no_upload_tags);
+ no_upload_ratings_check = builder.get_object("no_upload_ratings_check") as Gtk.CheckButton;
+ no_upload_ratings_check.set_active(last_no_upload_ratings);
+
logout_button = builder.get_object("logout_button") as Gtk.Button;
logout_button.clicked.connect(on_logout_button_clicked);
@@ -1325,6 +1329,7 @@ internal class PublishingOptionsPane : Shotwell.Plugins.Common.BuilderPane {
params.photo_size = photo_sizes[size_combo.get_active()];
params.title_as_comment = title_as_comment_check.get_active();
params.no_upload_tags = no_upload_tags_check.get_active();
+ params.no_upload_ratings = no_upload_ratings_check.get_active();
if (create_new_radio.get_active()) {
string uploadcomment = album_comment.buffer.text.strip();
int a = within_existing_combo.get_active();
@@ -1720,12 +1725,14 @@ private class CategoriesAddTransaction : Transaction {
private class ImagesAddTransaction : Publishing.RESTSupport.UploadTransaction {
private PublishingParameters parameters = null;
+ private Session session = null;
public ImagesAddTransaction(Session session, PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
base.with_endpoint_url(session, publishable, session.get_pwg_url());
if (session.is_authenticated()) {
add_header("Cookie", "pwg_id=".concat(session.get_pwg_id()));
}
+ this.session = session;
this.parameters = parameters;
string[] keywords = publishable.get_publishing_keywords();
@@ -1785,12 +1792,40 @@ private class ImagesAddTransaction : Publishing.RESTSupport.UploadTransaction {
!basename.down().has_suffix(".jpg")) {
basename += ".jpg";
}
- disposition_table.insert("filename", Soup.URI.encode(basename, null));
+ disposition_table.insert("filename", GLib.Uri.escape_string(basename, null));
disposition_table.insert("name", "image");
set_binary_disposition_table(disposition_table);
+ base.completed.connect(on_completed);
+ }
+
+ private void on_completed() {
+ try{
+ Publishing.RESTSupport.XmlDocument resp_doc = Publishing.RESTSupport.XmlDocument.parse_string(
+ base.get_response(), Transaction.validate_xml);
+ Xml.Node* image_node = resp_doc.get_named_child(resp_doc.get_root_node(), "image_id");
+ string image_id = image_node->get_content();
+
+ if (!parameters.no_upload_ratings)
+ new ImagesAddRating(session, publishable, image_id);
+ } catch(Spit.Publishing.PublishingError err) {
+ debug("Response parse error");
+ }
}
}
-} // namespace
+private class ImagesAddRating : Publishing.RESTSupport.UploadTransaction {
+ public ImagesAddRating(Session session, Spit.Publishing.Publishable publishable, string image_id) {
+ base.with_endpoint_url(session, publishable, session.get_pwg_url());
+ if (session.is_authenticated()) {
+ add_header("Cookie", "pwg_id=".concat(session.get_pwg_id()));
+ }
+ add_argument("method", "pwg.images.rate");
+ add_argument("image_id", image_id);
+ add_argument("rate", publishable.get_rating().to_string());
+ base.execute_async.begin();
+ }
+}
+
+} // namespace
diff --git a/plugins/shotwell-publishing/TumblrPublishing.vala b/plugins/shotwell-publishing/TumblrPublishing.vala
index 7061d6d..c9fed3f 100644
--- a/plugins/shotwell-publishing/TumblrPublishing.vala
+++ b/plugins/shotwell-publishing/TumblrPublishing.vala
@@ -6,16 +6,7 @@
*/
public class TumblrService : Object, Spit.Pluggable, Spit.Publishing.Service {
- private const string ICON_FILENAME = "tumblr.png";
-
- private static Gdk.Pixbuf[] icon_pixbuf_set = null;
-
- public TumblrService(GLib.File resource_directory) {
- if (icon_pixbuf_set == null)
- icon_pixbuf_set =
- Resources.load_from_resource(Resources.RESOURCE_PATH + "/" +
- ICON_FILENAME);
- }
+ public TumblrService() {}
public int get_pluggable_interface(int min_host_interface, int max_host_interface) {
return Spit.negotiate_interfaces(min_host_interface, max_host_interface,
@@ -23,23 +14,21 @@ public class TumblrService : Object, Spit.Pluggable, Spit.Publishing.Service {
}
public unowned string get_id() {
- return "org.yorba.shotwell.publishing.tumblr";
+ return "org.gnome.shotwell.publishing.tumblr";
}
public unowned string get_pluggable_name() {
return "Tumblr";
}
- public void get_info(ref Spit.PluggableInfo info) {
+ public Spit.PluggableInfo get_info() {
+ var info = new Spit.PluggableInfo();
+
info.authors = "Jeroen Arnoldus";
info.copyright = _("Copyright 2012 BJA Electronics");
- 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;
+ info.icon_name = "tumblr";
+
+ return info;
}
public void activation(bool enabled) {
@@ -189,72 +178,47 @@ namespace Publishing.Tumblr {
auth_token_secret.get_string(), "");
- do_get_blogs();
+ do_get_blogs.begin();
}
- private void do_get_blogs() {
+ private async void do_get_blogs() {
debug("ACTION: obtain all blogs of the tumblr user");
UserInfoFetchTransaction txn = new UserInfoFetchTransaction(session);
- txn.completed.connect(on_info_request_txn_completed);
- txn.network_error.connect(on_info_request_txn_error);
try {
- txn.execute();
- } catch (Spit.Publishing.PublishingError err) {
+ yield txn.execute_async();
+ if (!is_running())
+ return;
+
+ debug("EVENT: user info request transaction completed; response = '%s'",
+ txn.get_response());
+ do_parse_token_info_from_user_request(txn.get_response());
+ do_show_publishing_options_pane();
+ } catch (Error err) {
+ session.deauthenticate();
+ //invalidate_persistent_session();
+ debug("EVENT: user info request transaction caused a network error");
host.post_error(err);
}
-
-
}
-
- private void on_info_request_txn_completed(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_info_request_txn_completed);
- txn.network_error.disconnect(on_info_request_txn_error);
-
- if (!is_running())
- return;
-
- debug("EVENT: user info request transaction completed; response = '%s'",
- txn.get_response());
- do_parse_token_info_from_user_request(txn.get_response());
- do_show_publishing_options_pane();
- }
-
-
- private void do_parse_token_info_from_user_request(string response) {
+ private void do_parse_token_info_from_user_request(string response) throws Error {
debug("ACTION: parsing info request response '%s' into list of available blogs", response);
- try {
- var parser = new Json.Parser();
- parser.load_from_data (response, -1);
- var root_object = parser.get_root().get_object();
- this.username = root_object.get_object_member("response").get_object_member("user").get_string_member ("name");
- debug("Got user name: %s",username);
- foreach (var blognode in root_object.get_object_member("response").get_object_member("user").get_array_member("blogs").get_elements ()) {
- var blog = blognode.get_object ();
- string name = blog.get_string_member ("name");
- string url = blog.get_string_member ("url").replace("http://","").replace("https://", "").replace("/","");
- debug("Got blog name: %s and url: %s", name, url);
- this.blogs += new BlogEntry(name,url);
- }
- } catch (Error err) {
- host.post_error(err);
+
+ var parser = new Json.Parser();
+ parser.load_from_data (response, -1);
+ var root_object = parser.get_root().get_object();
+ this.username = root_object.get_object_member("response").get_object_member("user").get_string_member ("name");
+ debug("Got user name: %s",username);
+ foreach (var blognode in root_object.get_object_member("response").get_object_member("user").get_array_member("blogs").get_elements ()) {
+ var blog = blognode.get_object ();
+ string name = blog.get_string_member ("name");
+ string url = blog.get_string_member ("url").replace("http://","").replace("https://", "").replace("/","");
+ debug("Got blog name: %s and url: %s", name, url);
+ this.blogs += new BlogEntry(name,url);
}
}
- private void on_info_request_txn_error(Publishing.RESTSupport.Transaction txn,
- Spit.Publishing.PublishingError err) {
- txn.completed.disconnect(on_info_request_txn_completed);
- txn.network_error.disconnect(on_info_request_txn_error);
-
- if (!is_running())
- return;
-
- session.deauthenticate();
- //invalidate_persistent_session();
- debug("EVENT: user info request transaction caused a network error");
- host.post_error(err);
- }
private void do_show_publishing_options_pane() {
debug("ACTION: displaying publishing options pane");
@@ -328,7 +292,7 @@ namespace Publishing.Tumblr {
Uploader uploader = new Uploader(session, sorted_list.to_array(),blog_url);
uploader.upload_complete.connect(on_upload_complete);
uploader.upload_error.connect(on_upload_error);
- uploader.upload(on_upload_status_updated);
+ uploader.upload_async.begin(on_upload_status_updated);
}
private void do_show_success_pane() {
@@ -575,22 +539,6 @@ namespace Publishing.Tumblr {
internal class UploadTransaction : Publishing.RESTSupport.OAuth1.UploadTransaction {
//Workaround for Soup.URI.encode() to support binary data (i.e. string with \0)
- private string encode( uint8[] data ){
- var s = new StringBuilder();
- char[] bytes = new char[2];
- bytes[1] = 0;
- foreach( var byte in data )
- {
- if(byte == 0) {
- s.append( "%00" );
- } else {
- bytes[0] = (char)byte;
- s.append( Soup.URI.encode((string) bytes, ENCODE_RFC_3986_EXTRA) );
- }
- }
- return s.str;
- }
-
public UploadTransaction(Publishing.RESTSupport.OAuth1.Session session,Spit.Publishing.Publishable publishable, string blog_url) {
debug("Init upload transaction");
@@ -598,25 +546,21 @@ namespace Publishing.Tumblr {
}
- public override void execute() throws Spit.Publishing.PublishingError {
+ public override async void execute_async() throws Spit.Publishing.PublishingError {
string payload;
size_t payload_length;
try {
FileUtils.get_contents(base.publishable.get_serialized_file().get_path(), out payload,
out payload_length);
- string reqdata = this.encode(payload.data[0:payload_length]);
-
-
-
- add_argument("data[0]", reqdata);
+ add_argument("data64", Base64.encode(payload.data[0:payload_length]));
add_argument("type", "photo");
string[] keywords = base.publishable.get_publishing_keywords();
string tags = "";
if (keywords != null) {
tags = string.joinv (",", keywords);
}
- add_argument("tags", Soup.URI.encode(tags, ENCODE_RFC_3986_EXTRA));
+ add_argument("tags", tags);
} catch (FileError e) {
throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
@@ -626,13 +570,15 @@ namespace Publishing.Tumblr {
this.authorize();
- Publishing.RESTSupport.Argument[] request_arguments = get_arguments();
- assert(request_arguments.length > 0);
-
- var request_data = Publishing.RESTSupport.Argument.serialize_list(request_arguments);
+ var form = new GLib.HashTable<string, string>(GLib.str_hash, GLib.str_equal);
+ foreach (var arg in get_arguments()) {
+ form.insert(arg.key, arg.value);
+ }
+ assert(form.size() > 0);
- Soup.Message outbound_message = new Soup.Message( "POST", get_endpoint_url());
- outbound_message.set_request("application/x-www-form-urlencoded", Soup.MemoryUse.COPY, request_data.data);
+ var outbound_message = new Soup.Message ("POST", get_endpoint_url());
+ var body = new Bytes(Soup.Form.encode_hash(form).data);
+ outbound_message.set_request_body_from_bytes(Soup.FORM_MIME_TYPE_URLENCODED, body);
// TODO: there must be a better way to iterate over a map
Gee.MapIterator<string, string> i = base.message_headers.map_iterator();
@@ -641,11 +587,11 @@ namespace Publishing.Tumblr {
outbound_message.request_headers.append(i.get_key(), i.get_value());
cont = i.next();
}
- set_message(outbound_message);
+ set_message(outbound_message, body.length);
set_is_executed(true);
- send();
+ yield send_async();
}
}
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala b/plugins/shotwell-publishing/YouTubePublishing.vala
index e50d17a..f0a9866 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -5,14 +5,7 @@
*/
public class YouTubeService : Object, Spit.Pluggable, Spit.Publishing.Service {
- private const string ICON_FILENAME = "youtube.png";
-
- private static Gdk.Pixbuf[] icon_pixbuf_set = null;
-
- public YouTubeService(GLib.File resource_directory) {
- if (icon_pixbuf_set == null)
- icon_pixbuf_set = Resources.load_from_resource
- (Resources.RESOURCE_PATH + "/" + ICON_FILENAME);
+ public YouTubeService() {
}
public int get_pluggable_interface(int min_host_interface, int max_host_interface) {
@@ -21,23 +14,20 @@ public class YouTubeService : Object, Spit.Pluggable, Spit.Publishing.Service {
}
public unowned string get_id() {
- return "org.yorba.shotwell.publishing.youtube";
+ return "org.gnome.shotwell.publishing.youtube";
}
public unowned string get_pluggable_name() {
return "YouTube";
}
- public void get_info(ref Spit.PluggableInfo info) {
+ public Spit.PluggableInfo get_info() {
+ var info = new Spit.PluggableInfo();
info.authors = "Jani Monoses, 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;
+ info.icon_name = "youtube";
+
+ return info;
}
public Spit.Publishing.Publisher create_publisher(Spit.Publishing.PluginHost host) {
@@ -60,10 +50,23 @@ private const string DEVELOPER_KEY =
private enum PrivacySetting {
PUBLIC,
UNLISTED,
- PRIVATE
+ PRIVATE;
+
+ public string to_string() {
+ switch (this) {
+ case PUBLIC:
+ return "public";
+ case UNLISTED:
+ return "unlisted";
+ case PRIVATE:
+ return "private";
+ default:
+ assert_not_reached();
+ }
+ }
}
-private class PublishingParameters {
+internal class PublishingParameters {
private PrivacySetting privacy;
private string? user_name;
@@ -89,44 +92,14 @@ private class PublishingParameters {
}
}
-internal class YouTubeAuthorizer : GData.Authorizer, Object {
- private RESTSupport.GoogleSession session;
- private Spit.Publishing.Authenticator authenticator;
-
- public YouTubeAuthorizer(RESTSupport.GoogleSession session, Spit.Publishing.Authenticator authenticator) {
- this.session = session;
- this.authenticator = authenticator;
- }
-
- public bool is_authorized_for_domain(GData.AuthorizationDomain domain) {
- return domain.scope.has_suffix ("auth/youtube");
- }
-
- public void process_request(GData.AuthorizationDomain? domain,
- Soup.Message message) {
- if (domain == null) {
- return;
- }
-
- var header = "Bearer %s".printf(session.get_access_token());
- message.request_headers.replace("Authorization", header);
- }
-
- public bool refresh_authorization (GLib.Cancellable? cancellable = null) throws GLib.Error {
- this.authenticator.refresh();
- return true;
- }
-}
-
public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
private bool running;
private PublishingParameters publishing_parameters;
private Spit.Publishing.ProgressCallback? progress_reporter;
private Spit.Publishing.Authenticator authenticator;
- private GData.YouTubeService youtube_service;
public YouTubePublisher(Spit.Publishing.Service service, Spit.Publishing.PluginHost host) {
- base(service, host, "https://gdata.youtube.com/");
+ base(service, host, "https://www.googleapis.com/upload/youtube/v3/videos");
this.running = false;
this.publishing_parameters = new PublishingParameters();
@@ -161,8 +134,6 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
publishing_parameters.set_user_name(get_session().get_user_name());
- this.youtube_service = new GData.YouTubeService(DEVELOPER_KEY,
- new YouTubeAuthorizer(get_session(), this.authenticator));
do_show_publishing_options_pane();
}
@@ -181,7 +152,7 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
if (!is_running())
return;
- do_upload();
+ do_upload.begin();
}
private void on_upload_status_updated(int file_number, double completed_fraction) {
@@ -195,32 +166,6 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
progress_reporter(file_number, completed_fraction);
}
- private void on_upload_complete(Publishing.RESTSupport.BatchUploader uploader,
- int num_published) {
- uploader.upload_complete.disconnect(on_upload_complete);
- uploader.upload_error.disconnect(on_upload_error);
-
- debug("EVENT: uploader reports upload complete; %d items published.", num_published);
-
- if (!is_running())
- return;
-
- do_show_success_pane();
- }
-
- private void on_upload_error(Publishing.RESTSupport.BatchUploader 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);
-
- get_host().post_error(err);
- }
-
private void do_show_publishing_options_pane() {
debug("ACTION: showing publishing options pane.");
@@ -245,7 +190,7 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
get_host().set_service_locked(false);
}
- private void do_upload() {
+ private async void do_upload() {
debug("ACTION: uploading media items to remote server.");
get_host().set_service_locked(true);
@@ -261,12 +206,24 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
return;
Spit.Publishing.Publishable[] publishables = get_host().get_publishables();
- Uploader uploader = new Uploader(this.youtube_service, get_session(), publishables, publishing_parameters);
+ Uploader uploader = new Uploader(get_session(), publishables, publishing_parameters);
+
+ try {
+ var num_published = yield uploader.upload_async(on_upload_status_updated);
+ debug("EVENT: uploader reports upload complete; %d items published.", num_published);
+
+ if (!is_running())
+ return;
+
+ do_show_success_pane();
+ } catch (Error err) {
+ if (!is_running())
+ return;
- uploader.upload_complete.connect(on_upload_complete);
- uploader.upload_error.connect(on_upload_error);
+ debug("EVENT: uploader reports upload error = '%s'.", err.message);
- uploader.upload(on_upload_status_updated);
+ get_host().post_error(err);
+ }
}
private void do_show_success_pane() {
@@ -397,104 +354,20 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
}
}
-internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
- private const string ENDPOINT_URL = "https://uploads.gdata.youtube.com/feeds/api/users/default/uploads";
- private PublishingParameters parameters;
- private Publishing.RESTSupport.GoogleSession session;
- private Spit.Publishing.Publishable publishable;
- private GData.YouTubeService youtube_service;
-
- public UploadTransaction(GData.YouTubeService youtube_service, Publishing.RESTSupport.GoogleSession session,
- PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
- base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.POST);
- assert(session.is_authenticated());
- this.session = session;
- this.parameters = parameters;
- this.publishable = publishable;
- this.youtube_service = youtube_service;
- }
-
- public override void execute() throws Spit.Publishing.PublishingError {
- var video = new GData.YouTubeVideo(null);
-
- var slug = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
- // Set title to publishing name, but if that's empty default to filename.
- string title = publishable.get_publishing_name();
- if (title == "") {
- title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
- }
- video.title = title;
-
- video.is_private = (parameters.get_privacy() == PrivacySetting.PRIVATE);
-
- if (parameters.get_privacy() == PrivacySetting.UNLISTED) {
- video.set_access_control("list", GData.YouTubePermission.DENIED);
- } else if (!video.is_private) {
- video.set_access_control("list", GData.YouTubePermission.ALLOWED);
- }
-
- var file = publishable.get_serialized_file();
-
- try {
- var info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE + "," +
- FileAttribute.STANDARD_SIZE, FileQueryInfoFlags.NONE);
- var upload_stream = this.youtube_service.upload_video(video, slug,
- info.get_content_type());
- var input_stream = file.read();
-
- // Yuck...
- var loop = new MainLoop(null, false);
- this.splice_with_progress.begin(info, input_stream, upload_stream, (obj, res) => {
- try {
- this.splice_with_progress.end(res);
- } catch (Error error) {
- critical("Failed to upload: %s", error.message);
- }
- loop.quit();
- });
- loop.run();
- video = this.youtube_service.finish_video_upload(upload_stream);
- } catch (Error error) {
- critical("Upload failed: %s", error.message);
- }
- }
-
- private async void splice_with_progress(GLib.FileInfo info, GLib.InputStream input, GLib.OutputStream output) throws Error {
- var total_bytes = info.get_size();
- var bytes_to_write = total_bytes;
- uint8 buffer[8192];
-
- while (bytes_to_write > 0) {
- var bytes_read = yield input.read_async(buffer);
- if (bytes_read == 0)
- break;
-
- var bytes_written = yield output.write_async(buffer[0:bytes_read]);
- bytes_to_write -= bytes_written;
- chunk_transmitted((int)(total_bytes - bytes_to_write), (int) total_bytes);
- }
-
- yield output.close_async();
- yield input.close_async();
- }
-}
-
internal class Uploader : Publishing.RESTSupport.BatchUploader {
private PublishingParameters parameters;
- private GData.YouTubeService youtube_service;
- public Uploader(GData.YouTubeService youtube_service, Publishing.RESTSupport.GoogleSession session,
+ public Uploader(Publishing.RESTSupport.GoogleSession session,
Spit.Publishing.Publishable[] publishables, PublishingParameters parameters) {
base(session, publishables);
this.parameters = parameters;
- this.youtube_service = youtube_service;
}
protected override Publishing.RESTSupport.Transaction create_transaction(
Spit.Publishing.Publishable publishable) {
- return new UploadTransaction(this.youtube_service, (Publishing.RESTSupport.GoogleSession) get_session(),
- parameters, get_current_publishable());
+ return new UploadTransaction((Publishing.RESTSupport.GoogleSession) get_session(),
+ parameters, get_current_publishable());
}
}
diff --git a/plugins/shotwell-publishing/YoutubeUploader.vala b/plugins/shotwell-publishing/YoutubeUploader.vala
new file mode 100644
index 0000000..47c6051
--- /dev/null
+++ b/plugins/shotwell-publishing/YoutubeUploader.vala
@@ -0,0 +1,76 @@
+using Spit;
+
+internal class Publishing.YouTube.UploadTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
+ private PublishingParameters parameters;
+ private Publishing.RESTSupport.GoogleSession session;
+ private Spit.Publishing.Publishable publishable;
+
+ public UploadTransaction(Publishing.RESTSupport.GoogleSession session,
+ PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
+ base(session, "https://www.googleapis.com/upload/youtube/v3/videos",
+ Publishing.RESTSupport.HttpMethod.POST);
+ assert(session.is_authenticated());
+
+ this.session = session;
+ this.parameters = parameters;
+ this.publishable = publishable;
+ }
+ public override async void execute_async() throws Spit.Publishing.PublishingError {
+ // Collect parameters
+
+ var slug = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ // Set title to publishing name, but if that's empty default to filename.
+ string title = publishable.get_publishing_name();
+ if (title == "") {
+ title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ }
+
+ var builder = new Json.Builder();
+ builder.begin_object();
+ builder.set_member_name("snippet");
+ builder.begin_object();
+ builder.set_member_name("description");
+ builder.add_string_value(slug);
+ builder.set_member_name("title");
+ builder.add_string_value(title);
+ builder.end_object();
+ builder.set_member_name("status");
+ builder.begin_object();
+ builder.set_member_name("privacyStatus");
+ builder.add_string_value(parameters.get_privacy().to_string());
+ builder.end_object();
+ builder.end_object();
+
+ var meta_data = Json.to_string (builder.get_root(), false);
+ debug ("Parameters: %s", meta_data);
+ var message_parts = new Soup.Multipart("multipart/related");
+ var headers = new Soup.MessageHeaders(Soup.MessageHeadersType.MULTIPART);
+ var encoding = new GLib.HashTable<string, string>(str_hash, str_equal);
+ encoding.insert("encoding", "UTF-8");
+ headers.set_content_type ("application/json", encoding);
+
+ message_parts.append_part (headers, new Bytes (meta_data.data));
+ headers = new Soup.MessageHeaders(Soup.MessageHeadersType.MULTIPART);
+ headers.set_content_type ("application/octet-stream", null);
+ headers.append("Content-Transfer-Encoding", "binary");
+
+ MappedFile? mapped_file = null;
+ try {
+ mapped_file = new MappedFile(publishable.get_serialized_file().get_path(), false);
+ } catch (Error e) {
+ throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
+ _("A temporary file needed for publishing is unavailable"));
+ }
+
+
+ message_parts.append_part (headers, mapped_file.get_bytes());
+
+ var outbound_message = new Soup.Message.from_multipart (get_endpoint_url() + "?part=" + GLib.Uri.escape_string ("snippet,status"), message_parts);
+ outbound_message.get_request_headers().append("Authorization", "Bearer " +
+ session.get_access_token());
+
+ set_message(outbound_message, mapped_file.get_length() + meta_data.length);
+ set_is_executed(true);
+ yield send_async();
+ }
+}
diff --git a/plugins/shotwell-publishing/facebook.png b/plugins/shotwell-publishing/facebook.png
deleted file mode 100644
index 384609f..0000000
--- a/plugins/shotwell-publishing/facebook.png
+++ /dev/null
Binary files differ
diff --git a/plugins/shotwell-publishing/facebook_publishing_options_pane.ui b/plugins/shotwell-publishing/facebook_publishing_options_pane.ui
deleted file mode 100644
index 4d6b021..0000000
--- a/plugins/shotwell-publishing/facebook_publishing_options_pane.ui
+++ /dev/null
@@ -1,223 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
-<interface domain="shotwell">
- <requires lib="gtk+" version="3.14"/>
- <object class="GtkBox" id="facebook_pane_box">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="homogeneous">True</property>
- <child>
- <object class="GtkBox" id="facebook_pane_inner_box">
- <property name="width_request">1</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="orientation">vertical</property>
- <property name="spacing">16</property>
- <child>
- <object class="GtkLabel" id="how_to_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_left">16</property>
- <property name="margin_right">16</property>
- <property name="margin_top">16</property>
- <property name="margin_bottom">16</property>
- <property name="label"> (text depends on fb username and is modified in the app -
-anything put into this field won't display)</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="grid1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="row_spacing">8</property>
- <property name="column_spacing">32</property>
- <property name="column_homogeneous">True</property>
- <child>
- <object class="GtkRadioButton" id="use_existing_radio">
- <property name="label" translatable="yes">Publish to an e_xisting album:</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="halign">start</property>
- <property name="use_underline">True</property>
- <property name="draw_indicator">True</property>
- <property name="group">create_new_radio</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="create_new_radio">
- <property name="label" translatable="yes">Create a _new album named:</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="halign">start</property>
- <property name="margin_bottom">8</property>
- <property name="use_underline">True</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="new_album_entry">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="margin_bottom">8</property>
- <property name="invisible_char">●</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBoxText" id="existing_albums_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="size_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">start</property>
- <property name="label" translatable="yes">Upload _size:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">resolution_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">3</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBoxText" id="resolution_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">start</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">3</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="visibility_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">start</property>
- <property name="label" translatable="yes">Videos and new photo albums _visible to:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">visibility_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBoxText" id="visibility_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">start</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">2</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="padding">4</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="strip_metadata_check">
- <property name="label" translatable="yes">_Remove location, camera, and other identifying information before uploading</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="halign">start</property>
- <property name="use_underline">True</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkBox" id="box2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">32</property>
- <property name="homogeneous">True</property>
- <child>
- <object class="GtkButton" id="logout_button">
- <property name="label" translatable="yes">_Logout</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="padding">80</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="publish_button">
- <property name="label" translatable="yes">_Publish</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="padding">80</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="padding">2</property>
- <property name="position">3</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="padding">8</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
-</interface>
diff --git a/plugins/shotwell-publishing/flickr_publishing_options_pane.ui b/plugins/shotwell-publishing/flickr_publishing_options_pane.ui
index 77d32cf..e5e61aa 100644
--- a/plugins/shotwell-publishing/flickr_publishing_options_pane.ui
+++ b/plugins/shotwell-publishing/flickr_publishing_options_pane.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
+<!-- Generated with glade 3.22.0 -->
<interface domain="shotwell">
<requires lib="gtk+" version="3.14"/>
<object class="GtkBox" id="flickr_pane">
@@ -36,8 +36,8 @@ so changes made here will not display)</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_bottom">8</property>
- <property name="row_spacing">8</property>
- <property name="column_spacing">24</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="visibility_label">
<property name="visible">True</property>
@@ -58,7 +58,7 @@ so changes made here will not display)</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
- <property name="label" translatable="yes">Photo _size:</property>
+ <property name="label" translatable="yes">Photo _size</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">size_combo</property>
</object>
diff --git a/plugins/shotwell-publishing/meson.build b/plugins/shotwell-publishing/meson.build
index eeac177..a93726b 100644
--- a/plugins/shotwell-publishing/meson.build
+++ b/plugins/shotwell-publishing/meson.build
@@ -1,9 +1,9 @@
shotwell_publishing_sources = [
'shotwell-publishing.vala',
- 'FacebookPublishing.vala',
'FlickrPublishing.vala',
'TumblrPublishing.vala',
'YouTubePublishing.vala',
+ 'YoutubeUploader.vala',
'PiwigoPublishing.vala',
'PhotosPublisher.vala',
'PhotosService.vala',
@@ -13,13 +13,13 @@ shotwell_publishing_sources = [
shotwell_publishing_resources = gnome.compile_resources('publishing-resource',
'org.gnome.Shotwell.Publishing.gresource.xml',
- source_dir : meson.source_root())
+ source_dir : meson.project_source_root())
shared_module('shotwell-publishing',
shotwell_publishing_sources + shotwell_publishing_resources,
dependencies : [gtk, soup, gexiv2, gee, sw_plugin, json_glib,
- webkit, sw_plugin_common_dep, xml, gdata, gcr,
- gcr_ui, authenticator_dep],
+ webkit, sw_plugin_common_dep, xml, gcr,
+ gcr_ui, authenticator_dep, secret],
c_args : ['-DPLUGIN_RESOURCE_PATH="/org/gnome/Shotwell/Publishing"',
'-DGCR_API_SUBJECT_TO_CHANGE'],
install: true,
diff --git a/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml b/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml
index e04ab02..5272df6 100644
--- a/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml
+++ b/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/Shotwell/Publishing">
- <file>facebook.png</file>
- <file>piwigo.svg</file>
- <file>youtube.png</file>
- <file>tumblr.png</file>
- <file>google-photos.svg</file>
- <file>facebook_publishing_options_pane.ui</file>
+ <!-- icons, with additional aliases into the icon namespace -->
+ <file alias="icons/hicolor/scalable/apps/piwigo.svg">piwigo.svg</file>
+ <file alias="icons/hicolor/24x24/apps/youtube.png">youtube.png</file>
+ <file alias="icons/hicolor/scalable/apps/google-photos.svg">google-photos.svg</file>
+ <file alias="icons/hicolor/scalable/apps/tumblr.svg">tumblr.svg</file>
+
<file>flickr_publishing_options_pane.ui</file>
<file>google_photos_publishing_options_pane.ui</file>
<file>piwigo_authentication_pane.ui</file>
diff --git a/plugins/shotwell-publishing/piwigo_publishing_options_pane.ui b/plugins/shotwell-publishing/piwigo_publishing_options_pane.ui
index 397d2ec..c17d616 100644
--- a/plugins/shotwell-publishing/piwigo_publishing_options_pane.ui
+++ b/plugins/shotwell-publishing/piwigo_publishing_options_pane.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
+<!-- Generated with glade 3.22.0 -->
<interface domain="shotwell">
<requires lib="gtk+" version="3.14"/>
<object class="GtkWindow" id="publishing_options_pane">
@@ -8,8 +8,8 @@
<object class="GtkBox" id="content">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_left">10</property>
- <property name="margin_right">10</property>
+ <property name="margin_start">10</property>
+ <property name="margin_end">10</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
@@ -247,6 +247,22 @@
</packing>
</child>
<child>
+ <object class="GtkCheckButton" id="no_upload_ratings_check">
+ <property name="label" translatable="yes">_Do not upload ratings</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -280,10 +296,13 @@
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">5</property>
+ <property name="position">6</property>
</packing>
</child>
</object>
</child>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
</object>
</interface>
diff --git a/plugins/shotwell-publishing/shotwell-publishing.vala b/plugins/shotwell-publishing/shotwell-publishing.vala
index 52c82ec..f5251f4 100644
--- a/plugins/shotwell-publishing/shotwell-publishing.vala
+++ b/plugins/shotwell-publishing/shotwell-publishing.vala
@@ -13,45 +13,37 @@ private class ShotwellPublishingCoreServices : Object, Spit.Module {
// we need to get a module file handle because our pluggables have to load resources from the
// module file directory
public ShotwellPublishingCoreServices(GLib.File module_file) {
- GLib.File resource_directory = module_file.get_parent();
var factory = Publishing.Authenticator.Factory.get_instance();
var authenicators = factory.get_available_authenticators();
// Prevent vala complaining when all authenticators from this plugin
// are disabled
- debug("Looking for resources in %s", resource_directory.get_path());
debug("Found %d authenicators", authenicators.size);
-#if HAVE_FACEBOOK
- if (authenicators.contains("facebook")) {
- pluggables += new FacebookService(resource_directory);
- }
-#endif
-
#if HAVE_GOOGLEPHOTOS
if (authenicators.contains("google-photos")) {
- pluggables += new Publishing.GooglePhotos.Service(resource_directory);
+ pluggables += new Publishing.GooglePhotos.Service();
}
#endif
#if HAVE_FLICKR
if (authenicators.contains("flickr")) {
- pluggables += new FlickrService(resource_directory);
+ pluggables += new FlickrService();
}
#endif
#if HAVE_YOUTUBE
if (authenicators.contains("youtube")) {
- pluggables += new YouTubeService(resource_directory);
+ pluggables += new YouTubeService();
}
#endif
#if HAVE_PIWIGO
- pluggables += new PiwigoService(resource_directory);
+ pluggables += new PiwigoService();
#endif
#if HAVE_TUMBLR
- pluggables += new TumblrService(module_file.get_parent());
+ pluggables += new TumblrService();
#endif
}
@@ -64,7 +56,7 @@ private class ShotwellPublishingCoreServices : Object, Spit.Module {
}
public unowned string get_id() {
- return "org.yorba.shotwell.publishing.core_services";
+ return "org.gnome.shotwell.publishing.core_services";
}
public unowned Spit.Pluggable[]? get_pluggables() {
diff --git a/plugins/shotwell-publishing/tumblr.svg b/plugins/shotwell-publishing/tumblr.svg
new file mode 100644
index 0000000..d245dd1
--- /dev/null
+++ b/plugins/shotwell-publishing/tumblr.svg
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 159.30001 159.3"
+ xml:space="preserve"
+ sodipodi:docname="tumblr.svg"
+ width="159.3"
+ height="159.3"
+ inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs341" /><sodipodi:namedview
+ id="namedview339"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="false"
+ inkscape:zoom="4.9089767"
+ inkscape:cx="12.120652"
+ inkscape:cy="79.650002"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Layer_1" />
+<style
+ type="text/css"
+ id="style248">
+ .st0{fill:#FFFFFF;}
+ .st1{fill:#001935;}
+ .st2{fill:#231F20;}
+</style>
+<text
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="text250"
+ x="-696.69513"
+ y="-356.28101">Black #000000</text>
+<text
+ id="text260"
+ x="-1587.3539"
+ y="-356.28101"><tspan
+ x="-1587.3539"
+ y="-356.28101"
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="tspan252">Notes:</tspan><tspan
+ x="-1587.3539"
+ y="-246.28101"
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="tspan254">Always use the full wordmark</tspan><tspan
+ x="-1587.3539"
+ y="-191.38101"
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="tspan256">unless you’re explicitly speaking to </tspan><tspan
+ x="-1587.3539"
+ y="-136.38101"
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="tspan258">or marketing the iOS or Android app </tspan></text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text262"
+ x="-691.97113"
+ y="189.9763">iOS icon</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text264"
+ x="-696.69513"
+ y="-111.0251">Wordmark (always use full word)</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text266"
+ x="-369.3826"
+ y="189.9763">Android Icon</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text268"
+ x="-0.00025388421"
+ y="189.9763">t icon</text>
+<text
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="text270"
+ x="-696.69513"
+ y="363.68301">White #000000</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text272"
+ x="-691.97113"
+ y="909.94043">iOS icon</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text274"
+ x="-696.69513"
+ y="608.9389">Wordmark (always use full word)</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text276"
+ x="-369.3826"
+ y="909.94043">Android Icon</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text278"
+ x="-0.00025388421"
+ y="909.94043">t icon</text>
+<text
+ class="st0"
+ style="font-size:45.8156px;font-family:MyriadPro-Regular"
+ id="text280"
+ x="239.25819"
+ y="-356.28101">Blue #001935</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text282"
+ x="243.98219"
+ y="189.9763">iOS icon</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text284"
+ x="239.25819"
+ y="-111.0251">Wordmark (always use full word)</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text286"
+ x="566.57068"
+ y="189.9763">Android Icon</text>
+<text
+ class="st0"
+ style="font-size:21.8043px;font-family:MyriadPro-Regular"
+ id="text288"
+ x="935.953"
+ y="189.9763">t icon</text>
+<path
+ class="st1"
+ d="m 302.9,-147.5 c -24,0 -41.8,-12.3 -41.8,-41.8 v -47.2 h -21.8 v -25.6 c 24,-6.2 34,-26.8 35.1,-44.7 h 24.9 v 40.6 h 29 v 29.7 h -29 v 41.1 c 0,12.3 6.2,16.6 16.1,16.6 h 14.1 v 31.3 z m 89,1.4 c 13.6,0 29.3,-5.9 37.8,-16.4 v 15 h 44.8 V -173 h -11.4 v -93.3 H 409 v 25.9 h 15.4 v 44.9 c 0,3.6 -3,18.7 -23.3,18.7 -13,0 -15.4,-7.8 -15.4,-17.7 v -71.8 h -39.9 v 73.4 c 0,18.3 8.3,46.8 46.1,46.8 z m 96.9,-1.4 h 65.6 V -173 H 541 v -44.4 c 0,-6.5 5.5,-19.7 22.4,-19.7 13.6,0 16.4,8.3 16.4,18.1 v 46 h -13.2 v 25.5 h 65.1 V -173 H 618 v -44.4 c 0,-6.5 5,-19.7 21.9,-19.7 13.7,0 17.1,8.3 17.1,18.1 v 46 h -13 v 25.5 h 64.8 V -173 h -13.3 v -41.5 c 0,-24.4 -9.2,-53.2 -45.6,-53.2 -18,0 -32.7,8.5 -38.9,19 -7.8,-11.9 -19.7,-19 -37.4,-19 -14.1,0 -29.5,6 -37.8,17.4 v -16 h -49.2 v 25.9 h 15.9 V -173 H 489 v 25.5 z m 303,1.4 c 33.2,0 51,-24.9 51,-62.2 0,-35.8 -16.6,-59.3 -50.3,-59.3 -11.9,0 -23.7,4.5 -30.9,10 v -49.2 h -54.8 v 25.7 h 15.9 v 133.6 h 36.5 V -158 c 9.4,7.8 20.5,11.9 32.6,11.9 z m -8.3,-29 c -11.6,0 -23,-7.4 -23,-30.6 0,-28 13.5,-33 22.8,-33 10.8,0 20.4,8.8 20.4,30.5 0,31.4 -14.1,32.4 -20.2,33.1 z m 66.5,27.6 h 66 V -173 h -14 v -133.9 h -54.4 v 25.7 h 15.9 V -173 H 850 Z m 80.4,0 h 64.9 V -173 H 982 v -33.2 c 0,-22.6 15.9,-27.5 28,-27.5 h 16.1 v -34 h -13 c -14.7,0 -28.3,7.1 -34.4,18.5 v -17.2 h -49.4 v 25.9 h 14.3 v 67.4 h -13.1 v 25.6 z"
+ id="path290" />
+<path
+ class="st1"
+ d="m 999.6,159.3 c -24,0 -41.8,-12.3 -41.8,-41.8 V 70.3 H 936 V 44.7 C 960,38.5 970,17.9 971.1,0 H 996 v 40.6 h 29 v 29.7 h -29 v 41.1 c 0,12.3 6.2,16.6 16.1,16.6 h 14.1 v 31.3 z"
+ id="path292" />
+<g
+ id="g298">
+ <path
+ class="st1"
+ d="m 645.1,1.3 c -43.4,0 -78.6,35.1 -78.6,78.3 0,43.2 35.1,78.5 78.6,78.5 43.2,0 78.3,-35.3 78.3,-78.5 C 723.5,36.2 688.3,1.3 645.1,1.3 Z"
+ id="path294" />
+ <path
+ class="st0"
+ d="m 651.2,97.3 c 0,7.1 3.6,9.5 9.2,9.5 h 8.1 v 18 h -15.3 c -13.7,0 -24,-7.1 -24,-24 V 73.7 H 616.7 V 59 c 13.7,-3.6 19.5,-15.4 20.2,-25.6 h 14.3 v 23.3 h 16.6 v 17 h -16.6 v 23.6"
+ id="path296" />
+</g>
+<g
+ id="g304">
+ <path
+ class="st1"
+ d="m 359.9,7.3 h -87 C 257,7.3 244,20.3 244,36.2 v 87 c 0,15.9 13,28.9 28.9,28.9 h 87 c 15.9,0 28.9,-13 28.9,-28.9 v -87 c 0,-15.9 -13,-28.9 -28.9,-28.9 z"
+ id="path300" />
+ <path
+ class="st0"
+ d="M 341.3,124.8 H 326 c -13.7,0 -24,-7.1 -24,-24 V 73.7 H 289.5 V 59 C 303.2,55.4 309,43.6 309.7,33.4 H 324 v 23.3 h 16.6 v 17 H 324 v 23.6 c 0,7.1 3.6,9.5 9.2,9.5 h 8.1 z"
+ id="path302" />
+</g>
+<path
+ class="st0"
+ d="m -633.1,573.9 c -24,0 -41.8,-12.3 -41.8,-41.8 v -47.2 h -21.8 v -25.6 c 24,-6.2 34,-26.8 35.1,-44.7 h 24.9 v 40.6 h 29 v 29.7 h -29 V 526 c 0,12.3 6.2,16.6 16.1,16.6 h 14.1 v 31.3 z m 89.1,1.4 c 13.6,0 29.3,-5.9 37.8,-16.4 v 15 h 44.8 v -25.5 h -11.4 V 455.1 H -527 V 481 h 15.4 v 45 c 0,3.6 -3,18.7 -23.3,18.7 -13,0 -15.4,-7.8 -15.4,-17.7 v -71.8 h -39.9 v 73.4 c 0.1,18.3 8.4,46.7 46.2,46.7 z m 96.9,-1.4 h 65.6 v -25.5 h -13.4 V 504 c 0,-6.5 5.5,-19.7 22.4,-19.7 13.6,0 16.4,8.3 16.4,18.1 v 46 h -13.2 v 25.5 h 65.1 V 548.4 H -318 V 504 c 0,-6.5 5,-19.7 21.9,-19.7 13.7,0 17.1,8.3 17.1,18.1 v 46 h -13 v 25.5 h 64.8 v -25.5 h -13.3 V 507 c 0,-24.4 -9.2,-53.2 -45.6,-53.2 -18,0 -32.7,8.5 -38.9,19 -7.8,-11.9 -19.7,-19 -37.4,-19 -14.1,0 -29.5,6 -37.8,17.4 v -16 h -49.2 v 25.9 h 15.9 v 67.4 H -447 v 25.4 z m 303,1.4 c 33.2,0 51,-24.9 51,-62.2 0,-35.8 -16.6,-59.3 -50.3,-59.3 -11.9,0 -23.7,4.5 -30.9,10 v -49.2 h -54.8 v 25.7 h 15.9 v 133.6 h 36.5 v -10.5 c 9.3,7.9 20.5,11.9 32.6,11.9 z m -8.3,-28.9 c -11.6,0 -23,-7.4 -23,-30.6 0,-28 13.5,-33 22.8,-33 10.8,0 20.4,8.8 20.4,30.5 -0.1,31.4 -14.1,32.4 -20.2,33.1 z m 66.5,27.5 h 66 V 548.4 H -34 V 414.6 h -54.4 v 25.7 h 15.9 V 548.5 H -86 v 25.4 z m 80.4,0 H 59.4 V 548.4 H 46.1 v -33.2 c 0,-22.6 15.9,-27.5 28,-27.5 h 16.1 v -34 h -13 c -14.7,0 -28.3,7.1 -34.4,18.5 V 455 H -6.7 v 25.9 H 7.6 v 67.4 H -5.5 Z"
+ id="path306" />
+<path
+ class="st0"
+ d="m 63.6,880.8 c -24,0 -41.8,-12.3 -41.8,-41.8 V 791.8 H 0 V 766.2 C 24,760 34,739.4 35.1,721.5 H 60 V 762 h 29 v 29.7 H 60 v 41.1 c 0,12.3 6.2,16.6 16.1,16.6 h 14.1 v 31.3 H 63.6 Z"
+ id="path308" />
+<g
+ id="g314">
+ <path
+ class="st0"
+ d="m -290.8,722.7 c -43.4,0 -78.6,35.1 -78.6,78.3 0,43.2 35.1,78.5 78.6,78.5 43.2,0 78.3,-35.3 78.3,-78.5 0,-43.4 -35.1,-78.3 -78.3,-78.3 z"
+ id="path310" />
+ <path
+ class="st2"
+ d="m -284.7,818.8 c 0,7.1 3.6,9.5 9.2,9.5 h 8.1 v 18 h -15.3 c -13.7,0 -24,-7.1 -24,-24 v -27.1 h -12.5 v -14.7 c 13.7,-3.6 19.5,-15.4 20.2,-25.6 h 14.3 v 23.3 h 16.6 v 17 h -16.6 v 23.6"
+ id="path312" />
+</g>
+<g
+ id="g320">
+ <path
+ class="st0"
+ d="m -576.1,728.7 h -87 c -15.9,0 -28.9,13 -28.9,28.9 v 87 c 0,15.9 13,28.9 28.9,28.9 h 87 c 15.9,0 28.9,-13 28.9,-28.9 v -87 c 0.1,-15.9 -13,-28.9 -28.9,-28.9 z"
+ id="path316" />
+ <path
+ class="st2"
+ d="m -594.6,846.2 h -15.3 c -13.7,0 -24,-7.1 -24,-24 v -27.1 h -12.5 v -14.7 c 13.7,-3.6 19.5,-15.4 20.2,-25.6 h 14.3 v 23.3 h 16.6 v 17 h -16.6 v 23.6 c 0,7.1 3.6,9.5 9.2,9.5 h 8.1 z"
+ id="path318" />
+</g>
+<path
+ d="m -633.1,-147.5 c -24,0 -41.8,-12.3 -41.8,-41.8 v -47.2 h -21.8 v -25.6 c 24,-6.2 34,-26.8 35.1,-44.7 h 24.9 v 40.6 h 29 v 29.7 h -29 v 41.1 c 0,12.3 6.2,16.6 16.1,16.6 h 14.1 v 31.3 z m 89.1,1.4 c 13.6,0 29.3,-5.9 37.8,-16.4 v 15 h 44.8 V -173 h -11.4 v -93.3 H -527 v 25.9 h 15.4 v 44.9 c 0,3.6 -3,18.7 -23.3,18.7 -13,0 -15.4,-7.8 -15.4,-17.7 v -71.8 h -39.9 v 73.4 c 0.1,18.3 8.4,46.8 46.2,46.8 z m 96.9,-1.4 h 65.6 V -173 h -13.4 v -44.4 c 0,-6.5 5.5,-19.7 22.4,-19.7 13.6,0 16.4,8.3 16.4,18.1 v 46 h -13.2 v 25.5 h 65.1 V -173 H -318 v -44.4 c 0,-6.5 5,-19.7 21.9,-19.7 13.7,0 17.1,8.3 17.1,18.1 v 46 h -13 v 25.5 h 64.8 V -173 h -13.3 v -41.5 c 0,-24.4 -9.2,-53.2 -45.6,-53.2 -18,0 -32.7,8.5 -38.9,19 -7.8,-11.9 -19.7,-19 -37.4,-19 -14.1,0 -29.5,6 -37.8,17.4 v -16 h -49.2 v 25.9 h 15.9 V -173 H -447 v 25.5 z m 303,1.4 c 33.2,0 51,-24.9 51,-62.2 0,-35.8 -16.6,-59.3 -50.3,-59.3 -11.9,0 -23.7,4.5 -30.9,10 v -49.2 h -54.8 v 25.7 h 15.9 v 133.6 h 36.5 V -158 c 9.3,7.8 20.5,11.9 32.6,11.9 z m -8.3,-29 c -11.6,0 -23,-7.4 -23,-30.6 0,-28 13.5,-33 22.8,-33 10.8,0 20.4,8.8 20.4,30.5 -0.1,31.4 -14.1,32.4 -20.2,33.1 z m 66.5,27.6 h 66 V -173 H -34 v -133.9 h -54.4 v 25.7 h 15.9 V -173 H -86 v 25.5 z m 80.4,0 H 59.4 V -173 H 46.1 v -33.2 c 0,-22.6 15.9,-27.5 28,-27.5 h 16.1 v -34 h -13 c -14.7,0 -28.3,7.1 -34.4,18.5 v -17.2 H -6.7 v 25.9 H 7.6 v 67.4 H -5.5 Z"
+ id="path322" />
+<path
+ d="m 98.150002,159.3 c -24,0 -41.8,-12.3 -41.8,-41.8 V 70.3 h -21.8 V 44.7 c 24,-6.2 34,-26.8 35.1,-44.7 h 24.9 V 40.6 H 123.55 V 70.3 H 94.550002 v 41.1 c 0,12.3 6.199998,16.6 16.099998,16.6 h 14.1 v 31.3 z"
+ id="path324" />
+<g
+ id="g330">
+ <path
+ d="m -290.8,1.3 c -43.4,0 -78.6,35.1 -78.6,78.3 0,43.2 35.1,78.5 78.6,78.5 43.2,0 78.3,-35.3 78.3,-78.5 0,-43.4 -35.1,-78.3 -78.3,-78.3 z"
+ id="path326" />
+ <path
+ class="st0"
+ d="m -284.7,97.3 c 0,7.1 3.6,9.5 9.2,9.5 h 8.1 v 18 h -15.3 c -13.7,0 -24,-7.1 -24,-24 V 73.7 h -12.5 V 59 c 13.7,-3.6 19.5,-15.4 20.2,-25.6 h 14.3 v 23.3 h 16.6 v 17 h -16.6 v 23.6"
+ id="path328" />
+</g>
+<g
+ id="g336">
+ <path
+ d="m -576.1,7.3 h -87 c -15.9,0 -28.9,13 -28.9,28.9 v 87 c 0,15.9 13,28.9 28.9,28.9 h 87 c 15.9,0 28.9,-13 28.9,-28.9 v -87 c 0.1,-15.9 -13,-28.9 -28.9,-28.9 z"
+ id="path332" />
+ <path
+ class="st0"
+ d="m -594.6,124.8 h -15.3 c -13.7,0 -24,-7.1 -24,-24 V 73.7 h -12.5 V 59 c 13.7,-3.6 19.5,-15.4 20.2,-25.6 h 14.3 v 23.3 h 16.6 v 17 h -16.6 v 23.6 c 0,7.1 3.6,9.5 9.2,9.5 h 8.1 z"
+ id="path334" />
+</g>
+</svg>
diff --git a/plugins/shotwell-publishing/tumblr_publishing_options_pane.ui b/plugins/shotwell-publishing/tumblr_publishing_options_pane.ui
index acc5459..982dc26 100644
--- a/plugins/shotwell-publishing/tumblr_publishing_options_pane.ui
+++ b/plugins/shotwell-publishing/tumblr_publishing_options_pane.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
+<!-- Generated with glade 3.22.0 -->
<interface domain="shotwell">
<requires lib="gtk+" version="3.14"/>
<object class="GtkBox" id="tumblr_pane">
@@ -35,15 +35,16 @@ so changes made here will not display)</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
- <property name="row_spacing">8</property>
- <property name="column_spacing">24</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="blog_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Blogs:</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Blogs</property>
<property name="use_underline">True</property>
- <property name="justify">right</property>
+ <property name="justify">center</property>
<property name="mnemonic_widget">blog_combo</property>
</object>
<packing>
@@ -56,7 +57,7 @@ so changes made here will not display)</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
- <property name="label" translatable="yes">Photo _size:</property>
+ <property name="label" translatable="yes">Photo _size</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">size_combo</property>
</object>
diff --git a/plugins/shotwell-publishing/youtube_publishing_options_pane.ui b/plugins/shotwell-publishing/youtube_publishing_options_pane.ui
index 47f75a1..777632e 100644
--- a/plugins/shotwell-publishing/youtube_publishing_options_pane.ui
+++ b/plugins/shotwell-publishing/youtube_publishing_options_pane.ui
@@ -27,18 +27,17 @@
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_left">40</property>
- <property name="margin_right">40</property>
+ <property name="margin_start">40</property>
+ <property name="margin_end">40</property>
<property name="margin_top">16</property>
+ <property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="privacy_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_left">16</property>
- <property name="margin_right">16</property>
- <property name="xpad">10</property>
- <property name="label" translatable="yes">Video privacy _setting:</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Video privacy _setting</property>
<property name="use_underline">True</property>
</object>
<packing>
@@ -51,10 +50,6 @@
<object class="GtkComboBoxText" id="privacy_combo">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_left">16</property>
- <property name="margin_right">32</property>
- <property name="entry_text_column">0</property>
- <property name="id_column">1</property>
</object>
<packing>
<property name="expand">False</property>
@@ -74,8 +69,8 @@
<object class="GtkBox" id="button_area_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_left">112</property>
- <property name="margin_right">112</property>
+ <property name="margin_start">112</property>
+ <property name="margin_end">112</property>
<property name="margin_top">48</property>
<property name="margin_bottom">24</property>
<property name="spacing">128</property>
@@ -86,7 +81,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
</object>
<packing>
@@ -101,7 +95,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
</object>
<packing>