~ubuntu-branches/ubuntu/saucy/shotwell/saucy-proposed

« back to all changes in this revision

Viewing changes to plugins/shotwell-publishing/PicasaPublishing.vala

  • Committer: Package Import Robot
  • Author(s): Sebastien Bacher, Robert Ancell, Alberto Mardegan
  • Date: 2012-08-28 16:57:19 UTC
  • mfrom: (1.2.14)
  • Revision ID: package-import@ubuntu.com-20120828165719-p6ep5hdo24rssreo
Tags: 0.12.90-0ubuntu1
[ Robert Ancell ]
* New upstream release (LP: #1041011)
* debian/watch:
  - Watch for unstable versions
  - Releases now in .xz format

[ Alberto Mardegan ]
* debian/patches/06_uoa.patch:
  - updated for the new version

Show diffs side-by-side

added added

removed removed

Lines of Context:
62
62
namespace Publishing.Picasa {
63
63
 
64
64
internal const string SERVICE_WELCOME_MESSAGE = 
65
 
    _("You are not currently logged into Picasa Web Albums.\n\nYou must have already signed up for a Google account and set it up for use with Picasa to continue. You can set up most accounts by using your browser to log into the Picasa Web Albums site at least once.");
 
65
    _("You are not currently logged into Picasa Web Albums.\n\nClick Login to log into Picasa Web Albums in your Web browser. You will have to authorize Shotwell Connect to link to your Picasa Web Albums account.");
66
66
internal const string DEFAULT_ALBUM_NAME = _("Shotwell Connect");
 
67
internal const string OAUTH_CLIENT_ID = "1073902228337.apps.googleusercontent.com";
 
68
internal const string OAUTH_CLIENT_SECRET = "tcZxcw_HeyUbq4IIuOF1uq8u";
67
69
 
68
70
public class PicasaPublisher : Spit.Publishing.Publisher, GLib.Object {
69
71
    private weak Spit.Publishing.PluginHost host = null;
70
72
    private Spit.Publishing.ProgressCallback progress_reporter = null;
71
73
    private weak Spit.Publishing.Service service = null;
72
74
    private bool running = false;
 
75
    private bool strip_metadata = false;
73
76
    private Session session;
74
 
    private string? username = null;
 
77
    private string username = "[unknown]";
75
78
    private Album[] albums = null;
76
79
    private PublishingParameters parameters = null;
77
 
    private Spit.Publishing.Publisher.MediaType media_type = Spit.Publishing.Publisher.MediaType.NONE;
 
80
    private Spit.Publishing.Publisher.MediaType media_type =
 
81
        Spit.Publishing.Publisher.MediaType.NONE;
78
82
    private Signon.AuthSession auth_session = null;
79
83
 
80
84
    public PicasaPublisher(Spit.Publishing.Service service,
83
87
        this.host = host;
84
88
        this.session = new Session();
85
89
        
86
 
        // Ticket #3212 - Only display the size chooser if we're uploading a
87
 
        // photograph, since resizing of video isn't supported.
88
 
        //
89
 
        // Find the media types involved. We need this to decide whether
90
 
        // to show the size combobox or not.
91
 
        foreach(Spit.Publishing.Publishable p in host.get_publishables()) {
 
90
        foreach(Spit.Publishing.Publishable p in host.get_publishables())
92
91
            media_type |= p.get_media_type();
93
 
        }         
94
92
    }
95
93
    
96
94
    private Album[] extract_albums(Xml.Node* document_root) throws Spit.Publishing.PublishingError {
131
129
        return result;
132
130
    }
133
131
    
134
 
    private string? extract_username(Xml.Node* document_root) throws Spit.Publishing.PublishingError {
135
 
        Xml.Node* doc_node_iter = null;
136
 
        if (document_root->name == "feed")
137
 
            doc_node_iter = document_root->children;
138
 
        else
139
 
            throw new Spit.Publishing.PublishingError.MALFORMED_RESPONSE("response root node " +
140
 
                "isn't a <feed>");
141
 
 
142
 
        for ( ; doc_node_iter != null; doc_node_iter = doc_node_iter->next) {
143
 
            if (doc_node_iter->name != "author")
144
 
                continue;
145
 
 
146
 
            Xml.Node* author_node_iter = doc_node_iter->children;
147
 
            for ( ; author_node_iter != null; author_node_iter = author_node_iter->next) {
148
 
                if (author_node_iter->name == "name")
149
 
                    return author_node_iter->get_content();
150
 
            }
151
 
 
152
 
        }
153
 
 
154
 
        return null;
155
 
    }
156
 
 
157
 
    internal string? get_persistent_username() {
158
 
        return host.get_config_string("user_name", null);
159
 
    }
160
 
    
161
 
    internal string? get_persistent_auth_token() {
162
 
        return host.get_config_string("auth_token", null);
163
 
    }
164
 
    
165
 
    internal string? get_persistent_auth_method() {
166
 
        return host.get_config_string("auth_method", null);
167
 
    }
168
 
 
169
 
    internal void set_persistent_username(string username) {
170
 
        host.set_config_string("user_name", username);
171
 
    }
172
 
    
173
 
    internal void set_persistent_auth_token(string auth_token) {
174
 
        host.set_config_string("auth_token", auth_token);
175
 
    }
176
 
 
177
 
    internal void set_persistent_auth_method(string auth_method) {
178
 
        host.set_config_string("auth_method", auth_method);
179
 
    }
180
 
 
181
 
    internal void invalidate_persistent_session() {
182
 
        debug("invalidating persisted Picasa Web Albums session.");
183
 
 
184
 
        set_persistent_username("");
185
 
        set_persistent_auth_token("");
186
 
        set_persistent_auth_method("");
187
 
    }
188
 
    
189
 
    internal bool is_persistent_session_available() {
190
 
        return (get_persistent_username() != null && get_persistent_auth_token() != null);
191
 
    }
 
132
    internal bool get_persistent_strip_metadata() {
 
133
        return host.get_config_bool("strip_metadata", false);
 
134
    }
 
135
    
 
136
    internal void set_persistent_strip_metadata(bool strip_metadata) {
 
137
        host.set_config_bool("strip_metadata", strip_metadata);
 
138
    }    
192
139
 
193
140
    public bool is_running() {
194
141
        return running;
206
153
 
207
154
        do_hosted_web_authentication();
208
155
    }
209
 
 
 
156
    
210
157
    public void on_processed(Signon.AuthSession self, owned HashTable<string,Value?>? session_data, GLib.Error error){
211
158
        if (error != null) {
212
159
            debug("got error: %s", error.message);
222
169
        if (session_data.lookup("AccessToken") != null) {
223
170
            string token = session_data.lookup("AccessToken").get_string();
224
171
            debug("OAuth Access Token: %s", token);
225
 
            session.authenticate(token, null, "OAuth2");
 
172
            session.authenticate(token, "OAuth2");
226
173
        } else if (session_data.lookup("AuthToken") != null) {
227
174
            string token = session_data.lookup("AuthToken").get_string();
228
175
            debug("ClientLogin Access Token: %s", token);
229
 
            session.authenticate(token, null, "ClientLogin");
 
176
            session.authenticate(token, "ClientLogin");
230
177
        } else {
231
178
            debug("Access token not present!");
232
179
        }
248
195
                data = account.get_session_parameters(out mechanism);
249
196
                debug("Got account data");
250
197
            } catch (GLib.Error e) {
251
 
                debug("EVENT: couldn't create session for account: %s",
252
 
                      e.message);
253
 
            }
254
 
        }
255
 
 
256
 
        if (auth_session == null) {
257
 
            debug("Creating unbound session");
258
 
            try {
259
 
                auth_session = new Signon.AuthSession(0, "oauth2");
260
 
            } catch (GLib.Error e) {
261
 
                debug("EVENT: AuthSession creation failed: %s", e.message);
262
 
                return;
 
198
                warning("EVENT: couldn't create session for account: %s",
 
199
                        e.message);
263
200
            }
264
201
        }
265
202
 
266
203
        if (data == null) {
267
 
            debug("Using own params");
268
 
            data = new HashTable<string,Value?>(str_hash, str_equal);
269
 
            data.insert("Host", "accounts.google.com");
270
 
            data.insert("AuthPath", "o/oauth2/auth");
271
 
            data.insert("ClientId", "1041829795610-htf69c529db58qcq8jvf58bijn1ie3oi.apps.googleusercontent.com");
272
 
            data.insert("RedirectUri", "http://www.mardy.it/oauth2callback");
273
 
            string[] scopes = {
274
 
                "https://picasaweb.google.com/data/"
275
 
            };
276
 
            data.insert("Scope", scopes);
277
 
            string[] response_type = {
278
 
                "token"
279
 
            };
280
 
            data.insert("ResponseType", response_type);
 
204
            warning ("No account authentication data");
 
205
            host.post_error(new Spit.Publishing.PublishingError.SERVICE_ERROR(
 
206
                "Error while accessing the account"));
 
207
            return;
281
208
        }
282
209
 
283
210
        var windowId = host.get_dialog_xid();
284
211
        if (windowId != 0) {
285
 
            data.insert("WindowId", windowId);
 
212
            data.insert("WindowId", (uint)windowId);
286
213
        }
287
214
 
288
215
        auth_session.process(data, mechanism, on_processed);
297
224
 
298
225
        debug("EVENT: an authenticated session has become available.");
299
226
        
300
 
        do_save_auth_info();
 
227
        do_fetch_username();
 
228
    }
 
229
    
 
230
    private void on_fetch_username_transaction_completed(Publishing.RESTSupport.Transaction txn) {
 
231
        txn.completed.disconnect(on_fetch_username_transaction_completed);
 
232
        txn.network_error.disconnect(on_fetch_username_transaction_error);
 
233
        
 
234
        debug("EVENT: username fetch transaction completed successfully.");
 
235
 
 
236
        do_extract_username(txn.get_response());
301
237
        do_fetch_account_information();
302
238
    }
 
239
    
 
240
    private void on_fetch_username_transaction_error(Publishing.RESTSupport.Transaction txn,
 
241
        Spit.Publishing.PublishingError err) {
 
242
        txn.completed.disconnect(on_fetch_username_transaction_completed);
 
243
        txn.network_error.disconnect(on_fetch_username_transaction_error);
 
244
 
 
245
        debug("EVENT: username fetch transaction caused a network error");
 
246
    }
303
247
 
304
248
    private void on_initial_album_fetch_complete(Publishing.RESTSupport.Transaction txn) {
305
249
        txn.completed.disconnect(on_initial_album_fetch_complete);
334
278
        debug("EVENT: user clicked 'Logout' in the publishing options pane.");
335
279
 
336
280
        session.deauthenticate();
337
 
        invalidate_persistent_session();
338
281
 
339
282
        do_show_service_welcome_pane();
340
283
    }
341
284
 
342
 
    private void on_publishing_options_publish(PublishingParameters parameters) {
 
285
    private void on_publishing_options_publish(PublishingParameters parameters, 
 
286
        bool strip_metadata) {
343
287
        if (!is_running())
344
288
            return;
 
289
            
 
290
        this.strip_metadata = strip_metadata;
345
291
                
346
292
        debug("EVENT: user clicked 'Publish' in the publishing options pane.");
347
293
 
350
296
        if (parameters.is_to_new_album()) {
351
297
            do_create_album(parameters);
352
298
        } else {
353
 
            do_upload();
 
299
            do_upload(this.strip_metadata);
354
300
        }
355
301
    }
356
302
 
389
335
        }
390
336
        parameters.convert(response_albums[0].url);
391
337
 
392
 
        do_upload();
 
338
        do_upload(this.strip_metadata);
393
339
    }
394
340
 
395
341
    private void on_album_creation_error(Publishing.RESTSupport.Transaction bad_txn,
449
395
        host.install_welcome_pane(SERVICE_WELCOME_MESSAGE, on_service_welcome_login);
450
396
    }
451
397
    
452
 
    private void do_save_auth_info() {
453
 
        debug("ACTION: saving authentication information to configuration system.");
454
 
        
455
 
        assert(session.is_authenticated());
456
 
        
457
 
        set_persistent_auth_token(session.get_auth_token());
458
 
        set_persistent_username(session.get_username());
459
 
        set_persistent_auth_method(session.get_auth_method());
 
398
    private void do_extract_username(string response_body) {
 
399
        debug("ACTION: extracting username from body of server response");
 
400
        
 
401
        Json.Parser parser = new Json.Parser();
 
402
        
 
403
        try {
 
404
            parser.load_from_data(response_body);
 
405
        } catch (Error err) {
 
406
            host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
 
407
                "Couldn't parse JSON response: " + err.message));
 
408
            return;
 
409
        }
 
410
        
 
411
        Json.Object response_obj = parser.get_root().get_object();
 
412
 
 
413
        if (response_obj.has_member("name")) {
 
414
            string username = response_obj.get_string_member("name");
 
415
 
 
416
            if (username != "")
 
417
                this.username = username;
 
418
        }
 
419
    }
 
