Hi.

Attached patch adds support for Yandex.Fotki photo hosting site, which
is the biggest photo hosting in Russia and east Europe, it has about 2
millions of users and I believe quite a few of them use Linux.
There are more than 500 Tb of photos already.

Anyway, having Yandex.Fotki support in Shotwell will be a good stimulus
to start using Linux :)

Or maybe not, who knows.
Patch is very non-intrusive for Shotwell core, it just adds another
class construction into web publishing process, generic set/load/clear
gconf helpers and json-glib-1.0 dep in makefile (this lib is already
installed in glib package require by Shotwell).

What we can:
 * upload photos into existing album
 * create album if needed
 * set access permissions as well as XXX/hide original/forbid comments
   metadata flags
 * upload photos with local tags
 * set local tag which prevents upload (to eliminate copies, one can
   remove tag and upload will succeed of course)
 * use OAuth 2.0 for authorization (using embedded webkit toolkit to
   request tokens if they are missed as well as allowing to register new
   user just from Shotwell)
 * cache username/tokens in gconf

There is a small caveat thogh: Yandex did not yet release OAuth for
photos, so I use 'undocumented' server names and client ids. One can
edit special gconf keys to add proper strings which will change server
names and client id. When OAuth for photo hosting will be released
for public I will send updated patch to change default names of course.

This is just for the case that you will suddenly make a new release,
which will miss Yandex.Fotki functionality :)

Please review, comment and apply.
Thank you.

Index: src/Config.vala
===================================================================
--- src/Config.vala     (revision 2145)
+++ 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 2145)
+++ src/WebConnectors.vala      (working copy)
@@ -1094,6 +1094,7 @@
         result += "Facebook";
         result += "Flickr";
         result += "Picasa Web Albums";
+        result += "Yandex.Fotki";
 
         return result;
     }
@@ -1105,6 +1106,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,871 @@
+/* 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";
+
+       public class YandexLoginWelcomePane : PublishingDialogPane {
+               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);
+               }
+
+               public YandexLoginWelcomePane(string service_welcome_message) {
+                       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);
+
+                       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 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 TransformablePhoto []photos;
+
+               public override string get_name() { return "Yandex"; }
+               public override void cancel_interaction() { 
session.stop_transactions(); }
+
+               public Interactor(PublishingDialog host)
+               {
+                       base(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();
+                       //post_error(err);
+               }
+
+               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);
+
+                       // TODO: add a descriptive, translatable error message 
string here
+                       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);
+                       //yandex_request_web_auth();
+               }
+
+               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();
+                       //post_error(err);
+               }
+
+               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 (TransformablePhoto 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;
+                       }
+
+                       TransformablePhoto []photos_tmp = new 
TransformablePhoto[total - tagged];
+                       var pos = 0;
+                       foreach (TransformablePhoto 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(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();
+                       //post_error(err);
+               }
+
+               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, TransformablePhoto 
[]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, TransformablePhoto 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 2145)
+++ Makefile    (working copy)
@@ -97,6 +97,7 @@
        SlideshowPage.vala \
        LibraryFiles.vala \
        FlickrConnector.vala \
+       YandexConnector.vala \
        Printing.vala \
        Tag.vala \
        TagPage.vala \
@@ -150,6 +151,7 @@
        tags.ui \
        trash.ui \
        offline.ui \
+       yandex_publish_model.glade \
        shotwell.glade
 
 SYS_INTEGRATION_FILES = \
@@ -268,7 +270,8 @@
        glib-2.0 \
        libexif \
        sqlite3 \
-       gexiv2
+       gexiv2 \
+       json-glib-1.0
 
 LIBRAW_PKG = \
        libraw


-- 
        Evgeniy Polyakov
_______________________________________________
Shotwell mailing list
[email protected]
http://lists.yorba.org/cgi-bin/mailman/listinfo/shotwell

Reply via email to