Hi all.
I believe I've addressed all comments at
http://trac.yorba.org/ticket/2536#comment:2
either in private email to Jim or here.
Please review and commit.
I also added all people in tracker's CC list here, hope its ok.
Patch attached.
--
Evgeniy Polyakov
Index: src/Config.vala
===================================================================
--- src/Config.vala (revision 2214)
+++ src/Config.vala (working copy)
@@ -287,6 +287,21 @@
return get_string("/apps/shotwell/sharing/picasa/auth_token");
}
+ public string? get_gconf_string(string id, string key) {
+ return get_string(_("/apps/shotwell/sharing/%s/%s").printf(id, key));
+ }
+
+ public void set_gconf_string(string id, string key, string value) {
+ set_string(_("/apps/shotwell/sharing/%s/%s").printf(id, key), value);
+ }
+
+ public void unset_gconf_string(string id, string key) {
+ try {
+
client.recursive_unset(_("/apps/shotwell/sharing/%s/%s").printf(id, key),
GConf.UnsetFlags.NAMES);
+ } catch (GLib.Error err) {
+ }
+ }
+
public bool set_printing_content_layout(int layout_code) {
return set_int("/apps/shotwell/printing/content_layout", layout_code +
1);
}
Index: src/WebConnectors.vala
===================================================================
--- src/WebConnectors.vala (revision 2214)
+++ src/WebConnectors.vala (working copy)
@@ -1110,6 +1110,7 @@
result += "Facebook";
result += "Flickr";
result += "Picasa Web Albums";
+ result += "Yandex.Fotki";
return result;
}
@@ -1121,6 +1122,8 @@
return new FlickrConnector.Interactor(host);
} else if (service_name == "Picasa Web Albums") {
return new PicasaConnector.Interactor(host);
+ } else if (service_name == "Yandex.Fotki") {
+ return new YandexConnector.Interactor(host);
} else {
error("ServiceInteractor: unsupported service '%s'", service_name);
}
Index: src/YandexConnector.vala
===================================================================
--- src/YandexConnector.vala (revision 0)
+++ src/YandexConnector.vala (revision 0)
@@ -0,0 +1,829 @@
+/* Copyright 2010+ Evgeniy Polyakov <[email protected]>
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+#if !NO_PUBLISHING
+
+namespace YandexConnector {
+ private const string SERVICE_WELCOME_MESSAGE = _("You are not currently
logged into Yandex.Fotki.");
+ private const string RESTART_ERROR_MESSAGE = _("You have already logged
in and out of Yandex.Fotki during this Shotwell session.\nTo continue
publishing to Yandex.Fotki, quit and restart Shotwell, then try publishing
again.");
+
+ private string client_id;
+ private string auth_host;
+ private string service_host;
+
+ private string service_url;
+
+ private const string yandex_upload_tag = "yandex_uploaded";
+
+ private class YandexLoginWelcomePane : PublishingDialogPane {
+ private weak Interactor interactor;
+ private Gtk.Button login_button;
+ private Gtk.Entry username_entry;
+
+ public signal void login_requested(string text);
+
+ private void on_login_clicked() {
+ login_requested(username_entry.text);
+ }
+
+ private void on_username_changed() {
+ login_button.set_sensitive(username_entry.get_text() != "");
+ }
+
+ public YandexLoginWelcomePane(Interactor interactor, string
service_welcome_message) {
+ this.interactor = interactor;
+
+ Gtk.SeparatorToolItem top_space = new Gtk.SeparatorToolItem();
+ top_space.set_draw(false);
+ Gtk.SeparatorToolItem bottom_space = new Gtk.SeparatorToolItem();
+ bottom_space.set_draw(false);
+ add(top_space);
+
+ Gtk.Table content_layouter = new Gtk.Table(2, 1, false);
+
+ Gtk.Label not_logged_in_label = new Gtk.Label("");
+ not_logged_in_label.set_use_markup(true);
+ not_logged_in_label.set_markup(service_welcome_message);
+ not_logged_in_label.set_line_wrap(true);
+
not_logged_in_label.set_size_request(PublishingDialog.STANDARD_CONTENT_LABEL_WIDTH,
-1);
+ content_layouter.attach(not_logged_in_label, 0, 1, 0, 1,
+ Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+ Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 6, 0);
+
not_logged_in_label.set_size_request(PublishingDialog.STANDARD_CONTENT_LABEL_WIDTH,
112);
+ not_logged_in_label.set_alignment(0.5f, 0.0f);
+
+ login_button = new Gtk.Button.with_mnemonic(_("_Login"));
+
login_button.set_size_request(PublishingDialog.STANDARD_ACTION_BUTTON_WIDTH,
-1);
+ login_button.clicked.connect(on_login_clicked);
+
+ username_entry = new Gtk.Entry();
+
+ Gtk.Label username_label = new Gtk.Label("Username:");
+
+ auth_host = "oauth.morelia.yandex.ru";
+ service_host = "fimp.apodora.yandex.ru";
+ client_id = "ce22bf30fdbb413fa71436295fe803d1";
+
+ var auth = yandex_session.load_auth_host();
+ if (auth != null)
+ auth_host = auth;
+
+ var service = yandex_session.load_service_host();
+ if (service != null)
+ service_host = service;
+
+ var cid = yandex_session.load_client_id();
+ if (cid != null)
+ client_id = cid;
+
+ var username = yandex_session.load_username();
+ if (username != null)
+ username_entry.set_text(username);
+ username_entry.changed.connect(on_username_changed);
+
+ username_label.set_mnemonic_widget(username_entry);
+
+ service_url =_("http://%s/api/users/").printf(service_host);
+
+ var hbox = new Gtk.HBox (false, 20);
+ hbox.pack_start(username_label, false, true, 0);
+ hbox.pack_start(username_entry, false, true, 0);
+ hbox.pack_start(login_button, false, true, 0);
+
+ Gtk.Alignment login_button_aligner = new Gtk.Alignment(0.5f, 0.5f,
0.0f, 0.0f);
+ login_button_aligner.add(hbox);
+
+ content_layouter.attach(login_button_aligner, 0, 1, 1, 2,
+ Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+ Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 6, 0);
+ add(content_layouter);
+
+ add(bottom_space);
+ bottom_space.set_size_request(-1, 112);
+ }
+
+ public override void installed() {
+ username_entry.grab_focus();
+ username_entry.set_activates_default(true);
+ login_button.can_default = true;
+ interactor.get_host().set_default(login_button);
+ }
+ }
+
+ public class yandex_transaction: RESTTransaction {
+ private void add_headers(yandex_session session) {
+ if (session.is_authenticated())
+ add_header("Authorization", _("OAuth
%s").printf(session.get_access_token()));
+ }
+
+ public yandex_transaction.with_url(yandex_session session, string url,
HttpMethod method = HttpMethod.GET) {
+ base.with_endpoint_url(session, url, method);
+ add_headers(session);
+ }
+
+ public yandex_transaction(yandex_session session, HttpMethod method =
HttpMethod.GET) {
+ base(session, method);
+ add_headers(session);
+ }
+
+ public void add_data(string type, string data) {
+ set_custom_payload(data, type);
+ }
+ }
+
+ public class Interactor: ServiceInteractor {
+ private WebAuthenticationPane web_auth_pane = null;
+ private ProgressPane progress_pane;
+ private yandex_session session = null;
+ private Photo []photos;
+
+ public override string get_name() { return "Yandex"; }
+ public override void cancel_interaction() {
session.stop_transactions(); }
+
+ public Interactor(PublishingDialog host) {
+ base(host);
+ }
+
+ internal new PublishingDialog get_host() {
+ return base.get_host();
+ }
+
+ public void service_get_album_list_error(RESTTransaction t,
PublishingError err) {
+ stderr.printf("failed to get album list\n");
+ t.completed.disconnect(service_get_album_list_complete);
+ t.network_error.disconnect(service_get_album_list_error);
+ yandex_request_web_auth();
+ }
+
+ public void service_get_album_list_complete(RESTTransaction t) {
+ t.completed.disconnect(service_get_album_list_complete);
+ t.network_error.disconnect(service_get_album_list_error);
+
+ debug("service_get_album_list_complete: %s\n", t.get_response());
+
+ session.save_tokens();
+
+ parse_album_list(t.get_response());
+
+ PublishingOptionsPane publishing_options_pane = new
PublishingOptionsPane(session);
+
+ publishing_options_pane.publish.connect(on_publish);
+ publishing_options_pane.logout.connect(on_logout);
+ get_host().install_pane(publishing_options_pane);
+ }
+
+ private void on_logout() {
+ debug("Logout\n");
+ start_interaction();
+ }
+
+ private void on_upload_complete(BatchUploader uploader, int
num_published) {
+ uploader.status_updated.disconnect(progress_pane.set_status);
+ uploader.upload_complete.disconnect(on_upload_complete);
+ uploader.upload_error.disconnect(on_upload_error);
+
+ if (num_published == 0)
+ post_error(new PublishingError.LOCAL_FILE_ERROR(""));
+
+ get_host().unlock_service();
+ get_host().set_close_button_mode();
+
+ get_host().install_pane(new SuccessPane());
+ }
+
+ private void on_upload_error(BatchUploader uploader, PublishingError
err) {
+ uploader.status_updated.disconnect(progress_pane.set_status);
+ uploader.upload_complete.disconnect(on_upload_complete);
+ uploader.upload_error.disconnect(on_upload_error);
+
+ post_error(err);
+ }
+
+ private void start_upload() {
+ debug("Publishing to %s : %s\n", session.get_destination_album(),
session.get_destination_album_url());
+
+ get_host().lock_service();
+ get_host().set_cancel_button_mode();
+
+ progress_pane = new ProgressPane();
+ get_host().install_pane(progress_pane);
+
+ Uploader uploader = new Uploader(session, photos);
+
+ uploader.status_updated.connect(progress_pane.set_status);
+
+ uploader.upload_complete.connect(on_upload_complete);
+ uploader.upload_error.connect(on_upload_error);
+
+ uploader.upload();
+ }
+
+ private void on_publish() {
+ if (session.get_destination_album_url() == null)
+ create_destination_album();
+ else
+ start_upload();
+ }
+
+ public void service_get_album_list() {
+ string url = session.get_album_list_url();
+
+ debug("getting album list from %s\n", url);
+
+ yandex_transaction t = new yandex_transaction.with_url(session,
url);
+ t.completed.connect(service_get_album_list_complete);
+ t.network_error.connect(service_get_album_list_error);
+ t.execute();
+ }
+
+ public void service_doc_transaction_error(RESTTransaction t,
PublishingError err) {
+ t.completed.disconnect(service_doc_transaction_complete);
+ t.network_error.disconnect(service_doc_transaction_error);
+ yandex_request_web_auth();
+ }
+
+ public void service_doc_transaction_complete(RESTTransaction t) {
+ t.completed.disconnect(service_doc_transaction_complete);
+ t.network_error.disconnect(service_doc_transaction_error);
+
+ debug("service_doc completed: %s", t.get_response());
+
+ try {
+ RESTXmlDocument doc =
RESTXmlDocument.parse_string(t.get_response(), check_response);
+ Xml.Node* root = doc.get_root_node();
+
+ for (Xml.Node* work = root->children ; work != null; work =
work->next) {
+ if (work->name != "workspace")
+ continue;
+ for (Xml.Node* c = work->children ; c != null; c =
c->next) {
+ if (c->name != "collection")
+ continue;
+
+ if (c->get_prop("id") == "album-list") {
+ session.set_album_list_url(c->get_prop("href"));
+
+ service_get_album_list();
+ break;
+ }
+ }
+ }
+ } catch (PublishingError err) {
+ post_error(err);
+ }
+ }
+
+ private new string? check_response(RESTXmlDocument doc) {
+ return null;
+ }
+
+ private void parse_album_entry(Xml.Node *e) throws PublishingError {
+ string title = null;
+ string link = null;
+
+ for (Xml.Node* c = e->children ; c != null; c = c->next) {
+ if (c->name == "title")
+ title = c->get_content();
+
+ if ((c->name == "link") && (c->get_prop("rel") == "photos"))
+ link = c->get_prop("href");
+
+ if (title != null && link != null) {
+ session.add_album(title, link);
+ title = null;
+ link = null;
+ break;
+ }
+ }
+ }
+
+ public void parse_album_creation(string data) {
+ try {
+ RESTXmlDocument doc = RESTXmlDocument.parse_string(data,
check_response);
+ Xml.Node *root = doc.get_root_node();
+
+ parse_album_entry(root);
+ } catch (PublishingError err) {
+ post_error(err);
+ }
+ }
+
+ public void parse_album_list(string data) {
+ try {
+ RESTXmlDocument doc = RESTXmlDocument.parse_string(data,
check_response);
+ Xml.Node *root = doc.get_root_node();
+
+ for (Xml.Node *e = root->children ; e != null; e = e->next) {
+ if (e->name != "entry")
+ continue;
+
+ parse_album_entry(e);
+ }
+ } catch (PublishingError err) {
+ post_error(err);
+ }
+ }
+
+ private void on_web_auth_pane_token_check_required(string
access_token, string refresh_token) {
+ session.set_tokens(access_token, refresh_token);
+
+ get_host().lock_service();
+ get_host().set_cancel_button_mode();
+
+ yandex_transaction t = new yandex_transaction(session);
+ t.completed.connect(service_doc_transaction_complete);
+ t.network_error.connect(service_doc_transaction_error);
+ t.execute();
+ }
+
+ private void yandex_request_web_auth() {
+ session.want_web_check = false;
+ session.set_tokens(null, null);
+ web_auth_pane = new
WebAuthenticationPane(_("http://%s/authorize?client_id=%s&response_type=code").printf(auth_host,
client_id));
+
web_auth_pane.token_check_required.connect(on_web_auth_pane_token_check_required);
+ get_host().install_pane(web_auth_pane);
+ }
+
+ private void yandex_login_pane(string username) {
+ session = new yandex_session(username);
+
+ get_host().lock_service();
+ get_host().set_cancel_button_mode();
+ get_host().set_large_window_mode();
+
+ if (!session.is_authenticated()) {
+ yandex_request_web_auth();
+ } else {
+ session.want_web_check = true;
+
on_web_auth_pane_token_check_required(session.get_access_token(),
session.get_refresh_token());
+ }
+ }
+
+ public override void start_interaction() {
+ debug("Yandex.Interactor: starting iteractor\n");
+
+ photos = get_host().get_photos();
+
+ var total = photos.length;
+ var tagged = 0;
+
+ foreach (Photo p in photos) {
+ LibraryPhoto lphoto =
LibraryPhoto.global.fetch(p.get_photo_id());
+ var contains =
Tag.for_name(yandex_upload_tag).contains(lphoto);
+
+ if (contains)
+ tagged++;
+ }
+
+ if (total == tagged) {
+ get_host().lock_service();
+ get_host().set_cancel_button_mode();
+ get_host().install_pane(new StaticMessagePane(_("There are no
selected untagged photos to upload.\nPlease remove tag '%s' from selected
photos if you want to upload them.").printf(yandex_upload_tag)));
+ return;
+ }
+
+ Photo []photos_tmp = new Photo[total - tagged];
+ var pos = 0;
+ foreach (Photo p in photos) {
+ LibraryPhoto lphoto =
LibraryPhoto.global.fetch(p.get_photo_id());
+ var contains =
Tag.for_name(yandex_upload_tag).contains(lphoto);
+
+ if (contains)
+ continue;
+
+ if (pos < photos_tmp.length) {
+ photos_tmp[pos] = p;
+ pos++;
+ }
+ }
+
+ photos = photos_tmp;
+
+ get_host().unlock_service();
+ get_host().set_cancel_button_mode();
+
+ YandexLoginWelcomePane p = new YandexLoginWelcomePane(this,
SERVICE_WELCOME_MESSAGE);
+ p.login_requested.connect(yandex_login_pane);
+
+ get_host().install_pane(p);
+ }
+
+ private void album_creation_error(RESTTransaction t, PublishingError
err) {
+ t.completed.disconnect(album_creation_complete);
+ t.network_error.disconnect(album_creation_error);
+ yandex_request_web_auth();
+ }
+
+ private void album_creation_complete(RESTTransaction t) {
+ t.completed.disconnect(album_creation_complete);
+ t.network_error.disconnect(album_creation_error);
+
+ parse_album_creation(t.get_response());
+
+ if (session.get_destination_album_url() != null)
+ start_upload();
+ else
+ post_error(new PublishingError.PROTOCOL_ERROR("Server did not
create album"));
+ }
+
+ private void create_destination_album() {
+ string album = session.get_destination_album();
+ string url = _("%s/albums/").printf(session.get_endpoint_url());
+ string data = _("<entry xmlns=\"http://www.w3.org/2005/Atom\"
xmlns:f=\"yandex:fotki\"><title>%s</title></entry>").printf(album);
+
+ yandex_transaction t = new yandex_transaction.with_url(session,
url, HttpMethod.POST);
+
+ t.add_data("application/atom+xml; charset=utf-8; type=entry",
data);
+
+ t.completed.connect(album_creation_complete);
+ t.network_error.connect(album_creation_error);
+ t.execute();
+ }
+ }
+
+ public class yandex_publish_options {
+ public bool xxx = false;
+ public bool disable_comments = false;
+ public bool hide_original = false;
+ public string access_type;
+ }
+
+ public class yandex_session: RESTSession {
+ private string access_token = null;
+ private string refresh_token = null;
+ private string album_list_url = null;
+ public Gee.HashMap<string, string> album_list = null;
+ private string destination_album = null;
+ public yandex_publish_options options;
+ private string username = null;
+ public Gee.HashMap<RESTTransaction, PhotoID?> transactions = null;
+
+ public bool want_web_check = false;
+
+ public void set_tokens(string? access_token, string? refresh_token) {
+ debug("session: setting tokens: %s %s\n", access_token,
refresh_token);
+ this.access_token = access_token;
+ this.refresh_token = refresh_token;
+
+ if ((access_token == null) || (refresh_token == null))
+ save_tokens();
+ }
+
+ public yandex_session(string username) {
+ Config config = Config.get_instance();
+ base(_("%s%s/").printf(service_url, username));
+
+ transactions = new Gee.HashMap<RESTTransaction, PhotoID?>();
+
+ if (yandex_session.load_username() != username) {
+ config.unset_gconf_string("yandex", "access_token");
+ config.unset_gconf_string("yandex", "refresh_token");
+ } else {
+ access_token = config.get_gconf_string("yandex",
"access_token");
+ refresh_token = config.get_gconf_string("yandex",
"refresh_token");
+ }
+
+ save_username(username);
+ this.username = username;
+
+ album_list = new Gee.HashMap<string, string>();
+ options = new yandex_publish_options();
+ }
+
+ public string get_username() { return username; }
+
+ public bool is_authenticated() {
+ return access_token != null;
+ }
+ public string get_access_token() {
+ assert(is_authenticated());
+ return access_token;
+ }
+ public string get_refresh_token() {
+ assert(is_authenticated());
+ return refresh_token;
+ }
+
+ public void set_album_list_url(string url) {
+ this.album_list_url = url;
+ }
+
+ public bool has_album_list_url() {
+ return album_list_url != null;
+ }
+ public string get_album_list_url() {
+ assert(has_album_list_url());
+ return album_list_url;
+ }
+
+ public void add_album(string title, string link) {
+ debug("add album: %s %s\n", title, link);
+ album_list.set(title, link);
+ }
+
+ public void set_destination_album(string album) {
+ destination_album = album;
+ }
+ public string get_destination_album_url() {
+ return album_list[destination_album];
+ }
+ public string get_destination_album() {
+ return destination_album;
+ }
+
+ public static void save_username(string username) {
+ Config.get_instance().set_gconf_string("yandex", "username",
username);
+ }
+
+ public static string? load_username() {
+ return Config.get_instance().get_gconf_string("yandex",
"username");
+ }
+
+ public static string? load_auth_host() {
+ return Config.get_instance().get_gconf_string("yandex",
"auth_host");
+ }
+
+ public static string? load_client_id() {
+ return Config.get_instance().get_gconf_string("yandex",
"client_id");
+ }
+
+ public static string? load_service_host() {
+ return Config.get_instance().get_gconf_string("yandex",
"service_host");
+ }
+
+ public void save_tokens() {
+ Config client = Config.get_instance();
+ if (access_token == null) {
+ client.unset_gconf_string("yandex", "access_token");
+ client.unset_gconf_string("yandex", "refresh_token");
+ } else {
+ client.set_gconf_string("yandex", "access_token",
access_token);
+ client.set_gconf_string("yandex", "refresh_token",
refresh_token);
+ }
+ }
+ }
+
+ private class Uploader: BatchUploader {
+ private yandex_session session;
+
+ public Uploader(yandex_session session, Photo []photos) {
+ base(photos);
+
+ this.session = session;
+ }
+
+ protected override bool
prepare_file(BatchUploader.TemporaryFileDescriptor file) {
+ try {
+ file.source_photo.export(file.temp_file,
+ Scaling.for_original(),
+ Jpeg.Quality.MAXIMUM,
+ PhotoFileFormat.JFIF);
+ } catch(Error e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void on_file_uploaded(RESTTransaction txn) {
+ if (txn in session.transactions) {
+ LibraryPhoto lphoto =
LibraryPhoto.global.fetch(session.transactions[txn]);
+ Tag.for_name(yandex_upload_tag).attach(lphoto);
+ session.transactions.unset(txn);
+ } else {
+ stderr.printf("Failed to match photo ID to transaction: %s\n",
txn.get_response());
+ return;
+ }
+ }
+
+ protected override RESTTransaction
create_transaction_for_file(BatchUploader.TemporaryFileDescriptor file) {
+ RESTTransaction t = new upload_transaction(session,
file.temp_file.get_path(), file.source_photo);
+
+ session.transactions.set(t, file.source_photo.get_photo_id());
+ t.completed.connect(on_file_uploaded);
+ return t;
+ }
+ }
+
+ private class upload_transaction: yandex_transaction {
+ public upload_transaction(yandex_session session, string source_file,
Photo photo) {
+ base.with_url(session, session.get_destination_album_url(),
HttpMethod.POST);
+
+ set_custom_payload("qwe", "image/jpeg", 1);
+
+ add_header("Slug", photo.get_name());
+ debug("Uploading %s '%s' -> %s : %s\n", source_file,
photo.get_name(), session.get_destination_album(),
session.get_destination_album_url());
+
+ LibraryPhoto lphoto =
LibraryPhoto.global.fetch(photo.get_photo_id());
+
+ Gee.List<Tag>? photo_tags = Tag.global.fetch_for_photo(lphoto);
+ string tags = "";
+ if (photo_tags != null) {
+ foreach (Tag tag in photo_tags) {
+ tags += _("%s;").printf(tag.get_name());
+ }
+
+ add_header("Tags", tags);
+ }
+ debug("photo: '%s', tags: '%s'", photo.get_name(), tags);
+
+ Soup.Multipart message_parts = new
Soup.Multipart("multipart/form-data");
+ message_parts.append_form_string("tag", tags);
+ message_parts.append_form_string("title", photo.get_name());
+ message_parts.append_form_string("xxx",
session.options.xxx.to_string());
+ message_parts.append_form_string("hide_original",
session.options.hide_original.to_string());
+ message_parts.append_form_string("disable_comments",
session.options.disable_comments.to_string());
+ message_parts.append_form_string("access",
session.options.access_type.down());
+
+ string photo_data;
+ size_t data_length;
+
+ try {
+ FileUtils.get_contents(source_file, out photo_data, out
data_length);
+ } catch (FileError e) {
+ error("YandexUploadTransaction: couldn't read data from file
'%s'", source_file);
+ }
+
+ int image_part_num = message_parts.get_length();
+
+ Soup.Buffer bindable_data = new Soup.Buffer(Soup.MemoryUse.COPY,
photo_data, data_length);
+ message_parts.append_form_file("", source_file, "image/jpeg",
bindable_data);
+
+ unowned Soup.MessageHeaders image_part_header;
+ unowned Soup.Buffer image_part_body;
+ message_parts.get_part(image_part_num, out image_part_header, out
image_part_body);
+
+ GLib.HashTable<string, string> result = new GLib.HashTable<string,
string>(GLib.str_hash, GLib.str_equal);
+ result.insert("name", "data");
+ result.insert("filename", photo.get_name());
+
+ image_part_header.set_content_disposition("form-data", result);
+
+ Soup.Message outbound_message =
Soup.form_request_new_from_multipart(get_endpoint_url(), message_parts);
+ outbound_message.request_headers.append("Authorization", _("OAuth
%s").printf(session.get_access_token()));
+ set_message(outbound_message);
+ }
+ }
+
+ private class WebAuthenticationPane: PublishingDialogPane {
+ private string token_str_http = _("http://%s/token").printf(auth_host);
+ private string token_str_https =
_("https://%s/token").printf(auth_host);
+
+ private WebKit.WebView webview = null;
+ private Gtk.ScrolledWindow webview_frame = null;
+ private Gtk.Layout white_pane = null;
+ private string login_url;
+
+ private int started_token_recv = 0;
+
+ public signal void token_check_required(string access_token, string
refresh_token);
+
+ public WebAuthenticationPane(string login_url) {
+ this.login_url = login_url;
+
+ Gdk.Color white_color;
+ Gdk.Color.parse("white", out white_color);
+ Gtk.Adjustment layout_pane_adjustment = new Gtk.Adjustment(0.5,
0.0, 1.0, 0.01, 0.1, 0.1);
+ white_pane = new Gtk.Layout(layout_pane_adjustment,
layout_pane_adjustment);
+ white_pane.modify_bg(Gtk.StateType.NORMAL, white_color);
+ add(white_pane);
+
+ webview_frame = new Gtk.ScrolledWindow(null, null);
+ webview_frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN);
+ webview_frame.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC);
+
+ webview = new WebKit.WebView();
+ webview.load_finished.connect(on_load_finished);
+ webview.load_started.connect(on_load_started);
+ webview.navigation_requested.connect(navigation_requested);
+
webview.mime_type_policy_decision_requested.connect(mime_type_policy_decision_requested);
+
+ webview_frame.add(webview);
+ white_pane.add(webview_frame);
+ webview.set_size_request(853, 587);
+ }
+
+ private bool mime_type_policy_decision_requested (WebKit.WebFrame p0,
WebKit.NetworkRequest p1, string p2, WebKit.WebPolicyDecision p3) {
+ if (started_token_recv == 1) {
+ if (p2 != "application/json") {
+ debug("Trying to get yandex token: unsupported mime type
'%s'.\n", p2);
+ stderr.printf("Trying to get yandex token: unsupported
mime type '%s'.\n", p2);
+
+ started_token_recv = 2;
+ }
+ }
+ return true;
+ }
+
+ private WebKit.NavigationResponse navigation_requested
(WebKit.WebFrame frame, WebKit.NetworkRequest req) {
+ debug("Navigating to '%s', token: '%s'\n", req.uri,
token_str_https);
+ if (req.uri == token_str_https || req.uri == token_str_http)
+ started_token_recv = 1;
+ return WebKit.NavigationResponse.ACCEPT;
+ }
+
+ private void on_load_finished(WebKit.WebFrame frame) {
+ if (started_token_recv != 1) {
+ show_page();
+ return;
+ }
+
+ window.set_cursor(new Gdk.Cursor(Gdk.CursorType.LEFT_PTR));
+
+ WebKit.WebDataSource data = frame.get_data_source();
+ string s = data.get_data().str;
+ Json.Parser p = new Json.Parser();
+
+ try {
+ p.load_from_data(s, -1);
+ var root = p.get_root().get_object();
+
+ debug("data: %s\n", s);
+ debug("%s %s\n", root.get_string_member("access_token"),
root.get_string_member("refresh_token"));
+
+ token_check_required(root.get_string_member("access_token"),
root.get_string_member("refresh_token"));
+ } catch (Error e) {
+ stderr.printf("Invalid yandex token: %s.\n", s);
+ }
+ }
+
+ private void on_load_started(WebKit.WebFrame frame) {
+ webview_frame.hide();
+ window.set_cursor(new Gdk.Cursor(Gdk.CursorType.WATCH));
+ }
+
+ public void show_page() {
+ webview_frame.show();
+ window.set_cursor(new Gdk.Cursor(Gdk.CursorType.LEFT_PTR));
+ }
+
+ public override void installed() {
+ webview.open(login_url);
+ }
+ }
+
+ private class PublishingOptionsPane: PublishingDialogPane {
+ private Gtk.Builder builder;
+ private Gtk.Button logout_button;
+ private Gtk.Button publish_button;
+ private Gtk.ComboBoxEntry album_list;
+ private yandex_session session;
+
+ public signal void publish();
+ public signal void logout();
+
+ public PublishingOptionsPane(yandex_session session) {
+ this.session = session;
+
+ try {
+ builder = new Gtk.Builder();
+
builder.add_from_file(Resources.get_ui("yandex_publish_model.glade").get_path());
+ builder.connect_signals(null);
+ var align = builder.get_object("alignment") as Gtk.Alignment;
+
+ album_list = builder.get_object ("album_list") as
Gtk.ComboBoxEntry;
+ foreach (var entry in session.album_list) {
+ album_list.append_text(entry.key);
+ }
+ album_list.set_active(0);
+
+ publish_button = builder.get_object("publish_button") as
Gtk.Button;
+ logout_button = builder.get_object("logout_button") as
Gtk.Button;
+
+ publish_button.clicked.connect(on_publish_clicked);
+ logout_button.clicked.connect(on_logout_clicked);
+
+ align.reparent(this);
+ } catch (Error e) {
+ stderr.printf ("Could not load UI: %s\n", e.message);
+ }
+ }
+
+ private void on_logout_clicked() {
+ logout();
+ }
+
+ private void on_publish_clicked() {
+ session.set_destination_album(album_list.get_active_text());
+
+ var tmp = builder.get_object("xxx_check") as Gtk.CheckButton;
+ session.options.xxx = tmp.active;
+
+ tmp = builder.get_object("hide_original_check") as Gtk.CheckButton;
+ session.options.hide_original = tmp.active;
+
+ tmp = builder.get_object("disable_comments_check") as
Gtk.CheckButton;
+ session.options.disable_comments = tmp.active;
+
+ var access_type = builder.get_object("access_type_list") as
Gtk.ComboBoxEntry;
+ session.options.access_type = access_type.get_active_text();
+
+ publish();
+ }
+ }
+
+}
+
+#endif
Index: ui/yandex_publish_model.glade
===================================================================
--- ui/yandex_publish_model.glade (revision 0)
+++ ui/yandex_publish_model.glade (revision 0)
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="publish_options_window">
+ <child>
+ <object class="GtkAlignment" id="alignment">
+ <property name="visible">True</property>
+ <property name="xscale">0.10000000149011612</property>
+ <property name="yscale">0.10000000149011612</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Albums (or write
new)</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Access
type</property>
+ </object>
+ <packing>
+ <property name="x_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxEntry" id="access_type_list">
+ <property name="visible">True</property>
+ <property name="model">liststore1</property>
+ <property name="active">0</property>
+ <property name="text_column">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_padding">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxEntry" id="album_list">
+ <property name="visible">True</property>
+ <property name="model">liststore2</property>
+ <property name="active">0</property>
+ <property name="text_column">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_padding">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="disable_comments_check">
+ <property name="label" translatable="yes">Disable
comments</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="xxx_check">
+ <property name="label" translatable="yes">Mark as
XXX</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="hide_original_check">
+ <property name="label" translatable="yes">Forbid getting
photo's original</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <property name="layout_style">spread</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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name text -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Public</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Friends</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Private</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore2">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+</interface>
Index: Makefile
===================================================================
--- Makefile (revision 2214)
+++ Makefile (working copy)
@@ -97,6 +97,7 @@
SlideshowPage.vala \
LibraryFiles.vala \
FlickrConnector.vala \
+ YandexConnector.vala \
Printing.vala \
Tag.vala \
TagPage.vala \
@@ -151,6 +152,7 @@
tags.ui \
trash.ui \
offline.ui \
+ yandex_publish_model.glade \
shotwell.glade
SYS_INTEGRATION_FILES = \
@@ -271,7 +273,8 @@
glib-2.0 \
libexif \
sqlite3 \
- gexiv2
+ gexiv2 \
+ json-glib-1.0
LIBRAW_PKG = \
libraw
@@ -312,6 +315,7 @@
webkit-1.0 >= 1.1.5 \
gudev-1.0 >= 145 \
dbus-glib-1 >= 0.80
+ libjson-glib-1.0 >= 0.7
endif
PKGS = $(EXT_PKGS) $(LOCAL_PKGS) $(LIBRAW_PKG)
_______________________________________________
Shotwell mailing list
[email protected]
http://lists.yorba.org/cgi-bin/mailman/listinfo/shotwell