[seahorse/nielsdg/ssh] ssh: Make Key.parse() a proper async method
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [seahorse/nielsdg/ssh] ssh: Make Key.parse() a proper async method
- Date: Tue, 24 May 2022 07:11:56 +0000 (UTC)
commit f339233dc45e59d45e27486f6b9e57fae9fd8171
Author: Niels De Graef <nielsdegraef gmail com>
Date: Tue May 24 09:08:21 2022 +0200
ssh: Make Key.parse() a proper async method
This allows us to make full use of the event loop to parse keys, and
simplifies the code quite a bit.
ssh/actions.vala | 16 +-
ssh/generate.vala | 64 ++++----
ssh/key.vala | 56 +++----
ssh/seahorse-ssh-generate.ui | 351 +++++++++++++++++++------------------------
ssh/source.vala | 39 +++--
5 files changed, 237 insertions(+), 289 deletions(-)
---
diff --git a/ssh/actions.vala b/ssh/actions.vala
index d2c37b1f..6aad1ee5 100644
--- a/ssh/actions.vala
+++ b/ssh/actions.vala
@@ -57,13 +57,19 @@ public class Seahorse.Ssh.Actions : ActionGroup {
}
private void on_ssh_generate_key(SimpleAction action, Variant? param) {
- Generate generate_dialog = new Generate(Backend.instance.get_dot_ssh(),
- this.catalog);
+ var dialog = new Generate(Backend.instance.get_dot_ssh(),
+ this.catalog);
- int response = generate_dialog.run();
- if (response == Gtk.ResponseType.ACCEPT)
+ if (dialog.run() != Gtk.ResponseType.ACCEPT) {
+ dialog.destroy();
+ return;
+ }
+
+ dialog.generate_key.begin ((obj, res) => {
+ dialog.generate_key.end (res);
+ dialog.destroy();
this.catalog.activate_action("focus-place", "openssh");
- generate_dialog.destroy();
+ });
}
private void on_ssh_upload(SimpleAction action, Variant? param) {
diff --git a/ssh/generate.vala b/ssh/generate.vala
index eb577a55..e797d779 100644
--- a/ssh/generate.vala
+++ b/ssh/generate.vala
@@ -24,7 +24,7 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
public const int DEFAULT_DSA_SIZE = 1024;
public const int DEFAULT_RSA_SIZE = 2048;
- private Source source;
+ public Ssh.Source source { get; construct set; }
[GtkChild]
private unowned Gtk.Grid details_grid;
@@ -33,17 +33,15 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
private unowned Gtk.Entry email_entry;
[GtkChild]
private unowned Gtk.ComboBoxText algorithm_combo_box;
- [GtkChild]
- private unowned Gtk.Button create_with_setup_button;
- [GtkChild]
- private unowned Gtk.Button create_no_setup_button;
public Generate(Source src, Gtk.Window parent) {
- this.transient_for = parent;
- this.source = src;
+ GLib.Object (
+ source: src,
+ use_header_bar: 1,
+ transient_for: parent
+ );
- this.create_no_setup_button.clicked.connect((b) => create_key(false));
- this.create_with_setup_button.clicked.connect((b) => create_key(true));
+ this.source = src;
this.key_length_chooser = new KeyLengthChooser();
this.key_length_chooser.halign = Gtk.Align.START;
@@ -59,7 +57,13 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
this.key_length_chooser.algorithm = Algorithm.from_string(t);
}
- private void create_key(bool upload) {
+ /**
+ * Generate a key from the fields that were filled in.
+ *
+ * Note: make sure you don't destroy the window before this async method
+ * has finished
+ */
+ public async void generate_key() {
// The email address
string email = this.email_entry.text;
@@ -74,34 +78,22 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
string filename = this.source.new_filename_for_algorithm(type);
// We start creation
- Cancellable cancellable = new Cancellable();
- GenerateOperation op = new GenerateOperation();
- op.generate_async.begin(filename, email, type, bits, cancellable, (obj, res) => {
+ try {
+ debug("Generating %s key '%s' (file '%s')", t, email, filename);
+ GenerateOperation op = new GenerateOperation();
+ Cancellable cancellable = new Cancellable();
+ Seahorse.Progress.show(cancellable, _("Creating Secure Shell Key"), false);
+ yield op.generate_async(filename, email, type, bits, cancellable);
+
+ // We generated a key, but we still need to import it
try {
- op.generate_async.end(res);
-
- // The result of the operation is the key we generated
- source.add_key_from_filename.begin(filename, (obj, res) => {
- try {
- Key key = source.add_key_from_filename.end(res);
-
- if (upload && key != null) {
- List<Key> keys = new List<Key>();
- keys.append(key);
- Upload.prompt(keys, null);
- }
- } catch (GLib.Error e) {
- Seahorse.Util.show_error(null, _("Couldn’t load newly generated Secure Shell key"),
e.message);
- }
- });
+ debug("Importing generated key (file '%s')", filename);
+ Key key = yield source.add_key_from_filename(filename);
} catch (GLib.Error e) {
- Seahorse.Util.show_error(null, _("Couldn’t generate Secure Shell key"), e.message);
+ Seahorse.Util.show_error(null, _("Couldn’t load newly generated Secure Shell key"),
e.message);
}
- });
- Seahorse.Progress.show(cancellable, _("Creating Secure Shell Key"), false);
-
- response(Gtk.ResponseType.ACCEPT);
- destroy();
+ } catch (GLib.Error e) {
+ Seahorse.Util.show_error(null, _("Couldn’t generate Secure Shell key"), e.message);
+ }
}
-
}
diff --git a/ssh/key.vala b/ssh/key.vala
index da3ba01b..e2ebc30b 100644
--- a/ssh/key.vala
+++ b/ssh/key.vala
@@ -217,31 +217,31 @@ public class Seahorse.Ssh.Key : Seahorse.Object, Seahorse.Exportable, Seahorse.D
}
}
+ public struct KeyParseResult {
+ public KeyData[] public_keys;
+ public SecData[] secret_keys;
+ }
+
/**
* Parses a string into public/private keys.
*
* @param data The string that needs to be parsed.
- * @param pub_handler Will be called anytime a public key has been parsed.
- * If null, nothing will be done if a public key is parsed.
- * @param priv_handler Will be called anytime a private key has been parsed.
- * If null, nothing will be done if a private key is parsed.
* @param cancellable Can be used to cancel the parsing.
*/
- public static async void parse(string data,
- PubParsedHandler? pub_handler,
- PrivParsedHandler? priv_handler = null,
- Cancellable? cancellable = null) throws GLib.Error {
- if (data == null || data == "")
- return;
+ public static async KeyParseResult parse(string data,
+ Cancellable? cancellable = null) throws GLib.Error {
+ return_if_fail (data != null || data != "");
+
+ var pubkeys = new GenericArray<KeyData>();
+ var seckeys = new GenericArray<SecData>();
StringBuilder toParse = new StringBuilder(data.chug());
while (toParse.str.length > 0) {
// First of all, check for a private key, as it can span several lines
if (SecData.contains_private_key(toParse.str)) {
try {
- SecData secdata = SecData.parse_data(toParse);
- if (priv_handler != null)
- priv_handler(secdata);
+ var secdata = SecData.parse_data(toParse);
+ seckeys.add(secdata);
continue;
} catch (GLib.Error e) {
warning(e.message);
@@ -262,41 +262,29 @@ public class Seahorse.Ssh.Key : Seahorse.Object, Seahorse.Exportable, Seahorse.D
// See if we have a public key
try {
KeyData keydata = KeyData.parse_line(line);
- if (pub_handler != null)
- pub_handler(keydata);
+ pubkeys.add(keydata);
} catch (GLib.Error e) {
warning(e.message);
}
}
+
+ var result = KeyParseResult();
+ result.public_keys = pubkeys.steal();
+ result.secret_keys = seckeys.steal();
+ return result;
}
/**
* Parses the contents of the given file into public/private keys.
*
* @param data The file that will be parsed.
- * @param pub_handler Will be called anytime a public key has been parsed.
- * If null, nothing will be done if a public key is parsed.
- * @param priv_handler Will be called anytime a private key has been parsed.
- * If null, nothing will be done if a private key is parsed.
* @param cancellable Can be used to cancel the parsing.
*/
- public static async void parse_file(string filename,
- PubParsedHandler? pub_handler,
- PrivParsedHandler? priv_handler = null,
- Cancellable? cancellable = null) throws GLib.Error {
+ public static async KeyParseResult parse_file(string filename,
+ Cancellable? cancellable = null) throws GLib.Error {
string contents;
FileUtils.get_contents(filename, out contents);
- yield parse(contents, pub_handler, priv_handler, cancellable);
+ return yield parse(contents, cancellable);
}
-
- /**
- * Takes care of the public key that has been found in a string while parsing.
- */
- public delegate bool PubParsedHandler(KeyData data);
-
- /**
- * Takes care of the private key that has been found in a string while parsing.
- */
- public delegate bool PrivParsedHandler(SecData data);
}
diff --git a/ssh/seahorse-ssh-generate.ui b/ssh/seahorse-ssh-generate.ui
index 09645877..a9db2c27 100644
--- a/ssh/seahorse-ssh-generate.ui
+++ b/ssh/seahorse-ssh-generate.ui
@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <requires lib="gtk+" version="3.22"/>
+ <requires lib="gtk+" version="3.24"/>
<template class="SeahorseSshGenerate" parent="GtkDialog">
<property name="modal">True</property>
- <property name="resizable">False</property>
- <property name="border_width">5</property>
+ <property name="border_width">18</property>
<property name="title" translatable="yes">New Secure Shell Key</property>
<child internal-child="vbox">
<object class="GtkBox">
@@ -13,236 +12,202 @@
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
- <object class="GtkBox" id="hbox4">
+ <object class="GtkBox" id="vbox5">
<property name="visible">True</property>
- <property name="orientation">horizontal</property>
+ <property name="orientation">vertical</property>
<property name="can_focus">False</property>
- <property name="border_width">5</property>
<property name="spacing">12</property>
<child>
- <object class="GtkImage" id="ssh-image">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="yalign">0</property>
- <property name="pixel_size">48</property>
- <property name="icon_name">gcr-key-pair</property>
- </object>
- </child>
- <child>
- <object class="GtkBox" id="vbox5">
+ <object class="GtkBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
- <object class="GtkBox" id="vbox3">
+ <object class="GtkBox" id="hbox41">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
+ <property name="orientation">horizontal</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
- <object class="GtkBox" id="hbox41">
+ <object class="GtkLabel" id="label45">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">A Secure Shell (SSH) key lets you connect
securely to other computers.</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="details_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="email_label">
<property name="visible">True</property>
- <property name="orientation">horizontal</property>
<property name="can_focus">False</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkLabel" id="label45">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="yalign">0</property>
- <property name="label" translatable="yes">A Secure Shell (SSH) key lets you
connect securely to other computers.</property>
- <property name="wrap">True</property>
- </object>
- </child>
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">_Description</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">email_entry</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
</object>
<packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
</packing>
</child>
<child>
- <object class="GtkGrid" id="details_grid">
+ <object class="GtkEntry" id="email_entry">
+ <property name="width_request">180</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="row_spacing">6</property>
- <property name="column_spacing">12</property>
- <child>
- <object class="GtkLabel" id="email_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">1</property>
- <property name="yalign">0</property>
- <property name="label" translatable="yes">_Description</property>
- <property name="use_markup">True</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">email_entry</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="top_attach">0</property>
- <property name="left_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="email_entry">
- <property name="width_request">180</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">●</property>
- <property name="activates_default">True</property>
- <property name="invisible_char_set">True</property>
- </object>
- <packing>
- <property name="top_attach">0</property>
- <property name="left_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="xpad">3</property>
- <property name="label" translatable="yes">Your email address, or a reminder of
what this key is for.</property>
- <property name="wrap">True</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label49">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">end</property>
- <property name="label" translatable="yes">Encryption _Type</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">algorithm_combo_box</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="top_attach">2</property>
- <property name="left_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBoxText" id="algorithm_combo_box">
- <property name="visible">True</property>
- <property name="halign">start</property>
- <property name="can_focus">False</property>
- <property name="entry_text_column">0</property>
- <property name="id_column">1</property>
- <items>
- <item translatable="yes">RSA</item>
- <item translatable="yes">DSA</item>
- <item translatable="yes">ECDSA</item>
- <item translatable="yes">ED25519</item>
- </items>
- <signal name="changed" handler="on_algo_changed"/>
- </object>
- <packing>
- <property name="top_attach">2</property>
- <property name="left_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label50">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">end</property>
- <property name="label" translatable="yes">Key _Strength (bits)</property>
- <property name="use_underline">True</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="top_attach">3</property>
- <property name="left_attach">0</property>
- </packing>
- </child>
- <child>
- <!-- Key strength widget comes here -->
- <placeholder/>
- </child>
+ <property name="xalign">0</property>
+ <property name="xpad">3</property>
+ <property name="label" translatable="yes">Your email address, or a reminder of what
this key is for.</property>
+ <property name="wrap">True</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
</object>
<packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
</packing>
</child>
+ <child>
+ <object class="GtkLabel" id="label49">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Encryption _Type</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">algorithm_combo_box</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="algorithm_combo_box">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="can_focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <items>
+ <item translatable="yes">RSA</item>
+ <item translatable="yes">DSA</item>
+ <item translatable="yes">ECDSA</item>
+ <item translatable="yes">ED25519</item>
+ </items>
+ <signal name="changed" handler="on_algo_changed"/>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label50">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">Key _Strength (bits)</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <!-- Key strength widget comes here -->
+ <placeholder/>
+ </child>
</object>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_top">24</property>
- <property name="xalign">0</property>
- <property name="max-width-chars">75</property>
- <property name="label" translatable="yes">If there is a computer you want to use this
key with, you can set up that computer to recognize your new key.</property>
- <property name="wrap">True</property>
- </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
</child>
</object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">24</property>
+ <property name="xalign">0</property>
+ <property name="max-width-chars">75</property>
+ <property name="label" translatable="yes">If there is a computer you want to use this key
with, you can set up that computer to recognize your new key.</property>
+ <property name="wrap">True</property>
+ </object>
</child>
</object>
<packing>
- <property name="expand">False</property>
+ <property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
- <child internal-child="action_area">
- <object class="GtkButtonBox" id="action_area">
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkButton" id="create_no_setup_button">
- <property name="label" translatable="yes">_Just Create Key</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="can_default">True</property>
- <property name="receives_default">False</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="create_with_setup_button">
- <property name="label" translatable="yes">_Create and Set Up</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- </packing>
- </child>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="ok_button">
+ <property name="label" translatable="yes">_Generate</property>
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
</object>
</child>
+ <action-widgets>
+ <action-widget response="cancel">cancel_button</action-widget>
+ <action-widget response="accept" default="true">ok_button</action-widget>
+ </action-widgets>
</template>
</interface>
diff --git a/ssh/source.vala b/ssh/source.vala
index bf366419..9226a371 100644
--- a/ssh/source.vala
+++ b/ssh/source.vala
@@ -229,10 +229,10 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
&& FileUtils.test(pubfile, FileTest.EXISTS)
&& check_file_for_ssh(privfile)) {
try {
- yield Key.parse_file(pubfile, (keydata) => {
+ var result = yield Key.parse_file(pubfile);
+ foreach (unowned var keydata in result.public_keys) {
key = Source.add_key_from_parsed_data(this, keydata, pubfile, false, false, privfile);
- return true;
- });
+ }
} catch (GLib.Error e) {
throw new Error.GENERAL("Couldn't read SSH file: %s (%s)".printf(pubfile, e.message));
}
@@ -280,17 +280,17 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
// Now load the authorized keys
string pubfile = authorized_keys_path();
- Key.parse_file.begin(pubfile, (keydata) => {
+ var result = yield Key.parse_file(pubfile);
+ foreach (unowned var keydata in result.public_keys) {
Source.add_key_from_parsed_data(this, keydata, pubfile, true, true, null);
- return true;
- });
+ }
// Load the "other keys" (public keys without authorization)
pubfile = other_keys_path();
- Key.parse_file.begin(pubfile, (keydata) => {
+ result = yield Key.parse_file(pubfile);
+ foreach (unowned var keydata in result.public_keys) {
Source.add_key_from_parsed_data(this, keydata, pubfile, true, false, null);
- return true;
- });
+ }
return true;
}
@@ -331,7 +331,6 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
return key;
}
-
/**
* Parse an inputstream into a list of keys and import those keys.
*/
@@ -342,17 +341,15 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
input.read_all(buffer, out bytes_read, cancellable);
string fullpath = other_keys_path();
- Source src = this;
-
- Key.parse.begin((string) buffer,
- (keydata) => {
- import_public_async.begin(keydata, fullpath, cancellable);
- return true;
- },
- (secdata) => {
- new PrivateImportOperation().import_private_async.begin(src, secdata, null,
cancellable);
- return true;
- });
+
+ var result = yield Key.parse((string) buffer, cancellable);
+ foreach (unowned var keydata in result.public_keys) {
+ yield import_public_async(keydata, fullpath, cancellable);
+ }
+ foreach (unowned var secdata in result.secret_keys) {
+ var op = new PrivateImportOperation();
+ yield op.import_private_async(this, secdata, null, cancellable);
+ }
// TODO: The list of keys imported?
return null;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]