extensionDownloader: Port to Soup3

After 13 years, Soup will release a new, API-incompatible
version[0]. This is a good thing, make sure we support it.

[0] https://blog.tingping.se/2021/02/23/future-of-libsoup.html

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1940>
This commit is contained in:
Florian Müllner 2021-08-04 01:35:08 +02:00
parent 3ad569f426
commit ae90b50dc7
2 changed files with 118 additions and 85 deletions

View File

@ -7,7 +7,7 @@ imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION;
imports.gi.versions.Gio = '2.0'; imports.gi.versions.Gio = '2.0';
imports.gi.versions.GdkPixbuf = '2.0'; imports.gi.versions.GdkPixbuf = '2.0';
imports.gi.versions.Gtk = '3.0'; imports.gi.versions.Gtk = '3.0';
imports.gi.versions.Soup = '2.4'; imports.gi.versions.Soup = '3.0';
imports.gi.versions.TelepathyGLib = '0.12'; imports.gi.versions.TelepathyGLib = '0.12';
imports.gi.versions.TelepathyLogger = '0.2'; imports.gi.versions.TelepathyLogger = '0.2';

View File

@ -3,6 +3,8 @@
const { Clutter, Gio, GLib, GObject, Soup } = imports.gi; const { Clutter, Gio, GLib, GObject, Soup } = imports.gi;
const ByteArray = imports.byteArray;
const Config = imports.misc.config; const Config = imports.misc.config;
const Dialog = imports.ui.dialog; const Dialog = imports.ui.dialog;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
@ -10,6 +12,8 @@ const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main; const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
Gio._promisify(Soup.Session.prototype,
'send_and_read_async', 'send_and_read_finish');
Gio._promisify(Gio.OutputStream.prototype, Gio._promisify(Gio.OutputStream.prototype,
'write_bytes_async', 'write_bytes_finish'); 'write_bytes_async', 'write_bytes_finish');
Gio._promisify(Gio.IOStream.prototype, Gio._promisify(Gio.IOStream.prototype,
@ -23,19 +27,29 @@ var REPOSITORY_URL_UPDATE = 'https://extensions.gnome.org/update-info/';
let _httpSession; let _httpSession;
function installExtension(uuid, invocation) { /**
* @param {string} uuid - extension uuid
* @param {Gio.DBusMethodInvocation} invocation - the caller
* @returns {void}
*/
async function installExtension(uuid, invocation) {
const params = { const params = {
uuid, uuid,
shell_version: Config.PACKAGE_VERSION, shell_version: Config.PACKAGE_VERSION,
}; };
let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params); const message = Soup.Message.new_from_encoded_form('GET',
REPOSITORY_URL_INFO,
Soup.form_encode_hash(params));
_httpSession.queue_message(message, () => {
let info; let info;
try { try {
const bytes = await _httpSession.send_and_read_async(
message,
GLib.PRIORITY_DEFAULT,
null);
checkResponse(message); checkResponse(message);
info = JSON.parse(message.response_body.data); info = JSON.parse(ByteArray.toString(bytes.get_data()));
} catch (e) { } catch (e) {
Main.extensionManager.logExtensionError(uuid, e); Main.extensionManager.logExtensionError(uuid, e);
invocation.return_dbus_error( invocation.return_dbus_error(
@ -43,9 +57,8 @@ function installExtension(uuid, invocation) {
return; return;
} }
let dialog = new InstallExtensionDialog(uuid, info, invocation); const dialog = new InstallExtensionDialog(uuid, info, invocation);
dialog.open(global.get_current_time()); dialog.open(global.get_current_time());
});
} }
function uninstallExtension(uuid) { function uninstallExtension(uuid) {
@ -83,7 +96,7 @@ function uninstallExtension(uuid) {
function checkResponse(message) { function checkResponse(message) {
const { statusCode } = message; const { statusCode } = message;
const phrase = Soup.Status.get_phrase(statusCode); const phrase = Soup.Status.get_phrase(statusCode);
if (statusCode !== Soup.KnownStatusCode.OK) if (statusCode !== Soup.Status.OK)
throw new Error('Unexpected response: %s'.format(phrase)); throw new Error('Unexpected response: %s'.format(phrase));
} }
@ -107,7 +120,11 @@ async function extractExtensionArchive(bytes, dir) {
await unzip.wait_check_async(null); await unzip.wait_check_async(null);
} }
function downloadExtensionUpdate(uuid) { /**
* @param {string} uuid - extension uuid
* @returns {void}
*/
async function downloadExtensionUpdate(uuid) {
if (!Main.extensionManager.updatesSupported) if (!Main.extensionManager.updatesSupported)
return; return;
@ -115,25 +132,31 @@ function downloadExtensionUpdate(uuid) {
GLib.build_filenamev([global.userdatadir, 'extension-updates', uuid])); GLib.build_filenamev([global.userdatadir, 'extension-updates', uuid]));
const params = { shell_version: Config.PACKAGE_VERSION }; const params = { shell_version: Config.PACKAGE_VERSION };
const message = Soup.Message.new_from_encoded_form('GET',
REPOSITORY_URL_DOWNLOAD.format(uuid),
Soup.form_encode_hash(params));
let url = REPOSITORY_URL_DOWNLOAD.format(uuid);
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, async () => {
try { try {
const bytes = await _httpSession.send_and_read_async(
message,
GLib.PRIORITY_DEFAULT,
null);
checkResponse(message); checkResponse(message);
const bytes = message.response_body.flatten().get_as_bytes();
await extractExtensionArchive(bytes, dir); await extractExtensionArchive(bytes, dir);
Main.extensionManager.notifyExtensionUpdate(uuid); Main.extensionManager.notifyExtensionUpdate(uuid);
} catch (e) { } catch (e) {
log('Error while downloading update for extension %s: %s' log('Error while downloading update for extension %s: %s'
.format(uuid, e.message)); .format(uuid, e.message));
} }
});
} }
function checkForUpdates() { /**
* Check extensions.gnome.org for updates
*
* @returns {void}
*/
async function checkForUpdates() {
if (!Main.extensionManager.updatesSupported) if (!Main.extensionManager.updatesSupported)
return; return;
@ -158,28 +181,40 @@ function checkForUpdates() {
shell_version: Config.PACKAGE_VERSION, shell_version: Config.PACKAGE_VERSION,
disable_version_validation: versionCheck.toString(), disable_version_validation: versionCheck.toString(),
}; };
const requestBody = new GLib.Bytes(
ByteArray.fromString(JSON.stringify(metadatas)));
const uri = Soup.URI.new(REPOSITORY_URL_UPDATE); const message = Soup.Message.new('POST',
uri.set_query_from_form(params); '%s?%s'.format(REPOSITORY_URL_UPDATE, Soup.form_encode_hash(params)));
message.set_request_body_from_bytes('application/json', requestBody);
const message = Soup.Message.new_from_uri('POST', uri); let json;
message.set_request( try {
'application/json', const bytes = await _httpSession.send_and_read_async(
Soup.MemoryUse.COPY, message,
JSON.stringify(metadatas) GLib.PRIORITY_DEFAULT,
); null);
checkResponse(message);
_httpSession.queue_message(message, () => { json = ByteArray.toString(bytes.get_data());
if (message.status_code !== Soup.KnownStatusCode.OK) } catch (e) {
log('Update check failed: %s'.format(e.message));
return; return;
let operations = JSON.parse(message.response_body.data);
for (let uuid in operations) {
let operation = operations[uuid];
if (operation === 'upgrade' || operation === 'downgrade')
downloadExtensionUpdate(uuid);
} }
});
const operations = JSON.parse(json);
const updates = [];
for (const uuid in operations) {
const operation = operations[uuid];
if (operation === 'upgrade' || operation === 'downgrade')
updates.push(uuid);
}
try {
await Promise.allSettled(
updates.map(uuid => downloadExtensionUpdate(uuid)));
} catch (e) {
log('Some extension updates failed to download: %s'.format(e.message));
}
} }
var InstallExtensionDialog = GObject.registerClass( var InstallExtensionDialog = GObject.registerClass(
@ -214,40 +249,38 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled'])); this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
} }
_onInstallButtonPressed() { async _onInstallButtonPressed() {
this.close();
const params = { shell_version: Config.PACKAGE_VERSION }; const params = { shell_version: Config.PACKAGE_VERSION };
const message = Soup.Message.new_from_encoded_form('GET',
REPOSITORY_URL_DOWNLOAD.format(this._uuid),
Soup.form_encode_hash(params));
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid); const dir = Gio.File.new_for_path(
let message = Soup.form_request_new_from_hash('GET', url, params); GLib.build_filenamev([global.userdatadir, 'extensions', this._uuid]));
const dir = Gio.File.new_for_path(GLib.build_filenamev(
[global.userdatadir, 'extensions', this._uuid]));
let uuid = this._uuid;
let invocation = this._invocation;
_httpSession.queue_message(message, async () => {
try { try {
const bytes = await _httpSession.send_and_read_async(
message,
GLib.PRIORITY_DEFAULT,
null);
checkResponse(message); checkResponse(message);
const bytes = message.response_body.flatten().get_as_bytes();
await extractExtensionArchive(bytes, dir); await extractExtensionArchive(bytes, dir);
const extension = Main.extensionManager.createExtensionObject( const extension = Main.extensionManager.createExtensionObject(
uuid, dir, ExtensionUtils.ExtensionType.PER_USER); this._uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
Main.extensionManager.loadExtension(extension); Main.extensionManager.loadExtension(extension);
if (!Main.extensionManager.enableExtension(uuid)) if (!Main.extensionManager.enableExtension(this._uuid))
throw new Error('Cannot add %s to enabled extensions gsettings key'.format(uuid)); throw new Error('Cannot enable %s'.format(this._uuid));
invocation.return_value(GLib.Variant.new('(s)', ['successful'])); this._invocation.return_value(new GLib.Variant('(s)', ['successful']));
} catch (e) { } catch (e) {
log('Error while installing %s: %s'.format(uuid, e.message)); log('Error while installing %s: %s'.format(this._uuid, e.message));
uninstallExtension(uuid); this._invocation.return_dbus_error(
invocation.return_dbus_error(
'org.gnome.Shell.ExtensionError', e.message); 'org.gnome.Shell.ExtensionError', e.message);
} }
});
this.close();
} }
}); });