339 lines
11 KiB
JavaScript
339 lines
11 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
//
|
|
// Copyright (C) 2019 Endless Mobile, Inc.
|
|
//
|
|
// This is a GNOME Shell component to wrap the interactions over
|
|
// D-Bus with the Mogwai system daemon.
|
|
//
|
|
// Licensed under the GNU General Public License Version 2
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
const { Clutter, Gio, GLib,
|
|
GObject, Gtk, NM, Shell, St } = imports.gi;
|
|
|
|
const NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME = "connection.automatic-updates-notification-time";
|
|
const NM_SETTING_ALLOW_DOWNLOADS = 'connection.allow-downloads';
|
|
const NM_SETTING_TARIFF_ENABLED = "connection.tariff-enabled";
|
|
|
|
const SchedulerInterface = '\
|
|
<node> \
|
|
<interface name="com.endlessm.DownloadManager1.Scheduler"> \
|
|
<property name="ActiveEntryCount" type="u" access="read" /> \
|
|
<property name="EntryCount" type="u" access="read" /> \
|
|
</interface> \
|
|
</node>';
|
|
|
|
const SchedulerProxy = Gio.DBusProxy.makeProxyWrapper(SchedulerInterface);
|
|
|
|
let _updateManager = null;
|
|
|
|
function getUpdateManager() {
|
|
if (_updateManager == null)
|
|
_updateManager = new UpdateManager();
|
|
return _updateManager;
|
|
}
|
|
|
|
var State = {
|
|
UNKNOWN: 0,
|
|
DISCONNECTED: 1,
|
|
DISABLED: 2,
|
|
IDLE: 3,
|
|
SCHEDULED: 4,
|
|
DOWNLOADING: 5
|
|
};
|
|
|
|
function stateToIconName(state) {
|
|
switch (state) {
|
|
case State.UNKNOWN:
|
|
case State.DISCONNECTED:
|
|
return null;
|
|
|
|
case State.DISABLED:
|
|
return 'resource:///org/gnome/shell/theme/automatic-updates-off-symbolic.svg';
|
|
|
|
case State.IDLE:
|
|
case State.DOWNLOADING:
|
|
return 'resource:///org/gnome/shell/theme/automatic-updates-on-symbolic.svg';
|
|
|
|
case State.SCHEDULED:
|
|
return 'resource:///org/gnome/shell/theme/automatic-updates-scheduled-symbolic.svg';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
var UpdateManager = GObject.registerClass ({
|
|
Properties: {
|
|
'last-notification-time': GObject.ParamSpec.int('last-notification-time',
|
|
'last-notification-time',
|
|
'last-notification-time',
|
|
GObject.ParamFlags.READWRITE,
|
|
null),
|
|
'icon': GObject.ParamSpec.object('icon', 'icon', 'icon',
|
|
GObject.ParamFlags.READABLE,
|
|
Gio.Icon.$gtype),
|
|
'state': GObject.ParamSpec.uint('state', 'state', 'state',
|
|
GObject.ParamFlags.READABLE,
|
|
null),
|
|
},
|
|
}, class UpdateManager extends GObject.Object {
|
|
_init() {
|
|
super._init();
|
|
|
|
this._activeConnection = null;
|
|
this._settingChangedSignalId = 0;
|
|
this._updateTimeoutId = 0;
|
|
|
|
this._state = State.UNKNOWN;
|
|
|
|
NM.Client.new_async(null, this._clientGot.bind(this));
|
|
}
|
|
|
|
_clientGot(obj, result) {
|
|
this._client = NM.Client.new_finish(result);
|
|
|
|
this._client.connect('notify::primary-connection', this._sync.bind(this));
|
|
this._client.connect('notify::state', this._sync.bind(this));
|
|
|
|
// Start retrieving the Mogwai proxy
|
|
this._proxy = new SchedulerProxy(Gio.DBus.system,
|
|
'com.endlessm.MogwaiSchedule1',
|
|
'/com/endlessm/DownloadManager1',
|
|
(proxy, error) => {
|
|
if (error) {
|
|
log(error.message);
|
|
return;
|
|
}
|
|
this._proxy.connect('g-properties-changed',
|
|
this._sync.bind(this));
|
|
this._updateStatus();
|
|
});
|
|
|
|
this._sync();
|
|
}
|
|
|
|
_sync() {
|
|
if (!this._client || !this._proxy)
|
|
return;
|
|
|
|
if (this._updateTimeoutId > 0) {
|
|
GLib.source_remove(this._updateTimeoutId);
|
|
this._updateTimeoutId = 0;
|
|
}
|
|
|
|
// Intermediate states (connecting or disconnecting) must not trigger
|
|
// any kind of state change.
|
|
if (this._client.state == NM.State.CONNECTING || this._client.state == NM.State.DISCONNECTING)
|
|
return;
|
|
|
|
// Use a timeout to avoid instantly throwing the notification at
|
|
// the user's face, and to avoid a series of unecessary updates
|
|
// that happen when NetworkManager is still figuring out details.
|
|
this._updateTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
|
2,
|
|
() => {
|
|
this._updateStatus();
|
|
this._updateTimeoutId = 0;
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
GLib.Source.set_name_by_id(this._updateTimeoutId, '[update] updateStatus');
|
|
}
|
|
|
|
_updateStatus() {
|
|
// Update the current active connection. This will connect to the
|
|
// NM.SettingUser signal to sync every time someone updates the
|
|
// NM_SETTING_ALLOW_DOWNLOADS setting.
|
|
this._updateActiveConnection();
|
|
|
|
let state = this._getState();
|
|
if (state != this._state) {
|
|
this._state = state;
|
|
this.notify('state');
|
|
|
|
this._updateIcon();
|
|
}
|
|
}
|
|
|
|
_updateActiveConnection() {
|
|
let currentActiveConnection = this._getActiveConnection();
|
|
|
|
if (this._activeConnection == currentActiveConnection)
|
|
return;
|
|
|
|
// Disconnect from the previous active connection
|
|
if (this._settingChangedSignalId > 0) {
|
|
this._activeConnection.disconnect(this._settingChangedSignalId);
|
|
this._settingChangedSignalId = 0;
|
|
}
|
|
|
|
this._activeConnection = currentActiveConnection;
|
|
|
|
// Connect from the current active connection
|
|
if (currentActiveConnection)
|
|
this._settingChangedSignalId = currentActiveConnection.connect('changed', this._updateStatus.bind(this));
|
|
}
|
|
|
|
_ensureUserSetting(connection) {
|
|
let userSetting = connection.get_setting(NM.SettingUser.$gtype);
|
|
if (!userSetting) {
|
|
userSetting = new NM.SettingUser();
|
|
connection.add_setting(userSetting);
|
|
}
|
|
return userSetting;
|
|
}
|
|
|
|
_getState() {
|
|
if (!this._activeConnection)
|
|
return State.DISCONNECTED;
|
|
|
|
let userSetting = this._ensureUserSetting(this._activeConnection);
|
|
|
|
// We only return true when:
|
|
// * Automatic Updates are on
|
|
// * A schedule was set
|
|
// * Something is being downloaded
|
|
|
|
let allowDownloadsValue = userSetting.get_data(NM_SETTING_ALLOW_DOWNLOADS);
|
|
if (allowDownloadsValue) {
|
|
let allowDownloads = (allowDownloadsValue === '1');
|
|
|
|
if (!allowDownloads)
|
|
return State.DISABLED;
|
|
} else {
|
|
// Guess the default value from the metered state. Only return
|
|
// if it's disabled - if it's not, we want to follow the regular
|
|
// code paths and fetch the correct state
|
|
let connectionSetting = this._activeConnection.get_setting_connection();
|
|
|
|
if (!connectionSetting)
|
|
return State.DISABLED;
|
|
|
|
let metered = connectionSetting.get_metered();
|
|
if (metered == NM.Metered.YES || metered == NM.Metered.GUESS_YES)
|
|
return State.DISABLED;
|
|
}
|
|
|
|
// Without the proxy, we can't really know the state
|
|
if (!this._proxy)
|
|
return State.UNKNOWN;
|
|
|
|
let scheduleSet = userSetting.get_data(NM_SETTING_TARIFF_ENABLED) === '1';
|
|
if (!scheduleSet)
|
|
return State.IDLE;
|
|
|
|
let downloading = this._proxy.ActiveEntryCount > 0;
|
|
if (downloading)
|
|
return State.DOWNLOADING;
|
|
|
|
// At this point we're not downloading anything, but something
|
|
// might be queued
|
|
let downloadsQueued = this._proxy.EntryCount > 0;
|
|
if (downloadsQueued)
|
|
return State.SCHEDULED;
|
|
else
|
|
return State.IDLE;
|
|
}
|
|
|
|
_getActiveConnection() {
|
|
let activeConnection = this._client.get_primary_connection();
|
|
return activeConnection ? activeConnection.get_connection() : null;
|
|
}
|
|
|
|
_updateIcon() {
|
|
let state = this._state;
|
|
let iconName = stateToIconName(state);
|
|
|
|
if (iconName) {
|
|
let iconFile = Gio.File.new_for_uri(iconName);
|
|
this._icon = new Gio.FileIcon({ file: iconFile });
|
|
} else {
|
|
this._icon = null;
|
|
}
|
|
|
|
this.notify('icon');
|
|
}
|
|
|
|
get state() {
|
|
return this._state;
|
|
}
|
|
|
|
get lastNotificationTime() {
|
|
let connection = this._getActiveConnection();
|
|
if (!connection)
|
|
return -1;
|
|
|
|
let userSetting = connection.get_setting(NM.SettingUser.$gtype);
|
|
if (!userSetting)
|
|
return -1;
|
|
|
|
let time = userSetting.get_data(NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME);
|
|
return time ? parseInt(time) : -1;
|
|
}
|
|
|
|
set lastNotificationTime(time) {
|
|
if (!this._activeConnection)
|
|
return;
|
|
|
|
let userSetting = this._ensureUserSetting(this._activeConnection);
|
|
userSetting.set_data(NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME,
|
|
'%s'.format(time));
|
|
|
|
this._activeConnection.commit_changes(true, null);
|
|
}
|
|
|
|
|
|
get active() {
|
|
return this._active;
|
|
}
|
|
|
|
set active(_active) {
|
|
if (this._active == _active)
|
|
return;
|
|
|
|
this._active = _active;
|
|
this.notify('active');
|
|
}
|
|
|
|
get icon() {
|
|
return this._icon;
|
|
}
|
|
|
|
toggleAutomaticUpdates() {
|
|
if (!this._activeConnection)
|
|
return;
|
|
|
|
let userSetting = this._ensureUserSetting(this._activeConnection);
|
|
|
|
let state = this._getState();
|
|
let value;
|
|
|
|
if (state == State.IDLE ||
|
|
state == State.SCHEDULED ||
|
|
state == State.DOWNLOADING) {
|
|
value = '0';
|
|
} else {
|
|
value = '1';
|
|
}
|
|
|
|
userSetting.set_data(NM_SETTING_ALLOW_DOWNLOADS, value);
|
|
|
|
this._activeConnection.commit_changes_async(true, null, (con, res, data) => {
|
|
this._activeConnection.commit_changes_finish(res);
|
|
this._updateStatus();
|
|
});
|
|
}
|
|
});
|