diff --git a/src/Dialogs/CloneRepositoryDialog.vala b/src/Dialogs/CloneRepositoryDialog.vala index 8a0e2dfa4..149eed2ac 100644 --- a/src/Dialogs/CloneRepositoryDialog.vala +++ b/src/Dialogs/CloneRepositoryDialog.vala @@ -20,6 +20,17 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { private Gtk.Button clone_button; private Gtk.Stack stack; private Gtk.Spinner spinner; + private Gtk.Revealer revealer; + private Gtk.Label message_label; + private Gtk.Label indexing_label; + private Gtk.Label progress_label; + private Gtk.ProgressBar transfer_progress_bar; + private uint total_objects = 0; + private uint received_objects = 0; + private uint indexed_objects = 0; + private size_t received_bytes = 0; + private string remote_message = ""; + private uint update_transfer_info_timeout_id = 0; public bool can_clone { get; private set; default = false; } public string suggested_local_folder { get; construct; } @@ -36,6 +47,10 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { spinner.stop (); } } + + get { + return spinner.active; + } } public CloneRepositoryDialog (string _suggested_local_folder, string _suggested_remote) { @@ -45,6 +60,12 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { ); } + ~CloneRepositoryDialog () { + if (update_transfer_info_timeout_id > 0) { + Source.remove (update_transfer_info_timeout_id); + } + } + construct { transient_for = ((Gtk.Application)(GLib.Application.get_default ())).get_active_window (); image_icon = new ThemedIcon ("git"); @@ -119,15 +140,31 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { content_box.add (new CloneEntry (_("Location"), folder_chooser_button)); content_box.add (new CloneEntry (_("Name of Clone"), local_project_name_entry)); - var cloning_label = new Granite.HeaderLabel (_("Cloning in progress")); - spinner = new Gtk.Spinner (); - - var cloning_box = new Gtk.Box (HORIZONTAL, 12) { + var cloning_box = new Gtk.Box (VERTICAL, 24) { + valign = CENTER, + halign = CENTER + }; + var spinner_box = new Gtk.Box (HORIZONTAL, 12) { valign = CENTER, halign = CENTER }; - cloning_box.add (cloning_label); - cloning_box.add (spinner); + message_label = new Gtk.Label (""); + progress_label = new Gtk.Label (""); + indexing_label = new Gtk.Label (""); + transfer_progress_bar = new Gtk.ProgressBar () { + fraction = 0.0 + }; + + var cloning_label = new Granite.HeaderLabel (_("Cloning in progress")); + spinner = new Gtk.Spinner (); + spinner_box.add (cloning_label); + spinner_box.add (spinner); + + cloning_box.add (spinner_box); + cloning_box.add (message_label); + cloning_box.add (transfer_progress_bar); + cloning_box.add (progress_label); + cloning_box.add (indexing_label); stack = new Gtk.Stack (); stack.add_named (content_box, "entries"); @@ -160,6 +197,58 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { } } + public Ggit.RemoteCallbacks? get_remote_callbacks () { + update_transfer_info_timeout_id = Timeout.add (200, () => { + if (total_objects > 0) { + transfer_progress_bar.fraction = received_objects / total_objects; + } + + indexing_label.label = "%u indexed objects".printf (indexed_objects); + progress_label.label = "%u bytes received".printf ((uint)received_bytes); + message_label.label = remote_message; + + if (cloning_in_progress) { + return Source.CONTINUE; + } else { + update_transfer_info_timeout_id = 0; + return Source.REMOVE; + } + }); + + var cbs = new RemoteCallbacks (); + cbs.progress.connect ((message) => { + remote_message = message; + }); + cbs.transfer_progress.connect ((progress) => { + if (progress != null) { + received_objects = progress.get_received_objects (); + received_bytes = progress.get_received_bytes (); + + indexed_objects = progress.get_indexed_objects (); + total_objects = progress.get_total_objects (); + } + }); + //TODO Decide what to do with completion info. Notification? + cbs.completion.connect ((type) => { + warning ("received completion signal"); + switch (type) { + case DOWNLOAD: + warning ("Download completed"); + break; + case INDEXING: + warning ("Indexing completed"); + break; + case ERROR: + warning ("Transfer ended with error"); + break; + default: + assert_not_reached (); + } + }); + + return cbs; + } + public string get_valid_source_repository_uri () requires (can_clone) { //TODO Further validation here? return remote_repository_uri_entry.text; @@ -254,4 +343,29 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { orientation = VERTICAL; } } + + //Provide for plaintext user password for now + private class RemoteCallbacks : Ggit.RemoteCallbacks { + public string? user { get; construct; } + public string? password { get; construct; } + + public RemoteCallbacks (string? user = null, string? password = null) { + Object ( + user: user, + password: password + ); + } + + public override Ggit.Cred? credentials ( + string url, + string? username_from_url, + Ggit.Credtype allowed_types + ) throws Error { + if (user != null && password != null && Ggit.Credtype.USERPASS_PLAINTEXT in allowed_types) { + return new Ggit.CredPlaintext (user, password); + } else { + return null; + } + } + } } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 10e02af36..e13cb080e 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1055,34 +1055,26 @@ namespace Scratch { // Persist last entries (not necessarily valid) Scratch.settings.set_string ("default-remote", clone_dialog.get_remote ()); Scratch.settings.set_string ("default-projects-folder", clone_dialog.get_projects_folder ()); - // Clone dialog show spinner during cloning so keep visible //TODO Show more information re progress using Ggit callbacks if (res == Gtk.ResponseType.APPLY && clone_dialog.can_clone) { - clone_dialog.cloning_in_progress = true; + sidebar.cloning_in_progress = true; var uri = clone_dialog.get_valid_source_repository_uri (); var target = clone_dialog.get_valid_target (); + var callbacks = clone_dialog.get_remote_callbacks (); + clone_dialog.hide (); git_manager.clone_repository.begin ( uri, target, + callbacks, (obj, res) => { - clone_dialog.cloning_in_progress = false; + sidebar.cloning_in_progress = false; File? workdir = null; string? error = null; if (git_manager.clone_repository.end (res, out workdir, out error)) { open_folder (workdir); clone_dialog.destroy (); - var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( - _("Repository %s successfully cloned").printf (uri), - _("Local repository working directory is %s").printf (workdir.get_uri ()), - "dialog-information", - Gtk.ButtonsType.CLOSE - ) { - transient_for = this - }; - message_dialog.response.connect (message_dialog.destroy); - message_dialog.present (); + sidebar.notify_cloning_success (); } else { - clone_dialog.hide (); var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( _("Unable to clone %s").printf (uri), error, diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 5b80d3084..988d21e81 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -116,6 +116,7 @@ namespace Scratch.Services { public async bool clone_repository ( string uri, string local_folder, + Ggit.RemoteCallbacks? rcallbacks, out File? repo_workdir, out string? error ) { @@ -125,7 +126,7 @@ namespace Scratch.Services { var fetch_options = new Ggit.FetchOptions (); fetch_options.set_download_tags (Ggit.RemoteDownloadTagsType.UNSPECIFIED); //TODO Set callbacks for authentification and progress - fetch_options.set_remote_callbacks (null); + fetch_options.set_remote_callbacks (rcallbacks); var clone_options = new Ggit.CloneOptions (); clone_options.set_local (Ggit.CloneLocal.AUTO); diff --git a/src/Widgets/ChooseProjectButton.vala b/src/Widgets/ChooseProjectButton.vala index 3b035c5b2..1dc1aa44c 100644 --- a/src/Widgets/ChooseProjectButton.vala +++ b/src/Widgets/ChooseProjectButton.vala @@ -17,6 +17,7 @@ */ public class Code.ChooseProjectButton : Gtk.MenuButton { + public bool cloning_in_progress { get; set; } private const string NO_PROJECT_SELECTED = N_("No Project Selected"); private const string PROJECT_TOOLTIP = N_("Active Git Project: %s"); private Gtk.Label label_widget; @@ -35,12 +36,18 @@ public class Code.ChooseProjectButton : Gtk.MenuButton { xalign = 0.0f }; - var grid = new Gtk.Grid () { - halign = Gtk.Align.START + var cloning_spinner = new Gtk.Spinner (); + bind_property ("cloning-in-progress", cloning_spinner, "active"); + + var box = new Gtk.Box (HORIZONTAL, 3) { + hexpand = true, + vexpand = false }; - grid.add (img); - grid.add (label_widget); - add (grid); + + box.add (img); + box.add (label_widget); + box.add (cloning_spinner); + add (box); project_listbox = new Gtk.ListBox () { selection_mode = Gtk.SelectionMode.SINGLE diff --git a/src/Widgets/Sidebar.vala b/src/Widgets/Sidebar.vala index ff3060cf4..1998cb6c9 100644 --- a/src/Widgets/Sidebar.vala +++ b/src/Widgets/Sidebar.vala @@ -27,7 +27,19 @@ public class Code.Sidebar : Gtk.Grid { public Hdy.HeaderBar headerbar { get; private set; } public GLib.MenuModel project_menu_model { get; construct; } + // May show progress in different way in future + public bool cloning_in_progress { + get { + return choose_project_button.cloning_in_progress; + } + + set { + choose_project_button.cloning_in_progress = value; + } + } + private Gtk.StackSwitcher stack_switcher; + private Granite.Widgets.Toast cloning_success_toast; construct { orientation = Gtk.Orientation.VERTICAL; @@ -38,6 +50,8 @@ public class Code.Sidebar : Gtk.Grid { valign = Gtk.Align.CENTER }; + cloning_success_toast = new Granite.Widgets.Toast (_("Cloning complete")); + headerbar = new Hdy.HeaderBar () { custom_title = choose_project_button, show_close_button = true @@ -80,6 +94,7 @@ public class Code.Sidebar : Gtk.Grid { actionbar.pack_start (project_menu_button); add (headerbar); + add (cloning_success_toast); add (stack_switcher); add (stack); add (actionbar); @@ -161,4 +176,8 @@ public class Code.Sidebar : Gtk.Grid { public void remove_tab (Code.PaneSwitcher tab) { stack.remove (tab); } + + public void notify_cloning_success () { + cloning_success_toast.send_notification (); + } }