From 5208a333943894edc5caaabddb9356d4ac4edea3 Mon Sep 17 00:00:00 2001 From: Dale Harvey Date: Thu, 11 Jun 2020 08:10:50 +0000 Subject: [PATCH] Bug 1627550 - Trigger download of region maps when pref enabled r=leplatrem Differential Revision: https://phabricator.services.mozilla.com/D78884 --- toolkit/modules/Region.jsm | 106 ++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 8 deletions(-) diff --git a/toolkit/modules/Region.jsm b/toolkit/modules/Region.jsm index 8b18986b9edd..55c73f6c4a3f 100644 --- a/toolkit/modules/Region.jsm +++ b/toolkit/modules/Region.jsm @@ -10,6 +10,10 @@ const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); +const { RemoteSettings } = ChromeUtils.import( + "resource://services-settings/remote-settings.js" +); + XPCOMUtils.defineLazyModuleGetters(this, { AppConstants: "resource://gre/modules/AppConstants.jsm", LocationHelper: "resource://gre/modules/LocationHelper.jsm", @@ -40,12 +44,20 @@ XPCOMUtils.defineLazyPreferenceGetter( false ); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "localGeocodingEnabled", + "browser.region.local-geocoding", + false +); + const log = console.createInstance({ prefix: "Region.jsm", maxLogLevel: loggingEnabled ? "All" : "Warn", }); const REGION_PREF = "browser.search.region"; +const COLLECTION_ID = "regions"; /** * This module keeps track of the users current region (country). @@ -53,6 +65,8 @@ const REGION_PREF = "browser.search.region"; * specific customisations. */ class RegionDetector { + // The RemoteSettings client used to sync region files. + _rsClient = null; // Keep track of the wifi data across listener events. wifiDataPromise = null; // Topic for Observer events fired by Region.jsm. @@ -76,6 +90,11 @@ class RegionDetector { if (!region) { Services.tm.idleDispatchToMainThread(this._fetchRegion.bind(this)); } + if (localGeocodingEnabled) { + Services.tm.idleDispatchToMainThread( + this._setupRemoteSettings.bind(this) + ); + } this._home = region; } @@ -246,6 +265,85 @@ class RegionDetector { } } + /** + * Setup the RemoteSetting client + sync listener and ensure + * the map files are downloaded. + */ + async _setupRemoteSettings() { + log.info("_setupRemoteSettings"); + this._rsClient = RemoteSettings(COLLECTION_ID); + this._rsClient.on("sync", this._onRegionFilesSync.bind(this)); + await this._ensureRegionFilesDownloaded(); + } + + /** + * Called when RemoteSettings syncs new data, clean up any + * stale attachments and download any new ones. + * + * @param {Object} syncData + * Object describing the data that has just been synced. + */ + async _onRegionFilesSync({ data: { deleted } }) { + log.info("_onRegionFilesSync"); + const toDelete = deleted.filter(d => d.attachment); + // Remove local files of deleted records + await Promise.all( + toDelete.map(entry => this._rsClient.attachments.delete(entry)) + ); + await this._ensureRegionFilesDownloaded(); + } + + /** + * Download the RemoteSetting record attachments, when they are + * successfully downloaded set a flag so we can start using them + * for geocoding. + */ + async _ensureRegionFilesDownloaded() { + log.info("_ensureRegionFilesDownloaded"); + let records = (await this._rsClient.get()).filter(d => d.attachment); + log.info("_ensureRegionFilesDownloaded", records); + if (!records.length) { + log.info("_ensureRegionFilesDownloaded: Nothing to download"); + return; + } + let opts = { useCache: true }; + await Promise.all( + records.map(r => this._rsClient.attachments.download(r, opts)) + ); + log.info("_ensureRegionFilesDownloaded complete"); + this._regionFilesReady = true; + } + + /** + * Fetch an attachment from RemoteSettings. + * + * @param {String} id + * The id of the record to fetch the attachment from. + */ + async _fetchAttachment(id) { + let record = (await this._rsClient.get({ filters: { id } })).pop(); + let { buffer } = await this._rsClient.attachments.download(record, { + useCache: true, + }); + let text = new TextDecoder("utf-8").decode(buffer); + return JSON.parse(text); + } + + /** + * Get a map of the world with region definitions. + */ + async _getPlainMap() { + return this._fetchAttachment("world"); + } + + /** + * Get a map with the regions expanded by a few km to help + * fallback lookups when a location is not within a region. + */ + async _getBufferedMap() { + return this._fetchAttachment("world-buffered"); + } + /** * Gets the users current location using the same reverse IP * request that is used for GeoLocation requests. @@ -276,14 +374,6 @@ class RegionDetector { return this._geoCode(location); } - // TODO: Stubs for testing - async _getPlainMap() { - return null; - } - async _getBufferedMap() { - return null; - } - /** * Take a location and return the region code for that location * by looking up the coordinates in geojson map files.