Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Settings Sync #249

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/Dialogs/SettingsSync/GitHub.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
*/

namespace GitHub {
public class DeviceAndUserVerificationCodesResponse : Object {
public string device_code { get; set; }
public int expires_in { get; set; }
public int interval { get; set; }
public string user_code { get; set; }
public string verification_uri { get; set;}
}

public class UserAuthorizedDeviceResponse : Object {
public string access_token { get; set; }
public string token_type { get; set; }
public string scope { get; set;}
}

public errordomain Error {
ERROR
}
}

public class GitHub.Manager {
public async DeviceAndUserVerificationCodesResponse request_device_and_user_verification_codes () throws GitHub.Error {
Timeout.add (Random.int_range (3000, 10000), () => {
request_device_and_user_verification_codes.callback ();
return false;
}, Priority.DEFAULT);

yield;

var response = new DeviceAndUserVerificationCodesResponse () {
device_code = "asf7ahsf78ahsf7azgsf7ags7f6agsf67asgf7as",
user_code = "B00A-6EB8",
verification_uri = "https://github.com/login/device",
expires_in = 899,
interval = 5
};

return response;
}

public async UserAuthorizedDeviceResponse request_user_authorized_device (string device_code) throws GitHub.Error {
var response = new UserAuthorizedDeviceResponse () {
access_token = "asjfiajiufahjs89fahsf79ahf783h78aqfh783a",
token_type = "bearer",
scope = "gist"
};

return response;
}

public async UserAuthorizedDeviceResponse poll_user_authorized_device (string device_code, int expires_in, int interval) throws GitHub.Error {
var seconds_left = expires_in - interval;

UserAuthorizedDeviceResponse? response = null;
Timeout.add_seconds_full (Priority.DEFAULT, interval, ()=> {
if (seconds_left <= 0 || response != null) {
poll_user_authorized_device.callback ();
return false;
}

if (seconds_left < 894) {
request_user_authorized_device.begin (device_code, (obj, res) => {
try {
response = request_user_authorized_device.end (res);
} catch (Error e) {
warning ("Could not request user authorized device: %s", e.message);
}
});
}

seconds_left -= interval;

return true;
});

yield;

if (response == null) {
throw new GitHub.Error.ERROR ("Could not get response");
}

return response;
}

private static GLib.Once<Manager> instance;
public static unowned Manager get_default () {
return instance.once (() => {
return new Manager ();
});
}

private Manager () {}
}
123 changes: 123 additions & 0 deletions src/Dialogs/SettingsSync/SettingsSyncDialog.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
*/

public class OnlineAccounts.SettingsSyncDialog : Hdy.Window {
private Hdy.Deck deck;
private SettingsSyncLoginPage login_page;
private SettingsSyncSavePage save_page;
private Gtk.Spinner spinner;

construct {
login_page = new SettingsSyncLoginPage ();
save_page = new SettingsSyncSavePage ();

var header_label = new Granite.HeaderLabel (_("Synchronize your settings across devices"));

var explanation_label = new Gtk.Label (_("Settings Sync syncs your settings to a private Git repository hosted on GitHub."));
explanation_label.set_line_wrap (true);

spinner = new Gtk.Spinner () {
margin = 12
};

var authenticate_cancel_button = new Gtk.Button.with_label (_("Cancel"));

var authenticate_button = new Gtk.Button.with_label (_("Authenticate")) {
can_default = true
};
authenticate_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);

var action_area = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL) {
layout_style = Gtk.ButtonBoxStyle.END,
margin_top = 24,
spacing = 6,
valign = Gtk.Align.END,
vexpand = true
};
action_area.add (authenticate_cancel_button);
action_area.add (authenticate_button);

var authenticate_page = new Gtk.Grid () {
orientation = Gtk.Orientation.VERTICAL,
margin = 12
};
authenticate_page.add (header_label);
authenticate_page.add (explanation_label);
authenticate_page.add (spinner);
authenticate_page.add (action_area);

deck = new Hdy.Deck () {
can_swipe_back = true,
expand = true
};
deck.add (authenticate_page);
deck.add (login_page);
deck.add (save_page);

var window_handle = new Hdy.WindowHandle ();
window_handle.add (deck);

