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

Reply via email to