[polari] js: Promisify async operations



commit 042d62b66e34c8e15db5c4e48ffc8e2d313b7292
Author: Florian Müllner <fmuellner gnome org>
Date:   Mon Aug 20 00:51:19 2018 +0200

    js: Promisify async operations
    
    Promises make asynchronous operations easier to manage, in particular
    when used through the async/await syntax that allows for asynchronous
    code to closely resemble synchronous one.
    
    gjs has included a Gio._promisify() helper for a while now, which
    monkey-patches methods that follow GIO's async pattern to return a
    Promise when called without a callback argument.
    
    Use that to get rid of all those GAsyncReadyCallbacks!
    
    https://gitlab.gnome.org/GNOME/polari/-/merge_requests/169

 src/accountsMonitor.js   |  26 +++--
 src/application.js       | 108 +++++++++---------
 src/chatView.js          |  14 ++-
 src/connections.js       |  53 +++++----
 src/entryArea.js         |  32 +++---
 src/initialSetup.js      |  10 +-
 src/ircParser.js         |  71 ++++++------
 src/pasteManager.js      |  82 ++++++--------
 src/serverRoomManager.js |  22 ++--
 src/telepathyClient.js   | 282 +++++++++++++++++++++++++----------------------
 src/utils.js             | 197 ++++++++++++++-------------------
 11 files changed, 429 insertions(+), 468 deletions(-)
---
diff --git a/src/accountsMonitor.js b/src/accountsMonitor.js
index 1ba238b9..e95b072f 100644
--- a/src/accountsMonitor.js
+++ b/src/accountsMonitor.js
@@ -5,6 +5,10 @@ const Signals = imports.signals;
 
 const { NetworksManager } = imports.networksManager;
 
