|
@@ -0,0 +1,318 @@
|
|
|
+using Gtk;
|
|
|
+using Adw;
|
|
|
+using Invercargill;
|
|
|
+
|
|
|
+namespace Publicate {
|
|
|
+
|
|
|
+
|
|
|
+ public class PublishWindow : Adw.Window {
|
|
|
+
|
|
|
+ private Adw.HeaderBar header_bar;
|
|
|
+ private ViewerWindow window;
|
|
|
+ private Stack stack;
|
|
|
+
|
|
|
+ private Box loader;
|
|
|
+ private Label loader_status;
|
|
|
+ private ProgressBar loader_progress;
|
|
|
+ private bool loader_pulsing = false;
|
|
|
+
|
|
|
+ private Box identity_select;
|
|
|
+ private IdentityList identity_list;
|
|
|
+ private Button publish_button;
|
|
|
+
|
|
|
+ private Box decision;
|
|
|
+ private Label decision_heading;
|
|
|
+ private Label decision_body;
|
|
|
+ private Button cancel_button;
|
|
|
+ private Button continue_button;
|
|
|
+
|
|
|
+ private Box complete;
|
|
|
+
|
|
|
+ private delegate void DecisionCallback();
|
|
|
+ private DecisionCallback cancel_action;
|
|
|
+ private DecisionCallback continue_action;
|
|
|
+
|
|
|
+ public PublishWindow(ViewerWindow window) {
|
|
|
+ this.window = window;
|
|
|
+ modal = true;
|
|
|
+ transient_for = window;
|
|
|
+
|
|
|
+ var box = new Box(Orientation.VERTICAL, 0);
|
|
|
+ header_bar = new Adw.HeaderBar();
|
|
|
+ header_bar.show_end_title_buttons = false;
|
|
|
+ title = "Publish";
|
|
|
+
|
|
|
+ default_height = 500;
|
|
|
+ default_width = 700;
|
|
|
+
|
|
|
+ box.append (header_bar);
|
|
|
+
|
|
|
+ stack = new Stack();
|
|
|
+ stack.vexpand = true;
|
|
|
+ stack.transition_type = StackTransitionType.CROSSFADE;
|
|
|
+ box.append(stack);
|
|
|
+ content = box;
|
|
|
+
|
|
|
+ loader = new Box(Orientation.VERTICAL, 8);
|
|
|
+ loader_status = new Label ("Please wait…");
|
|
|
+ loader_status.halign = Align.START;
|
|
|
+ loader_progress = new ProgressBar ();
|
|
|
+ loader_progress.hexpand = true;
|
|
|
+ loader.append (loader_status);
|
|
|
+ loader.append (loader_progress);
|
|
|
+ loader.width_request = 550;
|
|
|
+ loader.valign = Align.CENTER;
|
|
|
+ loader.halign = Align.CENTER;
|
|
|
+
|
|
|
+ stack.add_child (loader);
|
|
|
+ stack.visible_child = loader;
|
|
|
+
|
|
|
+ identity_select = new Box(Orientation.VERTICAL, 8);
|
|
|
+ identity_select.margin_top = 18;
|
|
|
+ identity_select.margin_start = 18;
|
|
|
+ identity_select.margin_end = 18;
|
|
|
+ identity_select.margin_bottom = 18;
|
|
|
+
|
|
|
+ var identity_header = new Label("Select identity to publish with");
|
|
|
+ identity_header.halign = Align.START;
|
|
|
+ identity_header.add_css_class("title-2");
|
|
|
+ identity_select.append(identity_header);
|
|
|
+
|
|
|
+ identity_list = new IdentityList(window);
|
|
|
+ identity_select.append(identity_list);
|
|
|
+
|
|
|
+ publish_button = new Button.with_label("Publish");
|
|
|
+ publish_button.add_css_class("suggested-action");
|
|
|
+ publish_button.halign = Align.END;
|
|
|
+ publish_button.clicked.connect(() => publish_with.begin(false));
|
|
|
+ identity_select.append(publish_button);
|
|
|
+
|
|
|
+ stack.add_child(identity_select);
|
|
|
+
|
|
|
+ decision = new Box(Orientation.VERTICAL, 8);
|
|
|
+ decision.halign = Align.CENTER;
|
|
|
+ decision.valign = Align.CENTER;
|
|
|
+ decision.margin_top = 18;
|
|
|
+ decision.margin_start = 64;
|
|
|
+ decision.margin_end = 64;
|
|
|
+ decision.margin_bottom = 18;
|
|
|
+ decision.width_request = 200;
|
|
|
+
|
|
|
+ decision_heading = new Label("Decision");
|
|
|
+ decision_heading.halign = Align.START;
|
|
|
+ decision_heading.add_css_class("title-2");
|
|
|
+ decision.append(decision_heading);
|
|
|
+
|
|
|
+ decision_body = new Label("Details");
|
|
|
+ decision_body.halign = Align.START;
|
|
|
+ decision_body.hexpand = true;
|
|
|
+ decision_body.wrap = true;
|
|
|
+ decision_body.wrap_mode = Pango.WrapMode.WORD;
|
|
|
+ decision_body.natural_wrap_mode = NaturalWrapMode.WORD;
|
|
|
+ decision.append(decision_body);
|
|
|
+
|
|
|
+ var button_box = new Box(Orientation.HORIZONTAL, 0);
|
|
|
+ button_box.add_css_class("linked");
|
|
|
+
|
|
|
+ cancel_button = new Button.with_label("Cancel");
|
|
|
+ cancel_button.hexpand = true;
|
|
|
+ cancel_button.clicked.connect(() => cancel_action());
|
|
|
+ button_box.append(cancel_button);
|
|
|
+
|
|
|
+ continue_button = new Button.with_label("Continue");
|
|
|
+ continue_button.hexpand = true;
|
|
|
+ continue_button.clicked.connect(() => continue_action());
|
|
|
+ button_box.append(continue_button);
|
|
|
+
|
|
|
+ decision.append(button_box);
|
|
|
+ stack.add_child(decision);
|
|
|
+
|
|
|
+ complete = new Box(Orientation.VERTICAL, 8);
|
|
|
+ complete.halign = Align.CENTER;
|
|
|
+ complete.valign = Align.CENTER;
|
|
|
+ complete.margin_top = 18;
|
|
|
+ complete.margin_start = 18;
|
|
|
+ complete.margin_end = 18;
|
|
|
+ complete.margin_bottom = 18;
|
|
|
+
|
|
|
+ var status_page = new StatusPage();
|
|
|
+ complete.append(status_page);
|
|
|
+ status_page.icon_name = "emblem-ok-symbolic";
|
|
|
+ status_page.title = "Publish Completed!";
|
|
|
+
|
|
|
+ var close_button = new Button.with_label("Close");
|
|
|
+ status_page.child = close_button;
|
|
|
+ close_button.width_request = 400;
|
|
|
+ close_button.clicked.connect(() => close());
|
|
|
+
|
|
|
+ stack.add_child(complete);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void pulse_loader() {
|
|
|
+ if(loader_pulsing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ loader_pulsing = true;
|
|
|
+ Timeout.add(100, () => {
|
|
|
+ if(!loader_pulsing) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ loader_progress.pulse();
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void show_decision(string heading, string body, bool is_destructive, DecisionCallback cancel_cb, DecisionCallback continue_cb) {
|
|
|
+ decision_heading.set_text(heading);
|
|
|
+ decision_body.set_text(body);
|
|
|
+
|
|
|
+ continue_button.set_css_classes(new string[] { is_destructive ? "destructive-action" : "suggested-action"});
|
|
|
+ continue_action = continue_cb;
|
|
|
+ cancel_action = cancel_cb;
|
|
|
+
|
|
|
+ stack.visible_child = decision;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Ppub.Collection collection;
|
|
|
+ private CollectionConfig config;
|
|
|
+ private Pprf.Client client;
|
|
|
+ private File publication_file;
|
|
|
+ private string dest_name;
|
|
|
+ private BinaryData cid;
|
|
|
+ private bool unpublish = false;
|
|
|
+ private DateTime? old_publish_timestamp;
|
|
|
+ public async void publish_to(CollectionConfig config, File publication_file) {
|
|
|
+ // TODO wrap in try catch
|
|
|
+
|
|
|
+ this.config = config;
|
|
|
+ this.publication_file = publication_file;
|
|
|
+ dest_name = publication_file.get_basename();
|
|
|
+ if(!dest_name.has_suffix(".ppub")) {
|
|
|
+ dest_name += ".ppub";
|
|
|
+ }
|
|
|
+
|
|
|
+ pulse_loader();
|
|
|
+ loader_status.set_text(@"Looking up $(config.domain)…");
|
|
|
+ client = yield window.collection_service.get_client(config);
|
|
|
+ cid = new BinaryData.from_base64(config.collection_id);
|
|
|
+
|
|
|
+ loader_status.set_text(@"Querying $(config.domain) for collection information…");
|
|
|
+ collection = yield do_in_bg<Ppub.Collection>(() => client.get_collection(cid));
|
|
|
+
|
|
|
+ loader_pulsing = false;
|
|
|
+ loader_progress.fraction = 0;
|
|
|
+
|
|
|
+ if(collection.publications.any(p => p.file_name == dest_name)) {
|
|
|
+ show_decision("Replace Existing Publication?", @"There is already a published publication with the name \"$dest_name\", if you continue it will be replaced.", true, () => close(), () => select_identity(true));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ select_identity(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void select_identity(bool unpub) {
|
|
|
+ if(unpub) {
|
|
|
+ old_publish_timestamp = collection.publications.first(p => p.file_name == dest_name).publication_time;
|
|
|
+ }
|
|
|
+ unpublish = unpub;
|
|
|
+ stack.visible_child = identity_select;
|
|
|
+ identity_list.populate_identities(collection);
|
|
|
+ publish_button.sensitive = identity_list.has_entries;
|
|
|
+ header_bar.show_end_title_buttons = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void publish_with(bool overwrite) {
|
|
|
+ // TODO wrap in try catch
|
|
|
+
|
|
|
+ header_bar.show_end_title_buttons = false;
|
|
|
+ stack.visible_child = loader;
|
|
|
+
|
|
|
+ loader_status.set_text(@"Registering name \"$(dest_name)\"…");
|
|
|
+ pulse_loader();
|
|
|
+ try {
|
|
|
+ yield do_void_in_bg(() => client.register_name(cid, dest_name, identity_list.selected_identity));
|
|
|
+ }
|
|
|
+ catch(Pprf.Messages.PprfFailureError.NAME_EXISTS e) {
|
|
|
+ // Ignore if unpublishing anyway
|
|
|
+ if(unpublish) {
|
|
|
+ yield upload_and_publish(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else{
|
|
|
+ show_decision("Overwrite file?", @"There is already a file on this server with the name \"$dest_name\", if you continue it will be overwritten.", true, () => close(), () => upload_and_publish.begin(true));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ yield upload_and_publish(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void upload_and_publish(bool replace_destination) {
|
|
|
+ stack.visible_child = loader;
|
|
|
+ loader_status.set_text(@"Preparing to upload publication…");
|
|
|
+ pulse_loader();
|
|
|
+
|
|
|
+ var file_info = yield publication_file.query_info_async("*", FileQueryInfoFlags.NONE, 1);
|
|
|
+ var file_size = file_info.get_size();
|
|
|
+ var file_stream = yield publication_file.read_async(1);
|
|
|
+ var flags = replace_destination ? Pprf.Messages.FinaliseUploadFlags.OVERWRITE_DESTINATION : 0;
|
|
|
+ yield do_void_in_bg(() => client.upload(cid, file_stream, file_size, dest_name, unpublish, identity_list.selected_identity, upload_callback, flags));
|
|
|
+
|
|
|
+ pulse_loader();
|
|
|
+ loader_status.set_text(@"Computing publication checksum…");
|
|
|
+ var checksum = yield do_in_bg<BinaryData>(() => new BinaryData.from_byte_array(Pprf.Util.file_checksum(publication_file)));
|
|
|
+
|
|
|
+ loader_status.set_text(@"Signing publication…");
|
|
|
+ var timestamp = new DateTime.now_local();
|
|
|
+ if(old_publish_timestamp != null) {
|
|
|
+ timestamp = old_publish_timestamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ var publication = yield do_in_bg<Ppub.CollectionPublication>(() => new Ppub.CollectionPublication(dest_name, timestamp, identity_list.selected_identity.name, identity_list.selected_credentials, checksum.to_array()));
|
|
|
+
|
|
|
+ loader_status.set_text(@"Publishing…");
|
|
|
+ yield do_void_in_bg(() => client.publish(cid, publication, identity_list.selected_identity));
|
|
|
+
|
|
|
+ stack.visible_child = complete;
|
|
|
+ loader_pulsing = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double upload_frac;
|
|
|
+ private void upload_callback(uint64 bytes_sent, uint64 bytes_total, Pprf.UploadStatus status) {
|
|
|
+ print(@"Got update: $status\n");
|
|
|
+ upload_frac = ((double)bytes_sent)/((double)bytes_total);
|
|
|
+
|
|
|
+ switch(status) {
|
|
|
+ case Pprf.UploadStatus.INITIATING_SESSION:
|
|
|
+ Idle.add_once(() => loader_status.set_text(@"Establishing upload session with server…"));
|
|
|
+ break;
|
|
|
+ case Pprf.UploadStatus.UPLOADING_CHUNK:
|
|
|
+ case Pprf.UploadStatus.UPLOADED_CHUNK:
|
|
|
+ Idle.add_once(() => {
|
|
|
+ loader_pulsing = false;
|
|
|
+ loader_status.set_text(@"Uploading publication…");
|
|
|
+ loader_progress.fraction = upload_frac;
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case Pprf.UploadStatus.UNPUBLISHING:
|
|
|
+ Idle.add_once(() => {
|
|
|
+ loader_status.set_text(@"Unpublishing previous publication…");
|
|
|
+ pulse_loader();
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case Pprf.UploadStatus.FINALISING_SESSION:
|
|
|
+ Idle.add_once(() => {
|
|
|
+ loader_status.set_text(@"Finalising upload…");
|
|
|
+ pulse_loader();
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case Pprf.UploadStatus.COMPLETE:
|
|
|
+ Idle.add_once(() => {
|
|
|
+ loader_status.set_text(@"Upload completed!");
|
|
|
+ pulse_loader();
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|