420
    
 
421
    private void do_fetch_username() {
 
422
        debug("ACTION: running network transaction to fetch username.");
 
423
 
 
424
        host.install_login_wait_pane();
 
425
        host.set_service_locked(true);
 
426
        
 
427
        UsernameFetchTransaction txn = new UsernameFetchTransaction(session);
 
428
        txn.completed.connect(on_fetch_username_transaction_completed);
 
429
        txn.network_error.connect(on_fetch_username_transaction_error);
 
430
        
 
431
        try {
 
432
            txn.execute();
 
433
        } catch (Error err) {
 
434
            host.post_error(err);
 
435
        }
460
436
    }
461
437
 
462
438
    private void do_fetch_account_information() {
473
449
        try {
474
450
            directory_trans.execute();
475
451
        } catch (Spit.Publishing.PublishingError err) {
476
 
            // don't just post the error and stop publishing -- 404 and 403 errors are
477
 
            // recoverable
 
452
            // don't just post the error and stop publishing -- 404 errors are recoverable
478
453
            on_initial_album_fetch_error(directory_trans, err);
479
454
        }
480
455
    }
493
468
 
494
469
        try {
495
470
            albums = extract_albums(response_doc.get_root_node());
496
 
            username = extract_username(response_doc.get_root_node());
497
 
            set_persistent_username(username);
498
471
        } catch (Spit.Publishing.PublishingError err) {
499
472
            host.post_error(err);
500
473
            return;
505
478
 
506
479
    private void do_show_publishing_options_pane() {
507
480
        debug("ACTION: showing publishing options pane.");
508
 
        
509
 
        PublishingOptionsPane opts_pane = new PublishingOptionsPane(host, username, albums, media_type);
 
481
        Gtk.Builder builder = new Gtk.Builder();
 
482
 
 
483
        try {
 
484
            // the trailing get_path() is required, since add_from_file can't cope
 
485
            // with File objects directly and expects a pathname instead.
 
486
            builder.add_from_file(
 
487
                host.get_module_file().get_parent().
 
488
                get_child("picasa_publishing_options_pane.glade").get_path());
 
489
        } catch (Error e) {
 
490
            warning("Could not parse UI file! Error: %s.", e.message);
 
491
            host.post_error(
 
492
                new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
 
493
                    _("A file required for publishing is unavailable. Publishing to Picasa can't continue.")));
 
494
            return;
 
495
        }
 
496
 
 
497
        PublishingOptionsPane opts_pane = new PublishingOptionsPane(host, username, albums, media_type, builder,
 
498
            get_persistent_strip_metadata());
510
499
        opts_pane.publish.connect(on_publishing_options_publish);
511
500
        opts_pane.logout.connect(on_publishing_options_logout);
512
501
        host.install_dialog_pane(opts_pane);
534
523
        }
