c3428f1efa
Our weather integration should follow GNOME Weather as closely as possible, which means that we should respect its location permission rather than using our own or none at all (which we can as a "system" component and as geoclue's authorization agent). https://bugzilla.gnome.org/show_bug.cgi?id=780252
245 lines
8.0 KiB
JavaScript
245 lines
8.0 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Geoclue = imports.gi.Geoclue;
|
|
const Gio = imports.gi.Gio;
|
|
const GLib = imports.gi.GLib;
|
|
const GWeather = imports.gi.GWeather;
|
|
const Lang = imports.lang;
|
|
const Signals = imports.signals;
|
|
|
|
const PermissionStore = imports.misc.permissionStore;
|
|
const Util = imports.misc.util;
|
|
|
|
// Minimum time between updates to show loading indication
|
|
const UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;
|
|
|
|
const WeatherClient = new Lang.Class({
|
|
Name: 'WeatherClient',
|
|
|
|
_init: function() {
|
|
this._loading = false;
|
|
this._locationValid = false;
|
|
this._lastUpdate = GLib.DateTime.new_from_unix_local(0);
|
|
|
|
this._autoLocationRequested = false;
|
|
this._mostRecentLocation = null;
|
|
|
|
this._gclueService = null;
|
|
this._gclueStarted = false;
|
|
this._gclueStarting = false;
|
|
this._gclueLocationChangedId = 0;
|
|
|
|
this._weatherAuthorized = false;
|
|
this._permStore = new PermissionStore.PermissionStore((proxy, error) => {
|
|
if (error) {
|
|
log('Failed to connect to permissionStore: ' + error.message);
|
|
return;
|
|
}
|
|
|
|
this._permStore.LookupRemote('gnome', 'geolocation', (res, error) => {
|
|
if (error)
|
|
log('Error looking up permission: ' + error.message);
|
|
|
|
let [perms, data] = error ? [{}, null] : res;
|
|
let params = ['gnome', 'geolocation', false, data, perms];
|
|
this._onPermStoreChanged(this._permStore, '', params);
|
|
});
|
|
});
|
|
this._permStore.connectSignal('Changed',
|
|
Lang.bind(this, this._onPermStoreChanged));
|
|
|
|
this._locationSettings = new Gio.Settings({ schema_id: 'org.gnome.system.location' });
|
|
this._locationSettings.connect('changed::enabled',
|
|
Lang.bind(this, this._updateAutoLocation));
|
|
|
|
this._world = GWeather.Location.get_world();
|
|
|
|
let providers = GWeather.Provider.METAR |
|
|
GWeather.Provider.YR_NO |
|
|
GWeather.Provider.OWM;
|
|
this._weatherInfo = new GWeather.Info({ enabled_providers: providers });
|
|
this._weatherInfo.connect_after('updated', () => {
|
|
this._lastUpdate = GLib.DateTime.new_now_local();
|
|
this.emit('changed');
|
|
});
|
|
|
|
this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.Application.desktop',
|
|
'org.gnome.Weather.Application');
|
|
this._weatherAppMon.connect('available-changed', () => { this.emit('changed'); });
|
|
this._weatherAppMon.watchSetting('automatic-location',
|
|
Lang.bind(this, this._onAutomaticLocationChanged));
|
|
this._weatherAppMon.watchSetting('locations',
|
|
Lang.bind(this, this._onLocationsChanged));
|
|
},
|
|
|
|
get available() {
|
|
return this._weatherAppMon.available;
|
|
},
|
|
|
|
get loading() {
|
|
return this._loading;
|
|
},
|
|
|
|
get hasLocation() {
|
|
return this._locationValid;
|
|
},
|
|
|
|
get info() {
|
|
return this._weatherInfo;
|
|
},
|
|
|
|
activateApp: function() {
|
|
this._weatherAppMon.activateApp();
|
|
},
|
|
|
|
update: function() {
|
|
if (!this._locationValid)
|
|
return;
|
|
|
|
let now = GLib.DateTime.new_now_local();
|
|
// Update without loading indication if the current info is recent enough
|
|
if (this._weatherInfo.is_valid() &&
|
|
now.difference(this._lastUpdate) < UPDATE_THRESHOLD)
|
|
this._weatherInfo.update();
|
|
else
|
|
this._loadInfo();
|
|
},
|
|
|
|
get _useAutoLocation() {
|
|
return this._autoLocationRequested &&
|
|
this._locationSettings.get_boolean('enabled') &&
|
|
this._weatherAuthorized;
|
|
},
|
|
|
|
_loadInfo: function() {
|
|
let id = this._weatherInfo.connect('updated', () => {
|
|
this._weatherInfo.disconnect(id);
|
|
this._loading = false;
|
|
});
|
|
|
|
this._loading = true;
|
|
this.emit('changed');
|
|
|
|
this._weatherInfo.update();
|
|
},
|
|
|
|
_locationsEqual: function(loc1, loc2) {
|
|
if (loc1 == loc2)
|
|
return true;
|
|
|
|
if (loc1 == null || loc2 == null)
|
|
return false;
|
|
|
|
return loc1.equal(loc2);
|
|
},
|
|
|
|
_setLocation: function(location) {
|
|
if (this._locationsEqual(this._weatherInfo.location, location))
|
|
return;
|
|
|
|
this._weatherInfo.abort();
|
|
this._weatherInfo.set_location(location);
|
|
this._locationValid = (location != null);
|
|
|
|
if (location)
|
|
this._loadInfo();
|
|
else
|
|
this.emit('changed');
|
|
},
|
|
|
|
_updateLocationMonitoring: function() {
|
|
if (this._useAutoLocation) {
|
|
if (this._gclueLocationChangedId != 0 || this._gclueService == null)
|
|
return;
|
|
|
|
this._gclueLocationChangedId =
|
|
this._gclueService.connect('notify::location',
|
|
Lang.bind(this, this._onGClueLocationChanged));
|
|
this._onGClueLocationChanged();
|
|
} else {
|
|
if (this._gclueLocationChangedId)
|
|
this._gclueService.disconnect(this._gclueLocationChangedId);
|
|
this._gclueLocationChangedId = 0;
|
|
}
|
|
},
|
|
|
|
_startGClueService: function() {
|
|
if (this._gclueStarting)
|
|
return;
|
|
|
|
this._gclueStarting = true;
|
|
|
|
Geoclue.Simple.new('org.gnome.Shell', Geoclue.AccuracyLevel.CITY, null,
|
|
(o, res) => {
|
|
try {
|
|
this._gclueService = Geoclue.Simple.new_finish(res);
|
|
} catch(e) {
|
|
log('Failed to connect to Geoclue2 service: ' + e.message);
|
|
this._setLocation(this._mostRecentLocation);
|
|
return;
|
|
}
|
|
this._gclueStarted = true;
|
|
this._gclueService.get_client().distance_threshold = 100;
|
|
this._updateLocationMonitoring();
|
|
});
|
|
},
|
|
|
|
_onGClueLocationChanged: function() {
|
|
let geoLocation = this._gclueService.location;
|
|
let location = GWeather.Location.new_detached(geoLocation.description,
|
|
null,
|
|
geoLocation.latitude,
|
|
geoLocation.longitude);
|
|
this._setLocation(location);
|
|
},
|
|
|
|
_onAutomaticLocationChanged: function(settings, key) {
|
|
let useAutoLocation = settings.get_boolean(key);
|
|
if (this._autoLocationRequested == useAutoLocation)
|
|
return;
|
|
|
|
this._autoLocationRequested = useAutoLocation;
|
|
|
|
this._updateAutoLocation();
|
|
},
|
|
|
|
_updateAutoLocation: function() {
|
|
this._updateLocationMonitoring();
|
|
|
|
if (this._useAutoLocation)
|
|
this._startGClueService();
|
|
else
|
|
this._setLocation(this._mostRecentLocation);
|
|
},
|
|
|
|
_onLocationsChanged: function(settings, key) {
|
|
let serialized = settings.get_value(key).deep_unpack().shift();
|
|
let mostRecentLocation = null;
|
|
|
|
if (serialized)
|
|
mostRecentLocation = this._world.deserialize(serialized);
|
|
|
|
if (this._locationsEqual(this._mostRecentLocation, mostRecentLocation))
|
|
return;
|
|
|
|
this._mostRecentLocation = mostRecentLocation;
|
|
|
|
if (!this._useAutoLocation || !this._gclueStarted)
|
|
this._setLocation(this._mostRecentLocation);
|
|
},
|
|
|
|
_onPermStoreChanged: function(proxy, sender, params) {
|
|
let [table, id, deleted, data, perms] = params;
|
|
|
|
if (table != 'gnome' || id != 'geolocation')
|
|
return;
|
|
|
|
let permission = perms['org.gnome.Weather.Application'] || ['NONE'];
|
|
let [accuracy] = permission;
|
|
this._weatherAuthorized = accuracy != 'NONE';
|
|
|
|
this._updateAutoLocation();
|
|
}
|
|
});
|
|
Signals.addSignalMethods(WeatherClient.prototype);
|