default_height = 400;
default_width = 300;
window_position = Gtk.WindowPosition.CENTER_ON_PARENT;
modal = true;
type_hint = Gdk.WindowTypeHint.DIALOG;
add (window_handle);

authenticate_button.has_default = true;

authenticate_cancel_button.clicked.connect (() => {
destroy ();
});

authenticate_button.clicked.connect (() => {
authenticate_button.sensitive = false;
spinner.start ();

authenticate.begin ((obj, res) => {
try {
authenticate.end (res);
} catch (Error e) {
save_page.show_error (e);
deck.visible_child = save_page;
}
});
});

login_page.cancel.connect (destroy);

save_page.close.connect (destroy);
}

private async void authenticate () throws Error {
var github = GitHub.Manager.get_default ();
var device_response = yield github.request_device_and_user_verification_codes ();

spinner.stop ();
login_page.update (device_response.user_code, device_response.verification_uri);
deck.visible_child = login_page;

var token_response = yield github.poll_user_authorized_device (device_response.device_code, device_response.expires_in, device_response.interval);

// TODO: save token
save_page.show_success ();
deck.visible_child = save_page;
}
}
71 changes: 71 additions & 0 deletions src/Dialogs/SettingsSync/SettingsSyncLoginPage.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
*/

public class OnlineAccounts.SettingsSyncLoginPage : Gtk.Grid {
public signal void cancel ();

private Gtk.Label code_label;
private Gtk.LinkButton link_button;

construct {
var header_label = new Granite.HeaderLabel (_("Your Code"));

code_label = new Gtk.Label ("") {
selectable = true
};
code_label.get_style_context ().add_class ("h3");

var spinner = new Gtk.Spinner () {
margin = 12
};
spinner.start ();

link_button = new Gtk.LinkButton.with_label ("", _("Open in browser"));

var cancel_button = new Gtk.Button.with_label (_("Cancel"));

var action_area = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL) {
layout_style = Gtk.ButtonBoxStyle.END,
margin_top = 24,
spacing = 6,
valign = Gtk.Align.END,
vexpand = true
};
action_area.add (cancel_button);

margin = 12;
orientation = Gtk.Orientation.VERTICAL;
row_spacing = 6;
add (header_label);
add (code_label);
add (spinner);
add (link_button);
add (action_area);

cancel_button.clicked.connect (() => {
cancel ();
});
}

public void update (string user_code, string verification_uri) {
code_label.label = user_code;
link_button.uri = verification_uri;
}
}
88 changes: 88 additions & 0 deletions src/Dialogs/SettingsSync/SettingsSyncSavePage.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
*/

public class OnlineAccounts.SettingsSyncSavePage : Gtk.Grid {
public signal void close ();

private Gtk.Button close_button;

private Gtk.Stack stack;
private Granite.Widgets.AlertView error_alert_view;

construct {
error_alert_view = new Granite.Widgets.AlertView (
_("Settings Sync could not be set up."),
"",
"process-error"
);
error_alert_view.get_style_context ().remove_class (Gtk.STYLE_CLASS_VIEW);
error_alert_view.show_all ();

var success_alert_view = new Granite.Widgets.AlertView (
_("Success"),
_("Settings Sync has been set up."),
"process-completed"
);
success_alert_view.get_style_context ().remove_class (Gtk.STYLE_CLASS_VIEW);
success_alert_view.show_all ();

stack = new Gtk.Stack () {
expand = true,
homogeneous = false,
halign = Gtk.Align.CENTER,
valign = Gtk.Align.CENTER
};
stack.add_named (error_alert_view, "error");
stack.add_named (success_alert_view, "success");

close_button = new Gtk.Button.with_label (_("Close")) {
can_default = true
};
close_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);

var action_area = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL) {
layout_style = Gtk.ButtonBoxStyle.END,
margin_top = 24,
spacing = 6,
valign = Gtk.Align.END,
vexpand = true
};
action_area.add (close_button);

margin = 12;
orientation = Gtk.Orientation.VERTICAL;
row_spacing = 6;
add (stack);
add (action_area);

close_button.clicked.connect (() => {
close ();
});
}

public void show_success () {
stack.set_visible_child_name ("success");
}

public void show_error (Error error) {
error_alert_view.description = error.message;
stack.set_visible_child_name ("error");
}
}