535
524
    }
536
525
 
537
 
    private void do_upload() {
 
526
    private void do_upload(bool strip_metadata) {
 
527
        set_persistent_strip_metadata(strip_metadata);        
 
528
        
538
529
        debug("ACTION: uploading media items to remote server.");
539
530
 
540
531
        host.set_service_locked(true);
541
532
 
542
 
        progress_reporter = host.serialize_publishables(parameters.get_photo_major_axis_size());
 
533
        progress_reporter = host.serialize_publishables(parameters.get_photo_major_axis_size(), 
 
534
            strip_metadata);
543
535
        
544
536
        // Serialization is a long and potentially cancellable operation, so before we use
545
537
        // the publishables, make sure that the publishing interaction is still running. If it
575
567
        
576
568
        running = true;
577
569
 
578
 
        if (is_persistent_session_available()) {
579
 
            username = get_persistent_username();
580
 
            session.authenticate(get_persistent_auth_token(), get_persistent_username(),
581
 
                                 get_persistent_auth_method());
582
 
            do_fetch_account_information();
583
 
        } else {
584
 
            do_show_service_welcome_pane();
585
 
        }
 
570
        do_show_service_welcome_pane();
586
571
    }
587
572
 
588
573
    public void stop() {
608
593
 
609
594
internal class Session : Publishing.RESTSupport.Session {
610
595
    private string? auth_token = null;
611
 
    private string? username = null;
612
596
    private string? auth_method = null;
613
597
 
614
598
    public Session() {
618
602
        return (auth_token != null);
619
603
    }
620
604
 
621
 
    public void authenticate(string auth_token, string? username, string auth_method) {
 
605
    public void authenticate(string auth_token, string auth_method) {
622
606
        this.auth_token = auth_token;
623
 
        this.username = username;
624
607
        this.auth_method = auth_method;
625
608
        
626
609
        notify_authenticated();
628
611
    
629
612
    public void deauthenticate() {
630
613
        auth_token = null;
631
 
        username = null;
632
614
        auth_method = null;
633
615
    }
634
 
 
635
 
    public string? get_username() {
636
 
        return username;
637
 
    }
638
616
    
639
617
    public string? get_auth_token() {
640
618
        return auth_token;
658
636
 
659
637
        if (session.get_auth_method() == "ClientLogin") {
660
638
            add_header("Authorization",
661
 
                       "GoogleLogin auth=%s".printf(session.get_auth_token()));
 
639
                       "GoogleLogin auth=" + session.get_auth_token());
662
640
        } else {
663
 
            add_argument("access_token", session.get_auth_token());
664
641
            add_header("Authorization",
665
 
                       "Bearer %s".printf(session.get_auth_token()));
 
642
                       "Bearer " + session.get_auth_token());
666
643
        }
667
644
    }
668
645
}
669
646
 
 
647
internal class UsernameFetchTransaction : AuthenticatedTransaction {
 
648
    private const string ENDPOINT_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
 
649
    
 
650
    public UsernameFetchTransaction(Session session) {
 
651
        base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
 
652
    }
 
653
}
 
654
 
670
655
internal class AlbumDirectoryTransaction : AuthenticatedTransaction {
671
656
    private const string ENDPOINT_URL = "https://picasaweb.google.com/data/feed/api/user/" +
672
657
        "default";
701
686
 
702
687
internal class UploadTransaction : AuthenticatedTransaction {
703
688
    private PublishingParameters parameters;
704
 
    private const string METADATA_TEMPLATE = "<entry xmlns='http://www.w3.org/2005/Atom'> <title>%s</title> %s <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/photos/2007#photo'/> </entry>";
 
689
    private const string METADATA_TEMPLATE = "<?xml version=\"1.0\" ?><atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:mrss='http://search.yahoo.com/mrss/'> <atom:title>%s</atom:title> %s <atom:category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/photos/2007#photo'/> %s </atom:entry>";
705
690
    private Session session;
706
691
    private string mime_type;
707
692
    private Spit.Publishing.Publishable publishable;
721
706
    public override void execute() throws Spit.Publishing.PublishingError {
722
707
        // create the multipart request container
723
708
        Soup.Multipart message_parts = new Soup.Multipart("multipart/related");
724
 
        
 
709
 
725
710
        string summary = "";
726
711
        if (publishable.get_publishing_name() != "") {
727
 
            summary = "<summary>%s</summary>".printf(
 
712
            summary = "<atom:summary>%s</atom:summary>".printf(
728
713
                Publishing.RESTSupport.decimal_entity_encode(publishable.get_publishing_name()));
729
714
        }
730
715
 
 
716
        string[] keywords = publishable.get_publishing_keywords();
 
717
        string keywords_string = "";
 
718
        if (keywords.length > 0) {
 
719
            keywords_string = "<mrss:group><mrss:keywords>%s</mrss:keywords></mrss:group>".printf(string.joinv(", ", keywords));
 
720
        }
 
721
 
731
722
        string metadata = METADATA_TEMPLATE.printf(Publishing.RESTSupport.decimal_entity_encode(
732
723
            publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME)),
733
 
            summary);
 
724
            summary, keywords_string);
734
725
        Soup.Buffer metadata_buffer = new Soup.Buffer(Soup.MemoryUse.COPY, metadata.data);
735
726
        message_parts.append_form_file("", "", "application/atom+xml", metadata_buffer);
736
727
 
761
752
        set_message(outbound_message);
762
753
        if (session.get_auth_method() == "ClientLogin") {
763
754
            add_header("Authorization",
764
 
                       "GoogleLogin auth=%s".printf(session.get_auth_token()));
 
755
                       "GoogleLogin auth=" + session.get_auth_token());
765
756
        } else {
766
757
            add_header("Authorization",
767
 
                       "Bearer %s".printf(session.get_auth_token()));
 
758
                       "Bearer " + session.get_auth_token());
768
759
        }
769
760
 
770
761
        // send the message and get its response
774
765
}
775
766
 
776
767
internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
777
 
    private LegacyPublishingOptionsPane wrapped = null;
778
 
 
779
 
    public signal void publish(PublishingParameters parameters);
780
 
    public signal void logout();
781
 
 
782
 
    public PublishingOptionsPane(Spit.Publishing.PluginHost host, string username, Album[] albums, Spit.Publishing.Publisher.MediaType media_type) {
783
 
        wrapped = new LegacyPublishingOptionsPane(host, username, albums, media_type);
784
 
    }
785
 
    
786
 
    protected void notify_publish(PublishingParameters parameters) {
787
 
        publish(parameters);
788
 
    }
789
 
    
790
 
    protected void notify_logout() {
791
 
        logout();
792
 
    }
793
 
 
794
 
    public Gtk.Widget get_widget() {
795
 
        return wrapped;
796
 
    }
797
 
    
798
 
    public Spit.Publishing.DialogPane.GeometryOptions get_preferred_geometry() {
799
 
        return Spit.Publishing.DialogPane.GeometryOptions.NONE;
800
 
    }
801
 
    
802
 
    public void on_pane_installed() {        
803
 
        wrapped.publish.connect(notify_publish);
804
 
        wrapped.logout.connect(notify_logout);
805
 
        
806
 
        wrapped.installed();
807
 
    }
808
 
    
809
 
    public void on_pane_uninstalled() {
810
 
        wrapped.publish.disconnect(notify_publish);
811
 
        wrapped.logout.disconnect(notify_logout);
812
 
    }
813
 
}
814
 
 
815
 
