[shotwell] youtube: Port to libgdata
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell] youtube: Port to libgdata
- Date: Sat, 25 Feb 2017 21:20:47 +0000 (UTC)
commit f5a89c745f00f15d88e63a401e9fb6d9c4fca7e5
Author: Jens Georg <mail jensge org>
Date: Sat Feb 11 22:29:39 2017 +0100
youtube: Port to libgdata
Make YouTube upload work again
Signed-off-by: Jens Georg <mail jensge org>
https://bugzilla.gnome.org/show_bug.cgi?id=777910
configure.ac | 2 +-
plugins/shotwell-publishing/YouTubePublishing.vala | 270 +++++---------------
publish.am | 2 +
3 files changed, 68 insertions(+), 206 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3d11eed..a4fbaca 100644
--- a/configure.ac
+++ b/configure.ac
@@ -132,7 +132,7 @@ PKG_CHECK_MODULES(IMPORT, [gee-0.8 >= 0.8.5 glib-2.0 gio-2.0 sqlite3])
PKG_CHECK_MODULES(PUBLISHING, [gobject-2.0 glib-2.0 gexiv2 json-glib-1.0
gee-0.8 libsoup-2.4 libxml-2.0 gtk+-3.0
- webkit2gtk-4.0 gcr-3 gcr-ui-3])
+ webkit2gtk-4.0 gcr-3 gcr-ui-3 libgdata])
PKG_CHECK_MODULES(TRANSITIONS, [gobject-2.0 cairo gio-2.0 gdk-pixbuf-2.0
gdk-3.0])
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala
b/plugins/shotwell-publishing/YouTubePublishing.vala
index 0997f20..d0afd64 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -65,12 +65,10 @@ private enum PrivacySetting {
private class PublishingParameters {
private PrivacySetting privacy;
- private string? channel_name;
private string? user_name;
public PublishingParameters() {
this.privacy = PrivacySetting.PRIVATE;
- this.channel_name = null;
this.user_name = null;
}
@@ -82,14 +80,6 @@ private class PublishingParameters {
this.privacy = privacy;
}
- public string? get_channel_name() {
- return channel_name;
- }
-
- public void set_channel_name(string? channel_name) {
- this.channel_name = channel_name;
- }
-
public string? get_user_name() {
return user_name;
}
@@ -99,28 +89,45 @@ private class PublishingParameters {
}
}
-public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
- private class ChannelDirectoryTransaction :
- Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
- private const string ENDPOINT_URL = "https://gdata.youtube.com/feeds/users/default";
+internal class YoutubeAuthorizer : GData.Authorizer, Object {
+ private RESTSupport.GoogleSession session;
- public ChannelDirectoryTransaction(Publishing.RESTSupport.GoogleSession session) {
- base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
- }
+ public YoutubeAuthorizer(RESTSupport.GoogleSession session) {
+ this.session = session;
+ }
- public static string? validate_xml(Publishing.RESTSupport.XmlDocument doc) {
- Xml.Node* document_root = doc.get_root_node();
- if ((document_root->name == "feed") || (document_root->name == "entry"))
- return null;
- else
- return "response root node isn't a <feed> or <entry>";
- }
+ public bool is_authorized_for_domain(GData.AuthorizationDomain domain) {
+ return true;
}
-
+
+ public void process_request(GData.AuthorizationDomain? domain,
+ Soup.Message message) {
+ critical ("process_request called");
+ 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 {
+ critical("refresh_authorization called");
+ return false;
+ }
+
+ public async bool refresh_authorization_async (GLib.Cancellable? cancellable) throws GLib.Error {
+ critical("refresh_authorization_async called");
+ Idle.add(() => { refresh_authorization_async.callback(); return false; });
+
+ yield;
+
+ return false;
+ }
+}
+
+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/");
@@ -152,81 +159,15 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
get_session().stop_transactions();
}
-
- private string extract_channel_name_helper(Xml.Node* document_root) throws
- Spit.Publishing.PublishingError {
- string result = "";
-
- Xml.Node* doc_node_iter = null;
- if (document_root->name == "feed")
- doc_node_iter = document_root->children;
- else if (document_root->name == "entry")
- doc_node_iter = document_root;
- else
- throw new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
- "response root node isn't a <feed> or <entry>");
-
- for ( ; doc_node_iter != null; doc_node_iter = doc_node_iter->next) {
- if (doc_node_iter->name != "entry")
- continue;
-
- string name_val = null;
- string url_val = null;
- Xml.Node* channel_node_iter = doc_node_iter->children;
- for ( ; channel_node_iter != null; channel_node_iter = channel_node_iter->next) {
- if (channel_node_iter->name == "title") {
- name_val = channel_node_iter->get_content();
- } else if (channel_node_iter->name == "id") {
- // we only want nodes in the default namespace -- the feed that we get back
- // from Google also defines <entry> child nodes named <id> in the media
- // namespace
- if (channel_node_iter->ns->prefix != null)
- continue;
- url_val = channel_node_iter->get_content();
- }
- }
-
- result = name_val;
- break;
- }
-
- debug("YouTubePublisher: extracted channel name '%s' from response XML.", result);
-
- return result;
- }
protected override void on_login_flow_complete() {
debug("EVENT: OAuth login flow complete.");
publishing_parameters.set_user_name(get_session().get_user_name());
- do_fetch_account_information();
- }
-
- private void on_initial_channel_fetch_complete(Publishing.RESTSupport.Transaction txn) {
- txn.completed.disconnect(on_initial_channel_fetch_complete);
- txn.network_error.disconnect(on_initial_channel_fetch_error);
-
- debug("EVENT: finished fetching account and channel information.");
-
- if (!is_running())
- return;
-
- do_parse_and_display_account_information((ChannelDirectoryTransaction) txn);
- }
-
- private void on_initial_channel_fetch_error(Publishing.RESTSupport.Transaction bad_txn,
- Spit.Publishing.PublishingError err) {
- bad_txn.completed.disconnect(on_initial_channel_fetch_complete);
- bad_txn.network_error.disconnect(on_initial_channel_fetch_error);
-
- debug("EVENT: fetching account and channel information failed; response = '%s'.",
- bad_txn.get_response());
-
- if (!is_running())
- return;
-
- get_host().post_error(err);
+ this.youtube_service = new GData.YouTubeService(DEVELOPER_KEY,
+ new YoutubeAuthorizer(get_session()));
+ do_show_publishing_options_pane();
}
private void on_publishing_options_logout() {
@@ -284,47 +225,6 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
get_host().post_error(err);
}
- private void do_fetch_account_information() {
- debug("ACTION: fetching channel information.");
-
- get_host().install_account_fetch_wait_pane();
- get_host().set_service_locked(true);
-
- ChannelDirectoryTransaction directory_trans =
- new ChannelDirectoryTransaction(get_session());
- directory_trans.network_error.connect(on_initial_channel_fetch_error);
- directory_trans.completed.connect(on_initial_channel_fetch_complete);
-
- try {
- directory_trans.execute();
- } catch (Spit.Publishing.PublishingError err) {
- on_initial_channel_fetch_error(directory_trans, err);
- }
- }
-
- private void do_parse_and_display_account_information(ChannelDirectoryTransaction transaction) {
- debug("ACTION: extracting account and channel information from body of server response");
-
- Publishing.RESTSupport.XmlDocument response_doc;
- try {
- response_doc = Publishing.RESTSupport.XmlDocument.parse_string(
- transaction.get_response(), ChannelDirectoryTransaction.validate_xml);
- } catch (Spit.Publishing.PublishingError err) {
- get_host().post_error(err);
- return;
- }
-
- try {
- publishing_parameters.set_channel_name(extract_channel_name_helper(
- response_doc.get_root_node()));
- } catch (Spit.Publishing.PublishingError err) {
- get_host().post_error(err);
- return;
- }
-
- do_show_publishing_options_pane();
- }
-
private void do_show_publishing_options_pane() {
debug("ACTION: showing publishing options pane.");
@@ -366,7 +266,7 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
return;
Spit.Publishing.Publishable[] publishables = get_host().get_publishables();
- Uploader uploader = new Uploader(get_session(), publishables, publishing_parameters);
+ Uploader uploader = new Uploader(this.youtube_service, get_session(), publishables,
publishing_parameters);
uploader.upload_complete.connect(on_upload_complete);
uploader.upload_error.connect(on_upload_error);
@@ -392,7 +292,8 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
protected override Spit.Publishing.Authenticator get_authenticator() {
if (this.authenticator == null) {
- this.authenticator = Publishing.Authenticator.Factory.get_instance().create("picasa",
get_host());
+ this.authenticator =
+ Publishing.Authenticator.Factory.get_instance().create("youtube", get_host());
}
return this.authenticator;
@@ -415,7 +316,6 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
private Gtk.Box pane_widget = null;
private Gtk.ComboBoxText privacy_combo = null;
- private Gtk.Label publish_to_label = null;
private Gtk.Label login_identity_label = null;
private Gtk.Button publish_button = null;
private Gtk.Button logout_button = null;
@@ -437,7 +337,6 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
login_identity_label = this.builder.get_object("login_identity_label") as Gtk.Label;
privacy_combo = this.builder.get_object("privacy_combo") as Gtk.ComboBoxText;
- publish_to_label = this.builder.get_object("publish_to_label") as Gtk.Label;
publish_button = this.builder.get_object("publish_button") as Gtk.Button;
logout_button = this.builder.get_object("logout_button") as Gtk.Button;
pane_widget = this.builder.get_object("youtube_pane_widget") as Gtk.Box;
@@ -449,8 +348,6 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
login_identity_label.set_label(_("You are logged into YouTube as %s.").printf(
publishing_parameters.get_user_name()));
- publish_to_label.set_label(_("Videos will appear in ā%sā").printf(
- publishing_parameters.get_channel_name()));
foreach(PrivacyDescription desc in privacy_descriptions) {
privacy_combo.append_text(desc.description);
@@ -507,107 +404,70 @@ 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 const string UNLISTED_XML = "<yt:accessControl action='list' permission='denied'/>";
- private const string PRIVATE_XML = "<yt:private/>";
- private const string METADATA_TEMPLATE ="""<?xml version='1.0'?>
- <entry xmlns='http://www.w3.org/2005/Atom'
- xmlns:media='http://search.yahoo.com/mrss/'
- xmlns:yt='http://gdata.youtube.com/schemas/2007'>
- <media:group>
- <media:title type='plain'>%s</media:title>
- <media:category
-
scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>People
- </media:category>
- %s
- </media:group>
- %s
- </entry>""";
private PublishingParameters parameters;
private Publishing.RESTSupport.GoogleSession session;
private Spit.Publishing.Publishable publishable;
+ private GData.YouTubeService youtube_service;
- public UploadTransaction(Publishing.RESTSupport.GoogleSession session,
+ 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 {
- // create the multipart request container
- Soup.Multipart message_parts = new Soup.Multipart("multipart/related");
-
- string unlisted_video =
- (parameters.get_privacy() == PrivacySetting.UNLISTED) ? UNLISTED_XML : "";
-
- string private_video =
- (parameters.get_privacy() == PrivacySetting.PRIVATE) ? PRIVATE_XML : "";
+ 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;
- string metadata = METADATA_TEMPLATE.printf(Publishing.RESTSupport.decimal_entity_encode(title),
- private_video, unlisted_video);
- Soup.Buffer metadata_buffer = new Soup.Buffer(Soup.MemoryUse.COPY, metadata.data);
- message_parts.append_form_file("", "", "application/atom+xml", metadata_buffer);
+ video.is_private = (parameters.get_privacy() == PrivacySetting.PRIVATE);
- // attempt to read the binary video data from disk
- string video_data;
- size_t data_length;
- try {
- FileUtils.get_contents(publishable.get_serialized_file().get_path(), out video_data,
- out data_length);
- } catch (FileError e) {
- string msg = "YouTube: couldn't read data from %s: %s".printf(
- publishable.get_serialized_file().get_path(), e.message);
- warning("%s", msg);
-
- throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(msg);
+ 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);
}
- // bind the binary video 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.COPY,
- video_data.data[0:data_length]);
-
- message_parts.append_form_file("", publishable.get_serialized_file().get_path(),
- "video/mpeg", bindable_data);
- // create a message that can be sent over the wire whose payload is the multipart container
- // that we've been building up
- Soup.Message outbound_message =
- Soup.Form.request_new_from_multipart(get_endpoint_url(), message_parts);
- outbound_message.request_headers.append("X-GData-Key", "key=%s".printf(DEVELOPER_KEY));
- outbound_message.request_headers.append("Slug",
- publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME));
- outbound_message.request_headers.append("Authorization", "Bearer " +
- session.get_access_token());
- set_message(outbound_message);
-
- // send the message and get its response
- set_is_executed(true);
- send();
+ var file = publishable.get_serialized_file();
+
+ try {
+ var info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE, FileQueryInfoFlags.NONE);
+ var upload_stream = this.youtube_service.upload_video(video, slug,
+ info.get_content_type());
+ var input_stream = file.read();
+ upload_stream.splice(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE |
OutputStreamSpliceFlags.CLOSE_TARGET);
+ video = this.youtube_service.finish_video_upload(upload_stream);
+ } catch (Error error) {
+ critical("Upload failed: %s", error.message);
+ }
}
}
internal class Uploader : Publishing.RESTSupport.BatchUploader {
private PublishingParameters parameters;
+ private GData.YouTubeService youtube_service;
- public Uploader(Publishing.RESTSupport.GoogleSession session,
+ public Uploader(GData.YouTubeService youtube_service, 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((Publishing.RESTSupport.GoogleSession) get_session(),
+ return new UploadTransaction(this.youtube_service, (Publishing.RESTSupport.GoogleSession)
get_session(),
parameters, get_current_publishable());
}
}
diff --git a/publish.am b/publish.am
index 9736191..952ba62 100644
--- a/publish.am
+++ b/publish.am
@@ -53,6 +53,8 @@ plugins_shotwell_publishing_shotwell_publishing_la_VALAFLAGS = \
--pkg webkit2gtk-4.0 \
--pkg gcr-3 \
--pkg gcr-ui-3 \
+ --pkg libgdata \
+ --pkg goa-1.0 \
--vapidir $(abs_top_srcdir)/plugins
plugins_shotwell_publishing_shotwell_publishing_la_CFLAGS = \
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]