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 <z...@ioremap.net> + * + * 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 Shotwell@lists.yorba.org http://lists.yorba.org/cgi-bin/mailman/listinfo/shotwell