internal class LegacyPublishingOptionsPane : Gtk.VBox {
816
768
    private class SizeDescription {
817
769
        public string name;
818
770
        public int major_axis_pixels;
823
775
        }
824
776
    }
825
777
 
826
 
    private const int PACKER_VERTICAL_PADDING = 16;
827
 
    private const int PACKER_HORIZ_PADDING = 128;
828
 
    private const int INTERSTITIAL_VERTICAL_SPACING = 20;
829
 
    private const int ACTION_BUTTON_SPACING = 48;
830
 
    private const int ACTION_BUTTON_WIDTH = 128;
831
778
    private const string DEFAULT_SIZE_CONFIG_KEY = "default_size";
832
779
    private const string LAST_ALBUM_CONFIG_KEY = "last_album";
833
780
    
834
 
    private Gtk.ComboBoxText existing_albums_combo;
835
 
    private Gtk.Entry new_album_entry;
836
 
    private Gtk.CheckButton public_check;
837
 
    private Gtk.ComboBoxText size_combo;
838
 
    private Gtk.RadioButton use_existing_radio;
839
 
    private Gtk.RadioButton create_new_radio;
 
781
    private Gtk.Builder builder = null;
 
782
    
 
783
    private Gtk.Box pane_widget = null;
 
784
    private Gtk.Label login_identity_label = null;
 