+Gio._promisify(Tp.AccountManager.prototype, 'prepare_async', 'prepare_finish');
+Gio._promisify(Tp.Account.prototype,
+    'request_presence_async', 'request_presence_finish');
+
 var AccountsMonitor = class {
     static getDefault() {
         if (!this._singleton)
@@ -95,17 +99,17 @@ var AccountsMonitor = class {
         this.visibleAccounts.forEach(a => this._updateAccountReachable(a));
     }
 
-    _onPrepareShutdown() {
-        let presence = Tp.ConnectionPresenceType.OFFLINE;
-        this.accounts.filter(a => a.requested_presence_type !== presence).forEach(a => {
-            this._app.hold();
-            a.request_presence_async(presence, 'offline', '', (o, res) => {
-                try {
-                    a.request_presence_finish(res);
-                } catch (e) { }
-                this._app.release();
-            });
-        });
+    async _onPrepareShutdown() {
+        const presence = Tp.ConnectionPresenceType.OFFLINE;
+        const onlineAccounts =
+            this.accounts.filter(a => a.requested_presence_type !== presence);
+
+        this._app.hold();
+
+        await Promise.all(onlineAccounts.map(
+            a => a.request_presence_async(presence, 'offline', '')));
+
+        this._app.release();
     }
 
     _shouldMonitorAccount(account) {
diff --git a/src/application.js b/src/application.js
index 762c6ded..5b40ca70 100644
--- a/src/application.js
+++ b/src/application.js
@@ -15,6 +15,18 @@ const { TelepathyClient } = imports.telepathyClient;
 const { UserStatusMonitor } = imports.userTracker;
 const Utils = imports.utils;
 
+Gio._promisify(Tp.AccountRequest.prototype,
+    'create_account_async', 'create_account_finish');
+Gio._promisify(Tp.Account.prototype, 'remove_async', 'remove_finish');
+Gio._promisify(Tp.Account.prototype,
+    'request_presence_async', 'request_presence_finish');
+Gio._promisify(Tp.Account.prototype,
+    'set_enabled_async', 'set_enabled_finish');
+Gio._promisify(Tp.Account.prototype,
+    'set_nickname_async', 'set_nickname_finish');
+Gio._promisify(Tp.Account.prototype,
+    'update_parameters_vardict_async', 'update_parameters_vardict_finish');
+
 const MAX_RETRIES = 3;
 
 const IRC_SCHEMA_REGEX = /^(irc?:\/\/)([\da-z.-]+):?(\d+)?\/(?:%23)?([\w.+-]+)/i;
@@ -96,10 +108,8 @@ var Application = GObject.registerClass({
     }
 
     // Small wrapper to mark user-requested nick changes
-    setAccountNick(account, nick) {
-        account.set_nickname_async(nick, (a, res) => {
-            account.set_nickname_finish(res);
-        });
+    async setAccountNick(account, nick) {
+        await account.set_nickname_async(nick);
         this._untrackNominalNick(account);
     }
 
@@ -352,9 +362,7 @@ var Application = GObject.registerClass({
         this._accountsMonitor.connect('account-added', (am, account) => {
             // Reset nickname at startup
             let accountName = this._getTrimmedAccountName(account);
-            account.set_nickname_async(accountName, (a, res) => {
-                a.set_nickname_finish(res);
-            });
+            account.set_nickname_async(accountName);
         });
         this._accountsMonitor.connect('account-removed', (am, account) => {
             // Make sure we don't 'inject' outdated data into
@@ -448,7 +456,7 @@ var Application = GObject.registerClass({
         });
 
         let joinAction = this.lookup_action('join-room');
-        uris.forEach(uri => {
+        uris.forEach(async uri => {
             let [success, server, port, room] = this._parseURI(uri);
             if (!success)
                 return;
@@ -459,20 +467,16 @@ var Application = GObject.registerClass({
                        map[a].service === matchedId;
             });
 
+            let accountPath;
             if (matches.length) {
-                joinAction.activate(new GLib.Variant('(ssu)', [
-                    matches[0], `#${room}`, time,
-                ]));
+                accountPath = matches[0];
             } else {
-                this._createAccount(matchedId, server, port, a => {
-                    if (a) {
-                        joinAction.activate(new GLib.Variant('(ssu)', [
-                            a.get_object_path(),
-                            `#${room}`, time,
-                        ]));
-                    }
-                });
+                const account =
+                    await this._createAccount(matchedId, server, port);
+                accountPath = account.get_object_path();
             }
+
+            joinAction.activate(new GLib.Variant('(ssu)', [accountPath, `#${room}`, time]));
         });
     }
 
@@ -492,7 +496,7 @@ var Application = GObject.registerClass({
         return [success, server, port, room];
     }
 
-    _createAccount(id, server, port, callback) {
+    async _createAccount(id, server, port) {
         let params, name;
 
         if (id) {
@@ -522,14 +526,12 @@ var Application = GObject.registerClass({
         for (let prop in params)
             req.set_parameter(prop, params[prop]);
 
-        req.create_account_async((r, res) => {
-            let account = req.create_account_finish(res);
+        const account = await req.create_account_async();
 
-            Utils.clearAccountPassword(account);
-            Utils.clearIdentifyPassword(account);
+        Utils.clearAccountPassword(account);
+        Utils.clearIdentifyPassword(account);
 
-            callback(account);
-        });
+        return account;
     }
 
     _needsInitialSetup() {
@@ -602,9 +604,7 @@ var Application = GObject.registerClass({
                     return;
 
                 this._untrackNominalNick(account);
-                account.set_nickname_async(nominalNick, (a, res) => {
-                    a.set_nickname_finish(res);
-                });
+                account.set_nickname_async(nominalNick);
             });
         this._nickTrackData.set(account, { tracker, contactsChangedId });
     }
@@ -649,15 +649,15 @@ var Application = GObject.registerClass({
         let accountName = this._getTrimmedAccountName(account);
         let params = { account: new GLib.Variant('s', accountName) };
         let asv = new GLib.Variant('a{sv}', params);
-        account.update_parameters_vardict_async(asv, [], null);
+        account.update_parameters_vardict_async(asv, []);
     }
 
-    _retryWithParams(account, params) {
-        account.update_parameters_vardict_async(params, [], () => {
-            let presence = Tp.ConnectionPresenceType.AVAILABLE;
-            let msg = account.requested_status_message;
-            account.request_presence_async(presence, 'available', msg, null);
-        });
+    async _retryWithParams(account, params) {
+        await account.update_parameters_vardict_async(params, []);
+
+        const presence = Tp.ConnectionPresenceType.AVAILABLE;
+        const msg = account.requested_status_message;
+        account.request_presence_async(presence, 'available', msg);
     }
 
     _retryNickRequest(account) {
@@ -722,7 +722,7 @@ var Application = GObject.registerClass({
                 // Connection failed, keep tp from retrying over and over
                 let presence = Tp.ConnectionPresenceType.OFFLINE;
                 let msg = account.requested_status_message;
-                account.request_presence_async(presence, 'offline', msg, null);
+                account.request_presence_async(presence, 'offline', msg);
             }
         }
 
@@ -750,32 +750,26 @@ var Application = GObject.registerClass({
         action.change_state(GLib.Variant.new('b', !state.get_boolean()));
     }
 
-    _onRemoveConnection(action, parameter) {
+    async _onRemoveConnection(action, parameter) {
         let accountPath = parameter.deep_unpack();
         let account = this._accountsMonitor.lookupAccount(accountPath);
 
-        account.set_enabled_async(false, (a, res) => {
-            account.set_enabled_finish(res);
-            account.visible = false;
+        await account.set_enabled_async(false);
+        account.visible = false;
 
-            let label = _('%s removed.').format(account.display_name);
-            let n = new AppNotifications.UndoNotification(label);
-            this.notificationQueue.addNotification(n);
+        const label = _('%s removed.').format(account.display_name);
+        const n = new AppNotifications.UndoNotification(label);
+        this.notificationQueue.addNotification(n);
 
-            n.connect('closed', () => {
-                account.remove_async((o, r) => {
-                    a.remove_finish(r); // TODO: Check for errors
+        n.connect('closed', async () => {
+            await account.remove_async(); // TODO: Check for errors
 
-                    Utils.clearAccountPassword(a);
-                    Utils.clearIdentifyPassword(a);
-                });
-            });
-            n.connect('undo', () => {
-                account.set_enabled_async(true, (o, r) => {
-                    account.set_enabled_finish(r);
-                    account.visible = true;
-                });
-            });
+            Utils.clearAccountPassword(account);
+            Utils.clearIdentifyPassword(account);
+        });
+        n.connect('undo', async () => {
+            await account.set_enabled_async(true);
+            account.visible = true;
         });
     }
 
diff --git a/src/chatView.js b/src/chatView.js
index a7d2406a..34f12b5c 100644
--- a/src/chatView.js
+++ b/src/chatView.js
@@ -11,6 +11,9 @@ const { UserStatusMonitor } = imports.userTracker;
 const { URLPreview } = imports.urlPreview;
 const Utils = imports.utils;
 
+Gio._promisify(Tpl.LogWalker.prototype,
+    'get_events_async', 'get_events_finish');
+
 var MAX_NICK_CHARS = 8;
 const IGNORE_STATUS_TIME = 5;
 
@@ -359,8 +362,7 @@ var ChatView = GObject.registerClass({
             Tpl.EventTypeMask.TEXT, null);
 
         this._fetchingBacklog = true;
-        this._logWalker.get_events_async(NUM_INITIAL_LOG_EVENTS,
-            this._onLogEventsReady.bind(this));
+        this._getLogEvents(NUM_INITIAL_LOG_EVENTS);
 
         this._autoscroll = true;
 
@@ -549,11 +551,12 @@ var ChatView = GObject.registerClass({
         this._logWalker = null;
     }
 
-    _onLogEventsReady(lw, res) {
+    async _getLogEvents(num) {
+        const [events] = await this._logWalker.get_events_async(num);
+
         this._hideLoadingIndicator();
         this._fetchingBacklog = false;
 
-        let [, events] = lw.get_events_finish(res);
         let messages = events.map(e => this._createMessage(e));
         this._pendingLogs = messages.concat(this._pendingLogs);
         this._insertPendingLogs();
@@ -744,8 +747,7 @@ var ChatView = GObject.registerClass({
         this._fetchingBacklog = true;
         this._showLoadingIndicator();
         this._backlogTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
-            this._logWalker.get_events_async(NUM_LOG_EVENTS,
-                this._onLogEventsReady.bind(this));
+            this._getLogEvents(NUM_LOG_EVENTS);
             this._backlogTimeoutId = 0;
             return GLib.SOURCE_REMOVE;
         });
diff --git a/src/connections.js b/src/connections.js
index 7e2264d4..e42a5f78 100644
--- a/src/connections.js
+++ b/src/connections.js
@@ -1,11 +1,18 @@
 /* exported ConnectionProperties ConnectionDetails ConnectionsList */
 
-const { GLib, GObject, Gtk, TelepathyGLib: Tp } = imports.gi;
+const { Gio, GLib, GObject, Gtk, TelepathyGLib: Tp } = imports.gi;
 
 const { AccountsMonitor } = imports.accountsMonitor;
 const { NetworksManager } = imports.networksManager;
 const Utils = imports.utils;
 
+Gio._promisify(Tp.Account.prototype,
+    'set_display_name_async', 'set_display_name_finish');
+Gio._promisify(Tp.Account.prototype,
+    'update_parameters_vardict_async', 'update_parameters_vardict_finish');
+Gio._promisify(Tp.AccountRequest.prototype,
+    'create_account_async', 'create_account_finish');
+
 const DEFAULT_PORT = 6667;
 const DEFAULT_SSL_PORT = 6697;
 
@@ -196,7 +203,7 @@ var ConnectionsList = GObject.registerClass({
         });
     }
 
-    _onRowActivated(list, row) {
+    async _onRowActivated(list, row) {
         let name = this._networksManager.getNetworkName(row.id);
         let req = new Tp.AccountRequest({
             account_manager: Tp.AccountManager.dup(),
@@ -212,17 +219,14 @@ var ConnectionsList = GObject.registerClass({
         for (let prop in details)
             req.set_parameter(prop, details[prop]);
 
-        req.create_account_async((r, res) => {
-            let account = req.create_account_finish(res);
-            if (!account) // TODO: Handle errors
-                return;
+        this.emit('account-selected');
 
-            Utils.clearAccountPassword(account);
-            Utils.clearIdentifyPassword(account);
+        const account = await req.create_account_async();
 
-            this.emit('account-created', account);
-        });
-        this.emit('account-selected');
+        Utils.clearAccountPassword(account);
+        Utils.clearIdentifyPassword(account);
+
+        this.emit('account-created', account);
     }
 
     _setAccountRowSensitive(account, sensitive) {
@@ -432,7 +436,7 @@ var ConnectionDetails = GObject.registerClass({
             this._createAccount();
     }
 
-    _createAccount() {
+    async _createAccount() {
         let params = this._getParams();
         let accountManager = Tp.AccountManager.dup();
         let req = new Tp.AccountRequest({
@@ -448,32 +452,25 @@ var ConnectionDetails = GObject.registerClass({
         for (let prop in details)
             req.set_parameter(prop, details[prop]);
 
-        req.create_account_async((r, res) => {
-            let account = req.create_account_finish(res);
-            if (!account) // TODO: Handle errors
-                return;
+        const account = await req.create_account_async();
 
-            Utils.clearAccountPassword(account);
-            Utils.clearIdentifyPassword(account);
+        Utils.clearAccountPassword(account);
+        Utils.clearIdentifyPassword(account);
 
-            this.emit('account-created', account);
-        });
+        this.emit('account-created', account);
     }
 
-    _updateAccount() {
+    async _updateAccount() {
         let params = this._getParams();
         let account = this._account;
         let oldDetails = account.dup_parameters_vardict().deep_unpack();
         let [details, removed] = this._detailsFromParams(params, oldDetails);
         let vardict = GLib.Variant.new('a{sv}', details);
 
-        account.update_parameters_vardict_async(vardict, removed, (a, res) => {
-            a.update_parameters_vardict_finish(res); // TODO: Check for errors
-        });
-
-        account.set_display_name_async(params.name, (a, res) => {
-            a.set_display_name_finish(res); // TODO: Check for errors
-        });
+        await Promise.all([
+            account.update_parameters_vardict_async(vardict, removed),
+            account.set_display_name_async(params.name),
+        ]);
     }
 
     _detailsFromParams(params, oldDetails) {
diff --git a/src/entryArea.js b/src/entryArea.js
index a2ddefa7..6e7a6583 100644
--- a/src/entryArea.js
+++ b/src/entryArea.js
@@ -12,6 +12,8 @@ const { TabCompletion } = imports.tabCompletion;
 const MAX_NICK_UPDATE_TIME = 5; /* s */
 const MAX_LINES = 5;
 
+Gio._promisify(Gio._LocalFilePrototype,
+    'query_info_async', 'query_info_finish');
 
 var ChatEntry = GObject.registerClass({
     Implements: [DropTargetIface],
@@ -383,18 +385,13 @@ var EntryArea = GObject.registerClass({
         this._setPasteContent(pixbuf);
     }
 
-    pasteFile(file) {
-        file.query_info_async(
-            Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
-            Gio.FileQueryInfoFlags.NONE,
-            GLib.PRIORITY_DEFAULT, null,
-            this._onFileInfoReady.bind(this));
-    }
-
-    _onFileInfoReady(file, res) {
+    async pasteFile(file) {
         let fileInfo = null;
         try {
-            fileInfo = file.query_info_finish(res);
+            fileInfo = await file.query_info_async(
+                Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+                Gio.FileQueryInfoFlags.NONE,
+                GLib.PRIORITY_DEFAULT, null);
         } catch (e) {
             return;
         }
@@ -407,7 +404,7 @@ var EntryArea = GObject.registerClass({
         this._setPasteContent(file);
     }
 
-    _onPasteClicked() {
+    async _onPasteClicked() {
         let title;
         let nick = this._room.channel.connection.self_contact.alias;
         if (this._room.type === Tp.HandleType.ROOM)
@@ -415,24 +412,23 @@ var EntryArea = GObject.registerClass({
             title = _('%s in #%s').format(nick, this._room.display_name);
         else
             title = _('Paste from %s').format(nick);
+        this._confirmLabel.hide();
 
         this._confirmLabel.hide();
         this._uploadSpinner.start();
 
         let app = Gio.Application.get_default();
         try {
-            app.pasteManager.pasteContent(this._pasteContent, title, url => {
-                // TODO: handle errors
-                this._uploadSpinner.stop();
-                this._setPasteContent(null);
-                if (url)
-                    this._chatEntry.emit('insert-at-cursor', url);
-            });
+            const url =
+                await app.pasteManager.pasteContent(this._pasteContent, title);
+            this._setPasteContent(null);
+            this._chatEntry.emit('insert-at-cursor', url);
         } catch (e) {
             let type = typeof this._pasteContent;
             if (type === 'object')
                 type = this._pasteContent.toString();
             debug(`Failed to paste content of type ${type}`);
+        } finally {
             this._uploadSpinner.stop();
         }
     }
diff --git a/src/initialSetup.js b/src/initialSetup.js
index 9e63a4b3..82831a00 100644
--- a/src/initialSetup.js
+++ b/src/initialSetup.js
@@ -1,9 +1,11 @@
 /* exported InitialSetupWindow */
 
-const { Gio, GLib, GObject, Gtk } = imports.gi;
+const { Gio, GLib, GObject, Gtk, TelepathyGLib: Tp } = imports.gi;
 
 const Utils = imports.utils;
 
+Gio._promisify(Tp.Account.prototype, 'remove_async', 'remove_finish');
+
 const SetupPage = {
     CONNECTION: 0,
     ROOM: 1,
@@ -94,13 +96,11 @@ var InitialSetupWindow = GObject.registerClass({
         this._updateNextSensitivity();
     }
 
-    _unsetAccount() {
+    async _unsetAccount() {
         if (!this._currentAccount)
             return;
 
-        this._currentAccount.remove_async((a, res) => {
-            a.remove_finish(res);
-        });
+        await this._currentAccount.remove_async();
         this._currentAccount = null;
     }
 
diff --git a/src/ircParser.js b/src/ircParser.js
index 71c9e272..75909076 100644
--- a/src/ircParser.js
+++ b/src/ircParser.js
@@ -6,6 +6,9 @@ const Signals = imports.signals;
 const AppNotifications = imports.appNotifications;
 const { RoomManager } = imports.roomManager;
 const Utils = imports.utils;
+
+Gio._promisify(Tp.Account.prototype,
+    'request_presence_async', 'request_presence_finish');
 Gio._promisify(Tp.Connection.prototype,
     'dup_contact_by_id_async', 'dup_contact_by_id_finish');
 Gio._promisify(Tp.Contact.prototype,
@@ -100,17 +103,14 @@ var IrcParser = class {
                 retval = false;
                 break;
             }
-            this._room.channel.connection.dup_contact_by_id_async(nick, [],
-                (c, res) => {
-                    let contact;
-                    try {
-                        contact = c.dup_contact_by_id_finish(res);
-                    } catch (e) {
-                        logError(e, `Failed to get contact for ${nick}`);
-                        return;
-                    }
-                    this._room.add_member(contact);
-                });
+            try {
+                let connection = this._room.channel.connection;
+                let contact = await connection.dup_contact_by_id_async(nick);
+                this._room.add_member(contact);
+            } catch (e) {
+                logError(e, `Failed to get contact for ${nick}`);
+                retval = false;
+            }
             break;
         }
         case 'J':
@@ -141,17 +141,14 @@ var IrcParser = class {
                 retval = false;
                 break;
             }
-            this._room.channel.connection.dup_contact_by_id_async(nick, [],
-                (c, res) => {
-                    let contact;
-                    try {
-                        contact = c.dup_contact_by_id_finish(res);
-                    } catch (e) {
-                        logError(e, `Failed to get contact for ${nick}`);
-                        return;
-                    }
-                    this._room.remove_member(contact);
-                });
+            try {
+                let connection = this._room.channel.connection;
+                let contact = await connection.dup_contact_by_id_async(nick);
+                this._room.remove_member(contact);
+            } catch (e) {
+                logError(e, `Failed to get contact for ${nick}`);
+                retval = false;
+            }
             break;
         }
         case 'ME': {
@@ -247,14 +244,12 @@ var IrcParser = class {
         case 'QUIT': {
             let presence = Tp.ConnectionPresenceType.OFFLINE;
             let message = stripCommand(text);
-            this._room.account.request_presence_async(presence, 'offline', message,
-                (a, res) => {
-                    try {
-                        a.request_presence_finish(res);
-                    } catch (e) {
-                        logError(e, 'Failed to disconnect');
-                    }
-                });
+            try {
+                await this._room.account.request_presence_async(presence, 'offline', message);
+            } catch (e) {
+                logError(e, 'Failed to disconnect');
+                retval = false;
+            }
             break;
         }
         case 'SAY': {
@@ -318,15 +313,13 @@ var IrcParser = class {
         this._sendMessage(message);
     }
 
-    _sendMessage(message) {
-        this._room.channel.send_message_async(message, 0, (c, res) => {
-            try {
-                c.send_message_finish(res);
-            } catch (e) {
-                // TODO: propagate to user
-                logError(e, 'Failed to send message');
-            }
-        });
+    async _sendMessage(message) {
+        try {
+            await this._room.channel.send_message_async(message, 0);
+        } catch (e) {
+            // TODO: propagate to user
+            logError(e, 'Failed to send message');
+        }
     }
 };
 Signals.addSignalMethods(IrcParser.prototype);
diff --git a/src/pasteManager.js b/src/pasteManager.js
index dd2493d0..7ddf82ae 100644
--- a/src/pasteManager.js
+++ b/src/pasteManager.js
@@ -4,6 +4,14 @@ const { Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk, Polari } = imports.gi;
 
 const Utils = imports.utils;
 
+Gio._promisify(Gio._LocalFilePrototype,
+    'load_contents_async', 'load_contents_finish');
+Gio._promisify(Gio._LocalFilePrototype,
+    'query_info_async', 'query_info_finish');
+Gio._promisify(Gio._LocalFilePrototype, 'read_async', 'read_finish');
+Gio._promisify(GdkPixbuf.Pixbuf.prototype,
+    'new_from_stream_async', 'new_from_stream_finish');
+
 const DndTargetType = {
     URI_LIST: 1,
 
@@ -22,50 +30,36 @@ function _getTargetForContentType(contentType) {
 
 
 var PasteManager = class {
-    pasteContent(content, title, callback) {
+    pasteContent(content, title) {
         if (typeof content === 'string')
-            Utils.gpaste(content, title, callback);
+            return Utils.gpaste(content, title);
         else if (content instanceof GdkPixbuf.Pixbuf)
-            Utils.imgurPaste(content, title, callback);
+            return Utils.imgurPaste(content, title);
         else if (content.query_info_async)
-            this._pasteFile(content, title, callback);
+            return this._pasteFile(content, title);
         else
             throw new Error('Unhandled content type');
     }
 
-    _pasteFile(file, title, callback) {
-        file.query_info_async(
+    async _pasteFile(file, title) {
+        const fileInfo = await file.query_info_async(
             Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
             Gio.FileQueryInfoFlags.NONE,
-            GLib.PRIORITY_DEFAULT, null, (f, res) => {
-                try {
-                    let fileInfo = file.query_info_finish(res);
-                    this._handleFilePaste(file, fileInfo, title, callback);
-                } catch (e) {
-                    callback(null);
-                }
-            });
-    }
+            GLib.PRIORITY_DEFAULT, null);
 
-    _handleFilePaste(file, fileInfo, title, callback) {
         let contentType = fileInfo.get_content_type();
         let targetType = _getTargetForContentType(contentType);
 
         if (targetType === DndTargetType.TEXT) {
-            file.load_contents_async(null, (f, res) => {
-                let [, contents] = f.load_contents_finish(res);
-                Utils.gpaste(contents.toString(), title, callback);
-            });
+            const [, contents] = await file.load_contents_async(null);
+            return Utils.gpaste(contents.toString(), title);
         } else if (targetType === DndTargetType.IMAGE) {
-            file.read_async(GLib.PRIORITY_DEFAULT, null, (f, res) => {
-                let stream = f.read_finish(res);
-                GdkPixbuf.Pixbuf.new_from_stream_async(stream, null, (s, r) => {
-                    let pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(r);
-                    Utils.imgurPaste(pixbuf, title, callback);
-                });
-            });
+            const stream = await file.read_async(GLib.PRIORITY_DEFAULT, null);
+            const pixbuf =
+                await GdkPixbuf.Pixbuf.new_from_stream_async(stream, null);
+            return Utils.imgurPaste(pixbuf, title);
         } else {
-            callback(null);
+            throw new Error('Unhandled content type');
         }
     }
 };
@@ -149,7 +143,7 @@ var DropTargetIface = GObject.registerClass({
     }
 
 
-    _onDragDataReceived(_widget, context, _x, _y, data, info, time) {
+    async _onDragDataReceived(_widget, context, _x, _y, data, info, time) {
         if (info === DndTargetType.URI_LIST) {
             let uris = data.get_uris();
             if (!uris) {
@@ -158,14 +152,19 @@ var DropTargetIface = GObject.registerClass({
             }
 
             // TODO: handle multiple files ...
-            let file = Gio.File.new_for_uri(uris[0]);
+            const file = Gio.File.new_for_uri(uris[0]);
+            const attr = Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
+            const flags = Gio.FileQueryInfoFlags.NONE;
+            const priority = GLib.PRIORITY_DEFAULT;
             try {
-                this._lookupFileInfo(file, targetType => {
-                    let canHandle = targetType !== 0;
-                    if (canHandle)
-                        this.emit('file-dropped', file);
-                    Gtk.drag_finish(context, canHandle, false, time);
-                });
+                const fileInfo =
+                    await file.query_info_async(attr, flags, priority, null);
+                const contentType = fileInfo.get_content_type();
+                const targetType = _getTargetForContentType(contentType);
+                const canHandle = targetType !== 0;
+                if (canHandle)
+                    this.emit('file-dropped', file);
+                Gtk.drag_finish(context, canHandle, false, time);
             } catch (e) {
                 Gtk.drag_finish(context, false, false, time);
             }
@@ -184,15 +183,4 @@ var DropTargetIface = GObject.registerClass({
             Gtk.drag_finish(context, success, false, time);
         }
     }
-
-    _lookupFileInfo(file, callback) {
-        let attr = Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
-        let flags = Gio.FileQueryInfoFlags.NONE;
-        let priority = GLib.PRIORITY_DEFAULT;
-        file.query_info_async(attr, flags, priority, null, (f, res) => {
-            let fileInfo = file.query_info_finish(res);
-            let contentType = fileInfo.get_content_type();
-            callback(_getTargetForContentType(contentType));
-        });
-    }
 });
diff --git a/src/serverRoomManager.js b/src/serverRoomManager.js
index 25a3ea1b..315ef3e7 100644
--- a/src/serverRoomManager.js
+++ b/src/serverRoomManager.js
@@ -1,12 +1,14 @@
 /* exported ServerRoomManager ServerRoomList */
 
-const { Gdk, GLib, GObject, Gtk, TelepathyGLib: Tp } = imports.gi;
+const { Gdk, Gio, GLib, GObject, Gtk, TelepathyGLib: Tp } = imports.gi;
 const Signals = imports.signals;
 
 const { AccountsMonitor } = imports.accountsMonitor;
 const { RoomManager } = imports.roomManager;
 const Utils = imports.utils;
 
+Gio._promisify(Tp.RoomList.prototype, 'init_async', 'init_finish');
+
 const MS_PER_IDLE = 10; // max time spend in idle
 const MS_PER_FILTER_IDLE = 5; // max time spend in idle while filtering
 
@@ -46,7 +48,7 @@ var ServerRoomManager = class {
         return roomList.list.listing;
     }
 
-    _onAccountStatusChanged(mon, account) {
+    async _onAccountStatusChanged(mon, account) {
         if (account.connection_status === Tp.ConnectionStatus.CONNECTING)
             this.emit('loading-changed', account);
 
@@ -57,18 +59,16 @@ var ServerRoomManager = class {
             return;
 
         let roomList = new Tp.RoomList({ account });
-        roomList.init_async(GLib.PRIORITY_DEFAULT, null, (o, res) => {
-            try {
-                roomList.init_finish(res);
-            } catch (e) {
-                this._roomLists.delete(account);
-                return;
-            }
-            roomList.start();
-        });
         roomList.connect('got-room', this._onGotRoom.bind(this));
         roomList.connect('notify::listing', this._onListingChanged.bind(this));
         this._roomLists.set(account, { list: roomList, rooms: [] });
+
+        try {
+            await roomList.init_async(GLib.PRIORITY_DEFAULT, null);
+            roomList.start();
+        } catch (e) {
+            this._roomLists.delete(account);
+        }
     }
 
     _onAccountRemoved(mon, account) {
diff --git a/src/telepathyClient.js b/src/telepathyClient.js
index 180c8b27..751974f7 100644
--- a/src/telepathyClient.js
+++ b/src/telepathyClient.js
@@ -7,6 +7,22 @@ const { RoomManager } = imports.roomManager;
 const { UserStatusMonitor } = imports.userTracker;
 const Utils = imports.utils;
 
+Gio._promisify(Gio.DBusConnection.prototype, 'call', 'call_finish');
+Gio._promisify(Polari.Room.prototype,
+    'send_identify_message_async', 'send_identify_message_finish');
+Gio._promisify(Tp.Account.prototype, 'reconnect_async', 'reconnect_finish');
+Gio._promisify(Tp.Account.prototype,
+    'request_presence_async', 'request_presence_finish');
+Gio._promisify(Tp.Account.prototype,
+    'set_enabled_async', 'set_enabled_finish');
+Gio._promisify(Tp.Account.prototype,
+    'update_parameters_vardict_async', 'update_parameters_vardict_finish');
+Gio._promisify(Tp.AccountChannelRequest.prototype,
+    'ensure_and_observe_channel_async', 'ensure_and_observe_channel_finish');
+Gio._promisify(Tp.Channel.prototype, 'leave_async', 'leave_finish');
+Gio._promisify(Tp.TextChannel.prototype,
+    'send_message_async', 'send_message_finish');
+
 const SHELL_CLIENT_PREFIX = 'org.freedesktop.Telepathy.Client.GnomeShell';
 
 const SASLAuthenticationIface = '<node> \
@@ -54,19 +70,16 @@ class SASLAuthHandler {
             this._onProxyReady.bind(this));
     }
 
-    _onProxyReady() {
+    async _onProxyReady() {
         this._proxy.connectSignal('SASLStatusChanged',
             this._onSASLStatusChanged.bind(this));
 
         let account = this._channel.connection.get_account();
-        Utils.lookupAccountPassword(account, this._onPasswordReady.bind(this));
-    }
-
-    _onPasswordReady(password) {
-        if (password) {
+        try {
+            const password = await Utils.lookupAccountPassword(account);
             this._proxy.StartMechanismWithDataRemote(
                 'X-TELEPATHY-PASSWORD', password);
-        } else {
+        } catch (e) {
             this._proxy.AbortSASLRemote(
                 SASLAbortReason.USER_ABORT,
                 'Password not available',
@@ -97,15 +110,13 @@ class SASLAuthHandler {
         }
     }
 
-    _resetPrompt() {
+    async _resetPrompt() {
         let account = this._channel.connection.get_account();
         let prompt = new GLib.Variant('b', false);
         let params = new GLib.Variant('a{sv}', { 'password-prompt': prompt });
-        account.update_parameters_vardict_async(params, [], (a, res) => {
-            a.update_parameters_vardict_finish(res);
-            account.request_presence_async(Tp.ConnectionPresenceType.AVAILABLE,
-                'available', '', null);
-        });
+        await account.update_parameters_vardict_async(params, []);
+        await account.request_presence_async(Tp.ConnectionPresenceType.AVAILABLE,
+            'available', '', null);
     }
 }
 
@@ -143,7 +154,7 @@ class TelepathyClient extends Tp.BaseClient {
         this._monitorShellClient();
     }
 
-    _monitorShellClient() {
+    async _monitorShellClient() {
         // Track whether gnome-shell's built-in chat client is
         // running; unfortunately it uses :uniquify-name, so
         // we cannot simply use Gio.watch_bus_name()
@@ -160,28 +171,25 @@ class TelepathyClient extends Tp.BaseClient {
                 this._shellHandlesPrivateChats = newOwner !== '';
             });
 
-        conn.call(
-            'org.freedesktop.DBus',
-            '/org/freedesktop/DBus',
-            'org.freedesktop.DBus',
-            'ListNames',
-            null, /* params */
-            new GLib.VariantType('(as)'),
-            Gio.DBusCallFlags.NONE,
-            -1,
-            null, /* cancellable */
-            (_o, res) => {
-                let names = [];
-
-                try {
-                    [names] = conn.call_finish(res).deep_unpack();
-                } catch (e) {
-                    debug(`Failed to list bus names: ${e}`);
-                }
-
-                this._shellHandlesPrivateChats =
-                    names.some(n => n.startsWith(SHELL_CLIENT_PREFIX));
-            });
+        let names = [];
+        try {
+            const result = await conn.call(
+                'org.freedesktop.DBus',
+                '/org/freedesktop/DBus',
+                'org.freedesktop.DBus',
+                'ListNames',
+                null, /* params */
+                new GLib.VariantType('(as)'),
+                Gio.DBusCallFlags.NONE,
+                -1,
+                null);
+            [names] = result.deep_unpack();
+        } catch (e) {
+            debug(`Failed to list bus names: ${e}`);
+        }
+
+        this._shellHandlesPrivateChats =
+            names.some(n => n.startsWith(SHELL_CLIENT_PREFIX));
     }
 
     _onPrepared() {
@@ -271,23 +279,23 @@ class TelepathyClient extends Tp.BaseClient {
         this._setAccountPresence(account, presence);
     }
 
-    _onAccountStatusChanged(mon, account) {
+    async _onAccountStatusChanged(mon, account) {
         if (account.connection_status !== Tp.ConnectionStatus.CONNECTED)
             return;
 
-        Utils.lookupIdentifyPassword(account, password => {
-            if (password)
-                this._sendIdentify(account, password);
-            else
-                this._connectRooms(account);
-        });
+        try {
+            const password = await Utils.lookupIdentifyPassword(account);
+            this._sendIdentify(account, password);
+        } catch (e) {
+            this._connectRooms(account);
+        }
     }
 
     _connectAccount(account) {
         this._setAccountPresence(account, Tp.ConnectionPresenceType.AVAILABLE);
     }
 
-    _setAccountPresence(account, presence) {
+    async _setAccountPresence(account, presence) {
         if (!account.enabled)
             return;
 
@@ -298,13 +306,11 @@ class TelepathyClient extends Tp.BaseClient {
         let accountName = account.display_name;
 
         debug(`Setting presence of account "${accountName}" to ${status}`);
-        account.request_presence_async(presence, status, msg, (o, res) => {
-            try {
-                account.request_presence_finish(res);
-            } catch (e) {
-                log(`Connection failed: ${e.message}`);
-            }
-        });
+        try {
+            await account.request_presence_async(presence, status, msg);
+        } catch (e) {
+            log(`Connection failed: ${e.message}`);
+        }
     }
 
     _connectRooms(account) {
@@ -314,13 +320,16 @@ class TelepathyClient extends Tp.BaseClient {
         });
     }
 
-    _connectRoom(room) {
-        this._requestChannel(room.account, room.type, room.channel_name, null);
+    async _connectRoom(room) {
+        try {
+            await this._requestChannel(
+                room.account, room.type, room.channel_name, null);
+        } catch (e) {}
     }
 
-    _requestChannel(account, targetType, targetId, callback) {
+    async _requestChannel(account, targetType, targetId) {
         if (!account || !account.enabled)
-            return;
+            return null;
 
         let roomId = Polari.create_room_id(account,  targetId, targetType);
 
@@ -336,28 +345,28 @@ class TelepathyClient extends Tp.BaseClient {
         req.set_target_id(targetType, targetId);
         req.set_delegate_to_preferred_handler(true);
 
-        let preferredHandler = `${Tp.CLIENT_BUS_NAME_BASE}Polari`;
-        req.ensure_and_observe_channel_async(preferredHandler, cancellable,
-            (o, res) => {
-                let channel = null;
-                let room = this._roomManager.lookupRoom(roomId);
-                try {
-                    channel = req.ensure_and_observe_channel_finish(res);
-                    room.channel_error = '';
-                } catch (e) {
-                    debug(`Failed to ensure channel: ${e.message}`);
-
-                    if (room)
-                        room.channel_error = Tp.error_get_dbus_name(e.code);
-                }
-
-                if (callback)
-                    callback(channel);
-                this._pendingRequests.delete(roomId);
-            });
+        const room = this._roomManager.lookupRoom(roomId);
+        const preferredHandler = `${Tp.CLIENT_BUS_NAME_BASE}Polari`;
+        let channel = null;
+        try {
+            channel = await req.ensure_and_observe_channel_async(
+                preferredHandler, cancellable);
+            room.channel_error = '';
+        } catch (e) {
+            debug(`Failed to ensure channel: ${e.message}`);
+
+            if (room)
+                room.channel_error = Tp.error_get_dbus_name(e.code);
+
+            throw e;
+        } finally {
+            this._pendingRequests.delete(roomId);
+        }
+
+        return channel;
     }
 
-    _sendIdentify(account, password) {
+    async _sendIdentify(account, password) {
         let { settings } = account;
 
         let params = account.dup_parameters_vardict().deep_unpack();
@@ -366,41 +375,41 @@ class TelepathyClient extends Tp.BaseClient {
         let alwaysSendUsername = settings.get_boolean('identify-username-supported');
         let contactName = settings.get_string('identify-botname');
         let command = settings.get_string('identify-command');
-        this._requestChannel(account, Tp.HandleType.CONTACT, contactName,
-            channel => {
-                if (!channel)
-                    return;
 
-                let room = this._roomManager.lookupRoomByChannel(channel);
-                let activeNick = room.channel.connection.self_contact.alias;
-                // Omit username parameter when it matches the default, to
-                // support NickServ bots that don't support the parameter at all
-                if (!alwaysSendUsername && activeNick === username)
-                    username = null;
-                room.send_identify_message_async(command, username, password, (r, res) => {
-                    try {
-                        r.send_identify_message_finish(res);
-                    } catch (e) {
-                        log(`Failed to send identify message: ${e.message}`);
-                    }
-                    this._connectRooms(account);
-                });
-            });
+        let channel = null;
+        try {
+            channel = await this._requestChannel(
+                account, Tp.HandleType.CONTACT, contactName);
+        } catch (e) {
+            return;
+        }
+
+        const room = this._roomManager.lookupRoomByChannel(channel);
+        const activeNick = room.channel.connection.self_contact.alias;
+        // Omit username parameter when it matches the default, to
+        // support NickServ bots that don't support the parameter at all
+        if (!alwaysSendUsername && activeNick === username)
+            username = null;
+
+        try {
+            await room.send_identify_message_async(command, username, password);
+        } catch (e) {
+            log(`Failed to send identify message: ${e.message}`);
+        }
+        this._connectRooms(account);
     }
 
-    _sendMessage(channel, message) {
+    async _sendMessage(channel, message) {
         if (!message || !channel)
             return;
 
         let type = Tp.ChannelTextMessageType.NORMAL;
-        channel.send_message_async(Tp.ClientMessage.new_text(type, message), 0,
-            (c, res) => {
-                try {
-                    c.send_message_finish(res);
-                } catch (e) {
-                    log(`Failed to send message: ${e.message}`);
-                }
-            });
+        try {
+            await channel.send_message_async(
+                Tp.ClientMessage.new_text(type, message), 0);
+        } catch (e) {
+            log(`Failed to send message: ${e.message}`);
+        }
     }
 
     _onConnectAccountActivated(action, parameter) {
@@ -409,50 +418,57 @@ class TelepathyClient extends Tp.BaseClient {
         if (account.enabled)
             this._connectAccount(account);
         else
-            account.set_enabled_async(true, () => {});
+            account.set_enabled_async(true);
     }
 
-    _onDisconnectAccountActivated(action, parameter) {
+    async _onDisconnectAccountActivated(action, parameter) {
         let accountPath = parameter.deep_unpack();
         let account = this._accountsMonitor.lookupAccount(accountPath);
-        account.set_enabled_async(false, () => {
-            this._setAccountPresence(account, Tp.ConnectionPresenceType.OFFLINE);
-        });
+        await account.set_enabled_async(false);
+        this._setAccountPresence(account, Tp.ConnectionPresenceType.OFFLINE);
     }
 
     _onReconnectAccountActivated(action, parameter) {
         let accountPath = parameter.deep_unpack();
         let account = this._accountsMonitor.lookupAccount(accountPath);
-        account.reconnect_async((a, res) => a.reconnect_finish(res));
+        account.reconnect_async();
     }
 
-    _onAuthenticateAccountActivated(action, parameter) {
+    async _onAuthenticateAccountActivated(action, parameter) {
         let [accountPath, password] = parameter.deep_unpack();
         let account = this._accountsMonitor.lookupAccount(accountPath);
 
         let prompt = new GLib.Variant('b', password.length > 0);
         let params = GLib.Variant.new('a{sv}', { 'password-prompt': prompt });
-        account.update_parameters_vardict_async(params, [], (a, res) => {
-            a.update_parameters_vardict_finish(res);
-            Utils.storeAccountPassword(a, password, () => {
-                a.reconnect_async(null);
-            });
-        });
+        await account.update_parameters_vardict_async(params, []);
+        await Utils.storeAccountPassword(account, password);
+        await account.reconnect_async();
     }
 
-    _onQueryActivated(action, parameter) {
+    async _onQueryActivated(action, parameter) {
         let [accountPath, channelName, message, time_] = parameter.deep_unpack();
         let account = this._accountsMonitor.lookupAccount(accountPath);
 
         if (!account || !account.enabled)
             return;
 
-        this._requestChannel(account, Tp.HandleType.CONTACT, channelName, c => {
-            this._sendMessage(c, message);
-        });
+        try {
+            let channel = await this._requestChannel(
+                account, Tp.HandleType.CONTACT, channelName);
+
+            if (!message)
+                return;
+
+            let type = Tp.ChannelTextMessageType.NORMAL;
+            let tpMessage = Tp.ClientMessage.new_text(type, message);
+            await channel.send_message_async(tpMessage, 0);
+        } catch (e) {
+            if (message)
+                log(`Failed to send message: ${e.message}`);
+        }
     }
 
-    _onLeaveActivated(action, parameter) {
+    async _onLeaveActivated(action, parameter) {
         let [id, message] = parameter.deep_unpack();
 
         let request = this._pendingRequests.get(id);
@@ -475,16 +491,14 @@ class TelepathyClient extends Tp.BaseClient {
 
         let reason = Tp.ChannelGroupChangeReason.NONE;
         message = message || _('Good Bye');
-        room.channel.leave_async(reason, message, (c, res) => {
-            try {
-                c.leave_finish(res);
-            } catch (e) {
-                log(`Failed to leave channel: ${e.message}`);
-            }
-        });
+        try {
+            await room.channel.leave_async(reason, message);
+        } catch (e) {
+            log(`Failed to leave channel: ${e.message}`);
+        }
     }
 
-    _onSaveIdentifyPasswordActivated(action, parameter) {
+    async _onSaveIdentifyPasswordActivated(action, parameter) {
         let accountPath = parameter.deep_unpack();
         let account = this._accountsMonitor.lookupAccount(accountPath);
         if (!account)
@@ -494,12 +508,12 @@ class TelepathyClient extends Tp.BaseClient {
         if (!data)
             return;
 
-        Utils.storeIdentifyPassword(account, data.password, res => {
-            if (res)
-                this._saveIdentifySettings(account, data);
-
+        try {
+            await Utils.storeIdentifyPassword(account, data.password);
+            this._saveIdentifySettings(account, data);
+        } finally {
             this._pendingBotPasswords.delete(account.object_path);
-        });
+        }
     }
 
     _saveIdentifySettings(account, data) {
diff --git a/src/utils.js b/src/utils.js
index 90c7880c..1d197134 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -31,6 +31,10 @@ const { Gdk, Gio, GLib, Gtk, Secret, Soup, TelepathyGLib: Tp }  = imports.gi;
 
 const AppNotifications = imports.appNotifications;
 
+Gio._promisify(Secret, 'password_store', 'password_store_finish');
+Gio._promisify(Secret, 'password_lookup', 'password_lookup_finish');
+Gio._promisify(Secret, 'password_clear', 'password_clear_finish');
+
 const SECRET_SCHEMA_ACCOUNT = new Secret.Schema(
     'org.gnome.Polari.Account',
     Secret.SchemaFlags.NONE,
@@ -133,51 +137,48 @@ function getTpEventTime() {
     return Tp.user_action_time_from_x11(time);
 }
 
-function storeAccountPassword(account, password, callback) {
+function storeAccountPassword(account, password) {
     let label = _('Polari server password for %s').format(account.display_name);
-    _storePassword(SECRET_SCHEMA_ACCOUNT, label, account, password, callback);
+    _storePassword(SECRET_SCHEMA_ACCOUNT, label, account, password);
 }
 
-function storeIdentifyPassword(account, password, callback) {
+function storeIdentifyPassword(account, password) {
     let label = _('Polari NickServ password for %s').format(account.display_name);
-    _storePassword(SECRET_SCHEMA_IDENTIFY, label, account, password, callback);
+    _storePassword(SECRET_SCHEMA_IDENTIFY, label, account, password);
 }
 
-function _storePassword(schema, label, account, password, callback) {
+async function _storePassword(schema, label, account, password) {
     let attr = { 'account-id': account.get_path_suffix() };
     let coll = Secret.COLLECTION_DEFAULT;
-    Secret.password_store(schema, attr, coll, label, password, null, (o, res) => {
-        try {
-            let success = Secret.password_store_finish(res);
-            callback(success);
-        } catch (e) {
-            let name = account.display_name;
-            log(`Failed to store password for account ${name}: ${e.message}`);
-            callback(false);
-        }
-    });
+    try {
+        await Secret.password_store(schema, attr, coll, label, password, null);
+    } catch (e) {
+        const name = account.display_name;
+        log(`Failed to store password for account ${name}: ${e.message}`);
+        throw e;
+    }
 }
 
-function lookupAccountPassword(account, callback) {
-    _lookupPassword(SECRET_SCHEMA_ACCOUNT, account, callback);
+function lookupAccountPassword(account) {
+    return _lookupPassword(SECRET_SCHEMA_ACCOUNT, account);
 }
 
-function lookupIdentifyPassword(account, callback) {
-    _lookupPassword(SECRET_SCHEMA_IDENTIFY, account, callback);
+function lookupIdentifyPassword(account) {
+    return _lookupPassword(SECRET_SCHEMA_IDENTIFY, account);
 }
 
-function _lookupPassword(schema, account, callback) {
+async function _lookupPassword(schema, account) {
     let attr = { 'account-id': account.get_path_suffix() };
-    Secret.password_lookup(schema, attr, null, (o, res) => {
-        try {
-            let password = Secret.password_lookup_finish(res);
-            callback(password);
-        } catch (e) {
-            let name = account.display_name;
-            log(`Failed to lookup password for account "${name}": ${e.message}`);
-            callback(null);
-        }
-    });
+    let password = null;
+    try {
+        password = await Secret.password_lookup(schema, attr, null);
+    } catch (e) {
+        const name = account.display_name;
+        log(`Failed to lookup password for account "${name}": ${e.message}`);
+        throw e;
+    }
+
+    return password;
 }
 
 function clearAccountPassword(account) {
@@ -188,16 +189,15 @@ function clearIdentifyPassword(account) {
     _clearPassword(SECRET_SCHEMA_IDENTIFY, account);
 }
 
-function _clearPassword(schema, account) {
+async function _clearPassword(schema, account) {
     let attr = { 'account-id': account.get_path_suffix() };
-    Secret.password_clear(schema, attr, null, (o, res) => {
-        try {
-            Secret.password_clear_finish(res);
-        } catch (e) {
-            const name = account.display_name;
-            log(`Failed to clear password for account "${name}": ${e.message}`);
-        }
-    });
+    try {
+        await Secret.password_clear(schema, attr, null);
+    } catch (e) {
+        const name = account.display_name;
+        log(`Failed to clear password for account "${name}": ${e.message}`);
+        throw e;
+    }
 }
 
 // findUrls:
@@ -258,45 +258,39 @@ function updateTerms(terms, str) {
     return changed;
 }
 
-function _getGpasteExpire(callback) {
+function _queueSoupMessage(session, message) {
+    return new Promise((resolve, reject) => {
+        session.queue_message(message, () => {
+            const { statusCode } = message;
+            if (statusCode === Soup.KnownStatusCode.OK)
+                resolve(message.responseBody.data);
+            else
+                reject(new Error(`Got unexpected response ${statusCode}`));
+        });
+    });
+}
+
+async function _getGpasteExpire() {
     let session = new Soup.Session();
     let paramUrl = `${GPASTE_BASEURL}api/json/parameter/expire`;
     let message = Soup.form_request_new_from_hash('GET', paramUrl, {});
-    session.queue_message(message, () => {
-        if (message.status_code !== Soup.KnownStatusCode.OK) {
-            callback(false);
-            return;
-        }
-
-        let info = {};
-        try {
-            info = JSON.parse(message.response_body.data);
-        } catch (e) {
-            log(e.message);
-        }
-
-        let values = info.result ? info.result.values : undefined;
-        if (!values)
-            callback(false);
-
-        let day = 24 * 60 * 60;
-        _gpasteExpire = values.reduce((acc, val) => {
-            return Math.abs(day - acc) < Math.abs(day - val) ? acc : val;
-        }, 0).toString();
-        callback(true);
-    });
+
+    const json = await _queueSoupMessage(session, message);
+    const info = JSON.parse(json);
+
+    const values = info.result?.values;
+    if (!values)
+        throw new Error('Returned data is missing expected fields');
+
+    const day = 24 * 60 * 60;
+    return values.reduce((acc, val) => {
+        return Math.abs(day - acc) < Math.abs(day - val) ? acc : val;
+    }, 0).toString();
 }
 
-function gpaste(text, title, callback) {
-    if (_gpasteExpire === undefined) {
-        _getGpasteExpire(success => {
-            if (success)
-                gpaste(text, title, callback);
-            else
-                callback(null);
-        });
-        return;
-    }
+async function gpaste(text, title) {
+    if (_gpasteExpire === undefined)
+        _gpasteExpire = await _getGpasteExpire();
 
     if (title.length > MAX_PASTE_TITLE_LENGTH)
         title = `${title.substr(0, MAX_PASTE_TITLE_LENGTH - 1)}…`;
@@ -311,31 +305,19 @@ function gpaste(text, title, callback) {
     let session = new Soup.Session();
     let createUrl = `${GPASTE_BASEURL}api/json/create`;
     let message = Soup.form_request_new_from_hash('POST', createUrl, params);
-    session.queue_message(message, () => {
-        if (message.status_code !== Soup.KnownStatusCode.OK) {
-            callback(null);
-            return;
-        }
-
-        let info = {};
-        try {
-            info = JSON.parse(message.response_body.data);
-        } catch (e) {
-            log(e.message);
-        }
-        if (info.result && info.result.id)
-            callback(`${GPASTE_BASEURL}${info.result.id}`);
-        else
-            callback(null);
-    });
+
+    const json = await _queueSoupMessage(session, message);
+    const info = JSON.parse(json);
+
+    if (!info.result?.id)
+        throw new Error('Paste server did not return a URL');
+    return `${GPASTE_BASEURL}${info.result.id}`;
 }
 
-function imgurPaste(pixbuf, title, callback) {
+async function imgurPaste(pixbuf, title) {
     let [success, buffer] = pixbuf.save_to_bufferv('png', [], []);
-    if (!success) {
-        callback(null);
-        return;
-    }
+    if (!success)
+        throw new Error('Failed to create image buffer');
 
     let params = {
         title,
@@ -348,23 +330,14 @@ function imgurPaste(pixbuf, title, callback) {
 
     let requestHeaders = message.request_headers;
     requestHeaders.append('Authorization', `Client-ID ${IMGUR_CLIENT_ID}`);
-    session.queue_message(message, () => {
-        if (message.status_code !== Soup.KnownStatusCode.OK) {
-            callback(null);
-            return;
-        }
-
-        let info = {};
-        try {
-            info = JSON.parse(message.response_body.data);
-        } catch (e) {
-            log(e.message);
-        }
-        if (info.success)
-            callback(info.data.link);
-        else
-            callback(null);
-    });
+
+    const json = await _queueSoupMessage(session, message);
+    const info = JSON.parse(json);
+
+    if (!info.success)
+        throw new Error('Failed to upload image to paste service');
+
+    return info.data.link;
 }
 
 function formatTimePassed(seconds) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]