785
    private Gtk.Label publish_to_label = null;
 
786
    private Gtk.RadioButton use_existing_radio = null;
 
787
    private Gtk.ComboBoxText existing_albums_combo = null;
 
788
    private Gtk.RadioButton create_new_radio = null;
 
789
    private Gtk.Entry new_album_entry = null;
 
790
    private Gtk.CheckButton public_check = null;
 
791
    private Gtk.ComboBoxText size_combo = null;
 
792
    private Gtk.CheckButton strip_metadata_check = null;
 
793
    private Gtk.Button publish_button = null;
 
794
    private Gtk.Button logout_button = null;
 
795
    
840
796
    private Album[] albums;
841
797
    private SizeDescription[] size_descriptions;
842
 
    private Gtk.Button publish_button;
843
798
    private string username;
844
799
    private weak Spit.Publishing.PluginHost host;
845
800
 
846
 
    public signal void publish(PublishingParameters parameters);
 
801
    public signal void publish(PublishingParameters parameters, bool strip_metadata);
847
802
    public signal void logout();
848
803
 
849
 
    public LegacyPublishingOptionsPane(Spit.Publishing.PluginHost host, string username, 
850
 
        Album[] albums, Spit.Publishing.Publisher.MediaType media_type) {
 
804
    public PublishingOptionsPane(Spit.Publishing.PluginHost host, string username, 
 
805
        Album[] albums, Spit.Publishing.Publisher.MediaType media_type, Gtk.Builder builder,
 
806
        bool strip_metadata) {
851
807
        this.username = username;
852
808
        this.albums = albums;
853
809
        this.host = host;
854
810
        size_descriptions = create_size_descriptions();
855
811
 
856
 
        add(gtk_vspacer(8));
857
 
 
858
 
        Gtk.Label login_identity_label =
859
 
            new Gtk.Label(_("You are logged into Picasa Web Albums as %s.").printf(
860
 
            username));
861
 
 
862
 
        add(login_identity_label);
863
 
 
864
 
        Gtk.Box vert_packer = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
865
 
 
866
 
        vert_packer.add(gtk_vspacer(INTERSTITIAL_VERTICAL_SPACING));
867
 
 
868
 
        Gtk.Table main_table = new Gtk.Table(6, 3, false);
869
 
 
870
 
        // Ticket #3212, part II - If we're onluy uploading video, alter the         
871
 
        // 'will appear in' message to reflect this.          
872
 
        Gtk.Label publish_to_label;
873
 
 
874
 
        if((media_type & Spit.Publishing.Publisher.MediaType.PHOTO) == 0) 
875
 
            publish_to_label = new Gtk.Label(_("Videos will appear in:"));
876
 
        else
877
 
            publish_to_label = new Gtk.Label(_("Photos will appear in:"));
878
 
            
879
 
        publish_to_label.set_alignment(0.0f, 0.5f);
880
 
        main_table.attach(publish_to_label, 0, 2, 0, 1,
881
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
882
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
883
 
 
884
 
        main_table.attach(gtk_hspacer(2), 0, 1, 1, 2,
885
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
886
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
887
 
 
888
 
        use_existing_radio = new Gtk.RadioButton.with_mnemonic(null, _("An _existing album:"));
 
812
        this.builder = builder;
 
813
        assert(builder != null);
 
814
        assert(builder.get_objects().length() > 0);
 
815
 
 
816
        // pull in all widgets from builder.
 
817
        pane_widget = (Gtk.Box) builder.get_object("picasa_pane_widget");
 
818
        login_identity_label = (Gtk.Label) builder.get_object("login_identity_label");
 
819
        publish_to_label = (Gtk.Label) builder.get_object("publish_to_label");
 
820
        use_existing_radio = (Gtk.RadioButton) builder.get_object("use_existing_radio");
 
821
        existing_albums_combo = (Gtk.ComboBoxText) builder.get_object("existing_albums_combo");
 
822
        create_new_radio = (Gtk.RadioButton) builder.get_object("create_new_radio");
 
823
        new_album_entry = (Gtk.Entry) builder.get_object("new_album_entry");
 
824
        public_check = (Gtk.CheckButton) builder.get_object("public_check");
 
825
        size_combo = (Gtk.ComboBoxText) builder.get_object("size_combo");
 
826
        strip_metadata_check = (Gtk.CheckButton) this.builder.get_object("strip_metadata_check");
 
827
        publish_button = (Gtk.Button) builder.get_object("publish_button");
 
828
        logout_button = (Gtk.Button) builder.get_object("logout_button");
 
829
 
 
830
        // populate any widgets whose contents are programmatically-generated.
 
831
        login_identity_label.set_label(_("You are logged into Picasa Web Albums as %s.").printf(username));
 
832
        strip_metadata_check.set_active(strip_metadata);
 
833
 
 
834
 
 
835
        if((media_type & Spit.Publishing.Publisher.MediaType.PHOTO) == 0) {
 
836
            publish_to_label.set_label(_("Videos will appear in:"));
 
837
            size_combo.set_visible(false);
 
838
            size_combo.set_sensitive(false);
 
839
        }
 
840
        else {
 
841
            publish_to_label.set_label(_("Photos will appear in:"));
 
842
            foreach(SizeDescription desc in size_descriptions) {
 
843
                size_combo.append_text(desc.name);
 
844
            }
 
845
            size_combo.set_visible(true);
 
846
            size_combo.set_sensitive(true);
 
847
            size_combo.set_active(host.get_config_int(DEFAULT_SIZE_CONFIG_KEY, 0));
 
848
        }
 
849
 
 
850
        // connect all signals.
889
851
        use_existing_radio.clicked.connect(on_use_existing_radio_clicked);
890
 
        main_table.attach(use_existing_radio, 1, 2, 1, 2,
891
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
892
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
893
 
 
894
 
        existing_albums_combo = new Gtk.ComboBoxText();
895
 
        Gtk.Alignment existing_albums_combo_frame = new Gtk.Alignment(0.0f, 0.5f, 0.0f, 0.0f);
896
 
        existing_albums_combo_frame.add(existing_albums_combo);
897
 
        main_table.attach(existing_albums_combo_frame, 2, 3, 1, 2,
898
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
899
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
900
 
 
901
 
        create_new_radio = new Gtk.RadioButton.with_mnemonic(use_existing_radio.get_group(),
902
 
            _("A _new album named:"));
903
852
        create_new_radio.clicked.connect(on_create_new_radio_clicked);
904
 
        main_table.attach(create_new_radio, 1, 2, 2, 3,
905
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
906
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
907
 
 
908
 
        new_album_entry = new Gtk.Entry();
909
853
        new_album_entry.changed.connect(on_new_album_entry_changed);
910
 
        main_table.attach(new_album_entry, 2, 3, 2, 3,
911
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
912
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
913
 
 
914
 
        public_check = new Gtk.CheckButton.with_mnemonic(_("L_ist album in public gallery"));
915
 
        main_table.attach(public_check, 2, 3, 3, 4,
916
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
917
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
918
 
 
919
 
        main_table.attach(gtk_vspacer(INTERSTITIAL_VERTICAL_SPACING / 2), 2, 3, 4, 5,
920
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
921
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
922
 
 
923
 
        // Ticket #3212 - Only display the size chooser if we're uploading a
924
 
        // photograph, since resizing of video isn't supported.
925
 
        // 
926
 
        // If the media_type argument doesn't tell us we're getting at least
927
 
        // one photo, do not create or add these widgets.
928
 
        if((media_type & Spit.Publishing.Publisher.MediaType.PHOTO) != 0) {
929
 
            Gtk.Label size_label = new Gtk.Label.with_mnemonic(_("Photo _size preset:"));
930
 
            size_label.set_alignment(0.0f, 0.5f);
931
 
            
932
 
            main_table.attach(size_label, 0, 2, 5, 6,
933
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
934
 
            Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
935
 
 
936
 
            size_combo = new Gtk.ComboBoxText();
937
 
            foreach(SizeDescription desc in size_descriptions)
938
 
                size_combo.append_text(desc.name);
939
 
        
940
 
            size_combo.set_active(host.get_config_int(DEFAULT_SIZE_CONFIG_KEY, 0));
941
 
            Gtk.Alignment size_combo_frame = new Gtk.Alignment(0.0f, 0.5f, 0.0f, 0.0f);
942
 
        
943
 
            size_combo_frame.add(size_combo);
944
 
            main_table.attach(size_combo_frame, 2, 3, 5, 6,
945
 
                Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
946
 
                Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 4, 4);
947
 
 
948
 
            size_label.set_mnemonic_widget(size_combo);
949
 
        }
950
 
 
951
 
        vert_packer.add(main_table);
952
 
 
953
 
        vert_packer.add(gtk_vspacer(INTERSTITIAL_VERTICAL_SPACING));
954
 
 
955
 
        Gtk.Box action_button_layouter = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
956
 
        action_button_layouter.homogeneous = true;
957
 
 
958
 
        Gtk.Button logout_button = new Gtk.Button.with_mnemonic(_("_Logout"));
959
854
        logout_button.clicked.connect(on_logout_clicked);
960
 
        logout_button.set_size_request(ACTION_BUTTON_WIDTH, -1);
961
 
        Gtk.Alignment logout_button_aligner = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
962
 
        logout_button_aligner.add(logout_button);
963
 
        action_button_layouter.add(logout_button_aligner);
964
 
        action_button_layouter.add(gtk_hspacer(ACTION_BUTTON_SPACING));
965
 
        publish_button = new Gtk.Button.with_mnemonic(_("_Publish"));
966
855
        publish_button.clicked.connect(on_publish_clicked);
967
 
        publish_button.set_size_request(ACTION_BUTTON_WIDTH, -1);
968
 
        Gtk.Alignment publish_button_aligner = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
969
 
        publish_button_aligner.add(publish_button);
970
 
        action_button_layouter.add(publish_button_aligner);
971
 
 
972
 
        Gtk.Alignment action_button_wrapper = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
973
 
        action_button_wrapper.add(action_button_layouter);
974
 
 
975
 
        vert_packer.add(action_button_wrapper);
976
 
 
977
 
        vert_packer.add(gtk_vspacer(2 * PACKER_VERTICAL_PADDING));
978
 
 
979
 
        Gtk.Alignment vert_packer_wrapper = new Gtk.Alignment(0.5f, 0.5f, 0.0f, 0.0f);
980
 
        vert_packer_wrapper.add(vert_packer);
981
 
 
982
 
        add(vert_packer_wrapper);
983
856
    }
984
857
 
985
858
    private void on_publish_clicked() {
986
 
        host.set_config_int(DEFAULT_SIZE_CONFIG_KEY, size_combo.get_active());
987
 
        int photo_major_axis_size = size_descriptions[size_combo.get_active()].major_axis_pixels;
 
859
        // size_combo won't have been set to anything useful if this is the first time we've
 
860
        // published to Picasa, and/or we've only published video before, so it may be negative,
 
861
        // indicating nothing was selected. Clamp it to a valid value...
 
862
        int size_combo_last_active = (size_combo.get_active() >= 0) ? size_combo.get_active() : 0;
 
863
        
 
864
        host.set_config_int(DEFAULT_SIZE_CONFIG_KEY, size_combo_last_active);
 
865
        int photo_major_axis_size = size_descriptions[size_combo_last_active].major_axis_pixels;
988
866
        string album_name;
989
867
        if (create_new_radio.get_active()) {
990
868
            album_name = new_album_entry.get_text();
991
869
            host.set_config_string(LAST_ALBUM_CONFIG_KEY, album_name);
992
870
            bool is_public = public_check.get_active();
993
871
            publish(new PublishingParameters.to_new_album(photo_major_axis_size, album_name,
994
 
                is_public));
 
872
                is_public), strip_metadata_check.get_active());
995
873
        } else {
996
874
            album_name = albums[existing_albums_combo.get_active()].name;
997
875
            host.set_config_string(LAST_ALBUM_CONFIG_KEY, album_name);
998
876
            string album_url = albums[existing_albums_combo.get_active()].url;
999
 
            publish(new PublishingParameters.to_existing_album(photo_major_axis_size, album_url));
 
877
            publish(new PublishingParameters.to_existing_album(photo_major_axis_size, album_url), strip_metadata_check.get_active());
1000
878
        }
1001
879
    }
1002
880
 
1036
914
        result += new SizeDescription(_("Small (640 x 480 pixels)"), 640);
1037
915
        result += new SizeDescription(_("Medium (1024 x 768 pixels)"), 1024);
1038
916
        result += new SizeDescription(_("Recommended (1600 x 1200 pixels)"), 1600);
 
917
        result += new SizeDescription(_("Google+ (2048 x 1536 pixels)"), 2048);
1039
918
        result += new SizeDescription(_("Original Size"), PublishingParameters.ORIGINAL_SIZE);
1040
919
 
1041
920
        return result;
1073
952
        }
1074
953
        update_publish_button_sensitivity();
1075
954
    }
 
955
    
 
956
    protected void notify_publish(PublishingParameters parameters) {
 
957
        publish(parameters, strip_metadata_check.get_active());
 
958
    }
 
959
    
 
960
    protected void notify_logout() {
 
961
        logout();
 
962
    }
 
963
 
 
964
    public Gtk.Widget get_widget() {
 
965
        return pane_widget;
 
966
    }
 
967
    
 
968
    public Spit.Publishing.DialogPane.GeometryOptions get_preferred_geometry() {
 
969
        return Spit.Publishing.DialogPane.GeometryOptions.NONE;
 
970
    }
 
971
    
 
972
    public void on_pane_installed() {
 
973
        installed();
 
974
         
 
975
        publish.connect(notify_publish);
 
976
        logout.connect(notify_logout);
 
977
    }
 
978
    
 
979
    public void on_pane_uninstalled() {
 
980
        publish.disconnect(notify_publish);
 
981
        logout.disconnect(notify_logout);
 
982
    }
1076
983
}
1077
984
 
1078
985
internal class PublishingParameters {
1158
1065
}
1159
1066
 
1160
1067
}
 
1068