[gnome-shell/eos3.8: 115/255] Introduce Automatic Updates indicator



commit 09b1924723098091d2d79960c99022e9184dd771
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Tue Mar 20 21:59:32 2018 -0300

    Introduce Automatic Updates indicator
    
    This is the commit that introduces the new Automatic
    Updates indicator and menu item to our desktop shell.
    
    The indicator is only visible when automatic updates
    are allowed, there is a schedule set, and something
    is being downloaded at the moment. This is such to
    notify the most sensitive and vulnerable users that
    we are using their connection.
    
     * Squashed with 03b175ef3
     * Squashed with b4afda32e
     * Squashed with eef80730f
    
    https://phabricator.endlessm.com/T21683
    https://phabricator.endlessm.com/T22260
    https://phabricator.endlessm.com/T22190
    https://phabricator.endlessm.com/T22316
    
    automaticUpdates: Notify when changing the setting
    
    When the user switches from a connection that had automatic
    updates disabled to a connection that has it enabled (and
    vice-versa too, enabled → disabled), the proposed behavior
    is to send a notification about it.
    
    This commit implements such behavior in a particularly
    sophisticated way. We now store a new setting in each
    network that we notify, so if you connect to a network
    that you already connected in the past, before the new
    automatic updates feature landed, you'll still get a
    first-time notification. This is to make sure users
    properly learn and understand about the feature.
    
    In addition to that, a 4s timeout was added to the
    notification. Showing the notification immediately
    after connecting to a network is a drastic cognitive
    load, and it's pretty invasive.
    
    automaticUpdates: Simplify turn on/off label
    
    For consistency with other menus such as VPN and Bluetooth.
    
    automaticUpdates: Reduce timeout to 2 seconds
    
    Per new design direction.
    
    https://phabricator.endlessm.com/T22190
    
    automaticUpdates: Make first notification critical
    
    Subsequent notifications have normal priority, but, as
    per new product direction, the first notification for
    a given connection should be critical.
    
    https://phabricator.endlessm.com/T22190
    
    automaticUpdates: Comment on the intent of the previous change
    
    On rebase, squash with 28e72d33b7a33f757c38ae55511259d2a24b9f7c.
    
    https://phabricator.endlessm.com/T22190
    
    automaticUpdates: Ignore intermediate states
    
    The current code that tracks the connection and the state
    of Automatic Updates is not aware of intermediate state that
    NetworkManager enters when connecting or disconnecting from
    a network. In these states, the current code just jumps to
    a wrong final state (connected or disconnected) with actually
    wrong values.
    
    Fix that by ignoring changes when the network is being connected
    or disconnected.
    
    https://phabricator.endlessm.com/T22190
    
    automaticUpdates: Add a "Close" button
    
    As per product decision, add a close button to
    the automatic updates notification.
    
    https://phabricator.endlessm.com/T22190
    
    automaticUpdates: avoid ellipses on status menu
    
    For consistency with every other submenu.
    
    https://phabricator.endlessm.com/T22507

 .../com.endlessm.DownloadManager1.Scheduler.xml    |   6 +
 data/gnome-shell-dbus-interfaces.gresource.xml     |   1 +
 data/gnome-shell-theme.gresource.xml               |   3 +
 data/theme/endless-auto-updates-off-symbolic.svg   |   1 +
 data/theme/endless-auto-updates-on-symbolic.svg    |   1 +
 .../endless-auto-updates-scheduled-symbolic.svg    |   1 +
 js/js-resources.gresource.xml                      |   1 +
 js/ui/panel.js                                     |   4 +
 js/ui/status/automaticUpdates.js                   | 443 +++++++++++++++++++++
 po/POTFILES.in                                     |   1 +
 10 files changed, 462 insertions(+)
---
diff --git a/data/dbus-interfaces/com.endlessm.DownloadManager1.Scheduler.xml 
b/data/dbus-interfaces/com.endlessm.DownloadManager1.Scheduler.xml
new file mode 100644
index 0000000000..f2833b3777
--- /dev/null
+++ b/data/dbus-interfaces/com.endlessm.DownloadManager1.Scheduler.xml
@@ -0,0 +1,6 @@
+<node>
+  <interface name="com.endlessm.DownloadManager1.Scheduler">
+    <property name="ActiveEntryCount" type="u" access="read" />
+    <property name="EntryCount" type="u" access="read" />
+  </interface>
+</node>
diff --git a/data/gnome-shell-dbus-interfaces.gresource.xml b/data/gnome-shell-dbus-interfaces.gresource.xml
index 1967295d13..536c1daa6a 100644
--- a/data/gnome-shell-dbus-interfaces.gresource.xml
+++ b/data/gnome-shell-dbus-interfaces.gresource.xml
@@ -3,6 +3,7 @@
   <gresource prefix="/org/gnome/shell/dbus-interfaces">
     <file preprocess="xml-stripblanks">com.endlessm.AppStore.xml</file>
     <file preprocess="xml-stripblanks">com.endlessm.DiscoveryFeed.xml</file>
+    <file preprocess="xml-stripblanks">com.endlessm.DownloadManager1.Scheduler.xml</file>
     <file preprocess="xml-stripblanks">com.endlessm.Speedwagon.xml</file>
     <file preprocess="xml-stripblanks">net.hadess.SensorProxy.xml</file>
     <file preprocess="xml-stripblanks">net.hadess.SwitcherooControl.xml</file>
diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index 907d05dc25..6757e9d7f5 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -42,6 +42,9 @@
     <file>discovery-feed-bar-hover.png</file>
     <file>discovery-feed-tile-normal.png</file>
     <file>discovery-feed-tile-hover.png</file>
+    <file>endless-auto-updates-off-symbolic.svg</file>
+    <file>endless-auto-updates-on-symbolic.svg</file>
+    <file>endless-auto-updates-scheduled-symbolic.svg</file>
     <file>endless-button-symbolic.svg</file>
     <file>endless-help-symbolic.svg</file>
     <file>feedback-symbolic.svg</file>
diff --git a/data/theme/endless-auto-updates-off-symbolic.svg 
b/data/theme/endless-auto-updates-off-symbolic.svg
new file mode 100644
index 0000000000..fb5f24446a
--- /dev/null
+++ b/data/theme/endless-auto-updates-off-symbolic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 16 
16"><title>EOS_symbolic-icons_v0.1auto-updates_OFF</title><path 
d="M11.03,10.074a.125.125,0,0,0-.086.213l1.347,1.35a5.733,5.733,0,0,1-4.238,1.9,5.6,5.6,0,0,1-2.282-.484l-1.11,1.11a7.024,7.024,0,0,0,3.392.875,7.3,7.3,0,0,0,5.3-2.339l1.357,1.36a.125.125,0,0,0,.213-.086L15,10Z"
 style="fill:#999"/><path 
d="M12.921,5.9a6.354,6.354,0,0,1,.326,1.863.248.248,0,0,0,.244.244h1a.261.261,0,0,0,.257-.265,7.543,7.543,0,0,0-.677-2.991Z"
 style="fill:#999"/><path 
d="M6.286,9.707a.994.994,0,0,0,.715.3H9a1,1,0,0,0,1-1v-2A.994.994,0,0,0,9.7,6.3l2.175-2.175,0,0L12.93,3.067l0,0,1.4-1.4a.25.25,0,0,0,0-.354L13.617.608a.25.25,0,0,0-.354,0L11.772,2.1A6.97,6.97,0,0,0,7.948.961,7.3,7.3,0,0,0,2.651,3.3L1.293,1.94a.125.125,0,0,0-.214.086L1,6l3.971-.074a.125.125,0,0,0,.086-.213L3.71,4.363a5.733,5.733,0,0,1,4.238-1.9,5.523,5.523,0,0,1,2.723.739L7.86,6.011H7a1,1,0,0,0-1,1V7.87L3.291,10.58a6,6,0,0,1-.537-2.346.248.248,0,0,0-.244-.245h-1a.261.261,0,0,0-.257.265A
 
7.329,7.329,0,0,0,2.172,11.7L.608,13.263a.25.25,0,0,0,0,.354l.707.707a.25.25,0,0,0,.354,0l1.4-1.4,0,0,1.056-1.056,0,0Z"
 style="fill:#999"/></svg>
\ No newline at end of file
diff --git a/data/theme/endless-auto-updates-on-symbolic.svg b/data/theme/endless-auto-updates-on-symbolic.svg
new file mode 100644
index 0000000000..8b23fe3602
--- /dev/null
+++ b/data/theme/endless-auto-updates-on-symbolic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 16 
16"><title>EOS_symbolic-icons_v0.1auto-updates_ON</title><rect x="6.001" y="6.011" width="4" height="4" 
rx="1" ry="1" style="fill:#999"/><path 
d="M5.057,5.713,3.71,4.362a5.733,5.733,0,0,1,4.238-1.9,5.173,5.173,0,0,1,5.3,5.305.248.248,0,0,0,.244.244h1a.261.261,0,0,0,.257-.265A6.684,6.684,0,0,0,7.948.961,7.3,7.3,0,0,0,2.65,3.3L1.293,1.94a.125.125,0,0,0-.213.086L1,6l3.971-.074A.125.125,0,0,0,5.057,5.713Z"
 style="fill:#999"/><path 
d="M11.03,10.074a.125.125,0,0,0-.086.213l1.347,1.35a5.733,5.733,0,0,1-4.238,1.9,5.173,5.173,0,0,1-5.3-5.305.248.248,0,0,0-.244-.245h-1a.261.261,0,0,0-.257.265,6.684,6.684,0,0,0,6.8,6.785,7.3,7.3,0,0,0,5.3-2.339l1.357,1.36a.125.125,0,0,0,.214-.086L15,10Z"
 style="fill:#999"/></svg>
\ No newline at end of file
diff --git a/data/theme/endless-auto-updates-scheduled-symbolic.svg 
b/data/theme/endless-auto-updates-scheduled-symbolic.svg
new file mode 100644
index 0000000000..c62eb24197
--- /dev/null
+++ b/data/theme/endless-auto-updates-scheduled-symbolic.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 16 
16"><title>EOS_symbolic-icons_v0.1update-scheduled_OUTLINE</title><path 
d="M7.99,15.054A6.7,6.7,0,0,1,1.06,8,6.7,6.7,0,0,1,7.99.954,6.71,6.71,0,0,1,14.948,8,6.71,6.71,0,0,1,7.99,15.054Zm0-12.6A5.2,5.2,0,0,0,2.56,8a5.2,5.2,0,0,0,5.43,5.55A5.215,5.215,0,0,0,13.448,8,5.216,5.216,0,0,0,7.99,2.454Z"
 style="fill:#999"/><path 
d="M9.209,10.443,7.2,8.437a.25.25,0,0,1-.073-.177l0-4.01A.25.25,0,0,1,7.379,4h1.25a.25.25,0,0,1,.25.25l0,3.283a.25.25,0,0,0,.073.177l1.5,1.494a.25.25,0,0,1,0,.354l-.883.884A.25.25,0,0,1,9.209,10.443Z"
 style="fill:#999"/></svg>
\ No newline at end of file
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 404134d0c2..bba1a6acf0 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -152,6 +152,7 @@
     <file>ui/iconGridLayout.js</file>
     <file>ui/internetSearch.js</file>
     <file>ui/sideComponent.js</file>
+    <file>ui/status/automaticUpdates.js</file>
     <file>ui/userMenu.js</file>
     <file>ui/workspaceMonitor.js</file>
   </gresource>
diff --git a/js/ui/panel.js b/js/ui/panel.js
index d387d123a6..3f3c03a92f 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -748,12 +748,14 @@ class AggregateMenu extends PanelMenu.Button {
         this._screencast = new imports.ui.status.screencast.Indicator();
         this._location = new imports.ui.status.location.Indicator();
         this._nightLight = new imports.ui.status.nightLight.Indicator();
+        this._automaticUpdates = new imports.ui.status.automaticUpdates.Indicator();
         this._thunderbolt = new imports.ui.status.thunderbolt.Indicator();
 
         this._indicators.add_child(this._thunderbolt);
         this._indicators.add_child(this._screencast);
         this._indicators.add_child(this._location);
         this._indicators.add_child(this._nightLight);
+        this._indicators.add_child(this._automaticUpdates);
         if (this._network)
             this._indicators.add_child(this._network);
         if (this._bluetooth)
@@ -768,6 +770,8 @@ class AggregateMenu extends PanelMenu.Button {
         if (this._network)
             this.menu.addMenuItem(this._network.menu);
 
+        this.menu.addMenuItem(this._automaticUpdates.menu);
+
         if (this._bluetooth)
             this.menu.addMenuItem(this._bluetooth.menu);
 
diff --git a/js/ui/status/automaticUpdates.js b/js/ui/status/automaticUpdates.js
new file mode 100644
index 0000000000..38e5fde49f
--- /dev/null
+++ b/js/ui/status/automaticUpdates.js
@@ -0,0 +1,443 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+//
+// Copyright (C) 2018 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.
+
+/* exported Indicator */
+
+const { Gio, GLib, GObject, NM, Shell } = imports.gi;
+
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+
+const { loadInterfaceXML } = imports.misc.fileUtils;
+
+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 = loadInterfaceXML('com.endlessm.DownloadManager1.Scheduler');
+const SchedulerProxy = Gio.DBusProxy.makeProxyWrapper(SchedulerInterface);
+
+var AutomaticUpdatesState = {
+    UNKNOWN: 0,
+    DISCONNECTED: 1,
+    DISABLED: 2,
+    IDLE: 3,
+    SCHEDULED: 4,
+    DOWNLOADING: 5,
+};
+
+function automaticUpdatesStateToString(state) {
+    switch (state) {
+    case AutomaticUpdatesState.UNKNOWN:
+    case AutomaticUpdatesState.DISCONNECTED:
+        return null;
+
+    case AutomaticUpdatesState.DISABLED:
+        return 'resource:///org/gnome/shell/theme/endless-auto-updates-off-symbolic.svg';
+
+    case AutomaticUpdatesState.IDLE:
+    case AutomaticUpdatesState.DOWNLOADING:
+        return 'resource:///org/gnome/shell/theme/endless-auto-updates-on-symbolic.svg';
+
+    case AutomaticUpdatesState.SCHEDULED:
+        return 'resource:///org/gnome/shell/theme/endless-auto-updates-scheduled-symbolic.svg';
+    }
+
+    return null;
+}
+
+var Indicator = GObject.registerClass(
+class AutomaticUpdatesIndicator extends PanelMenu.SystemIndicator {
+    _init() {
+        super._init();
+
+        this.visible = false;
+
+        this._item = new PopupMenu.PopupSubMenuMenuItem('', true);
+        this._toggleItem = this._item.menu.addAction('', this._toggleAutomaticUpdates.bind(this));
+        this._item.menu.addAction(_('Updates Queue'),
+            () => {
+                const params = new GLib.Variant('(sava{sv})', [
+                    'set-mode', [new GLib.Variant('s', 'updates')],
+                    {},
+                ]);
+
+                Gio.DBus.session.call(
+                    'org.gnome.Software',
+                    '/org/gnome/Software',
+                    'org.gtk.Actions',
+                    'Activate',
+                    params,
+                    null,
+                    Gio.DBusCallFlags.NONE,
+                    5000,
+                    null,
+                    (conn, result) => {
+                        try {
+                            conn.call_finish(result);
+                        } catch (e) {
+                            logError(e, 'Failed to start gnome-software');
+                        }
+                    });
+            });
+        this._item.menu.addSettingsAction(_('Set a Schedule'), 'gnome-updates-panel.desktop');
+        this.menu.addMenuItem(this._item);
+
+        this._activeConnection = null;
+        this._settingChangedSignalId = 0;
+        this._updateTimeoutId = 0;
+        this._notification = null;
+
+        this._state = AutomaticUpdatesState.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));
+
+        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
+        this._sessionUpdated();
+
+        // 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();
+    }
+
+    _sessionUpdated() {
+        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
+        this.menu.setSensitive(sensitive);
+    }
+
+    _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, '[automaticUpdates] 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();
+
+        // Toggle item name
+        this._updateAutomaticUpdatesItem();
+
+        // Icons
+        let icon = this._getIcon();
+
+        this._item.icon.gicon = icon;
+        this.gicon = icon;
+
+        // Only show the Automatic Updates icon at the bottom bar when it is
+        // both enabled, and there are updates being downloaded or installed.
+        this._updateVisibility();
+
+        // The status label
+        this._item.label.text = _('Automatic Updates');
+
+        this._state = this._getState();
+    }
+
+    _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;
+
+        // Maybe send a notification if the connection changed and
+        // with it, the automatic updates setting
+        this._updateNotification();
+
+        // Connect from the current active connection
+        if (currentActiveConnection)
+            this._settingChangedSignalId = currentActiveConnection.connect('changed', 
this._updateStatus.bind(this));
+    }
+
+    _updateNotification() {
+        // Only notify when in an regular session, not in GDM or initial-setup.
+        if (Main.sessionMode.currentMode !== 'user' &&
+            Main.sessionMode.currentMode !== 'user-coding' &&
+            Main.sessionMode.currentMode !== 'endless')
+            return;
+
+        // Also don't notify when starting up
+        if (this._state === AutomaticUpdatesState.UNKNOWN)
+            return;
+
+        let alreadySentNotification = this._alreadySentNotification();
+        let newState = this._getState();
+
+        let wasDisconnected = this._state === AutomaticUpdatesState.DISCONNECTED;
+        let wasActive = this._state >= AutomaticUpdatesState.IDLE;
+        let isActive = newState >= AutomaticUpdatesState.IDLE;
+
+        // The criteria to notify about the Automatic Updates setting is:
+        //   1. If the user was disconnected and connects to a new network; or
+        //   2. If the user was connected and connects to a network with different status;
+        if ((wasDisconnected && alreadySentNotification) || (!wasDisconnected && isActive === wasActive))
+            return;
+
+        if (this._notification)
+            this._notification.destroy();
+
+        if (newState === AutomaticUpdatesState.DISCONNECTED)
+            return;
+
+        let source = new MessageTray.SystemNotificationSource();
+        Main.messageTray.add(source);
+
+        // Figure out the title, subtitle and icon
+        let title, subtitle, iconFile;
+
+        if (isActive) {
+            title = _('Automatic updates on');
+            subtitle = _('You have Unlimited Data so automatic updates have been turned on for this 
connection.');
+            iconFile = automaticUpdatesStateToString(AutomaticUpdatesState.IDLE);
+        } else {
+            title = _('Automatic updates are turned off to save your data');
+            subtitle = _('You will need to choose which Endless updates to apply when on this connection.');
+            iconFile = automaticUpdatesStateToString(AutomaticUpdatesState.DISABLED);
+        }
+
+        let gicon = new Gio.FileIcon({ file: Gio.File.new_for_uri(iconFile) });
+
+        // Create the notification.
+        // The first time we notify the user for a given connection,
+        // we set the urgency to critical so that we make sure the
+        // user understands how we may be changing their settings.
+        // On subsequent notifications for the given connection,
+        // for instance if the user regularly switches between
+        // metered and unmetered connections, we set the urgency
+        // to normal so as not to be too obtrusive.
+        this._notification = new MessageTray.Notification(source, title, subtitle, { gicon });
+        this._notification.setUrgency(alreadySentNotification ? MessageTray.Urgency.NORMAL : 
MessageTray.Urgency.CRITICAL);
+        this._notification.setTransient(false);
+
+        this._notification.addAction(_('Close'), () => {
+            this._notification.destroy();
+        });
+
+        this._notification.addAction(_('Change Settings…'), () => {
+            let app = Shell.AppSystem.get_default().lookup_app('gnome-updates-panel.desktop');
+            Main.overview.hide();
+            app.activate();
+        });
+
+        source.notify(this._notification);
+
+        this._notification.connect('destroy', () => {
+            this._notification = null;
+        });
+
+        // Now that we first detected this connection, mark it as such
+        let userSetting = this._ensureUserSetting(this._activeConnection);
+        userSetting.set_data(
+            NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME,
+            '%s'.format(GLib.get_real_time()));
+
+        this._activeConnection.commit_changes(true, null);
+    }
+
+    _updateAutomaticUpdatesItem() {
+        let state = this._getState();
+
+        if (state === AutomaticUpdatesState.DISABLED)
+            this._toggleItem.label.text = _('Turn On');
+        else
+            this._toggleItem.label.text = _('Turn Off');
+    }
+
+    _toggleAutomaticUpdates() {
+        if (!this._activeConnection)
+            return;
+
+        let userSetting = this._ensureUserSetting(this._activeConnection);
+
+        let state = this._getState();
+        let value;
+
+        if (state === AutomaticUpdatesState.IDLE ||
+            state === AutomaticUpdatesState.SCHEDULED ||
+            state === AutomaticUpdatesState.DOWNLOADING)
+            value = '0';
+        else
+            value = '1';
+
+        userSetting.set_data(NM_SETTING_ALLOW_DOWNLOADS, value);
+
+        this._activeConnection.commit_changes_async(true, null, (con, res) => {
+            this._activeConnection.commit_changes_finish(res);
+            this._updateStatus();
+        });
+    }
+
+    _ensureUserSetting(connection) {
+        let userSetting = connection.get_setting(NM.SettingUser.$gtype);
+        if (!userSetting) {
+            userSetting = new NM.SettingUser();
+            connection.add_setting(userSetting);
+        }
+        return userSetting;
+    }
+
+    _getIcon() {
+        let state = this._getState();
+        let iconName = automaticUpdatesStateToString(state);
+
+        if (!iconName)
+            return null;
+
+        let iconFile = Gio.File.new_for_uri(iconName);
+        let gicon = new Gio.FileIcon({ file: iconFile });
+
+        return gicon;
+    }
+
+    _updateVisibility() {
+        let state = this._getState();
+
+        this._item.visible = state !== AutomaticUpdatesState.DISCONNECTED;
+        this.visible = state === AutomaticUpdatesState.DOWNLOADING;
+    }
+
+    _getState() {
+        if (!this._activeConnection)
+            return AutomaticUpdatesState.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 AutomaticUpdatesState.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 AutomaticUpdatesState.DISABLED;
+
+            let metered = connectionSetting.get_metered();
+            if (metered === NM.Metered.YES || metered === NM.Metered.GUESS_YES)
+                return AutomaticUpdatesState.DISABLED;
+        }
+
+        // Without the proxy, we can't really know the state
+        if (!this._proxy)
+            return AutomaticUpdatesState.UNKNOWN;
+
+        let scheduleSet = userSetting.get_data(NM_SETTING_TARIFF_ENABLED) === '1';
+        if (!scheduleSet)
+            return AutomaticUpdatesState.IDLE;
+
+        let downloading = this._proxy.ActiveEntryCount > 0;
+        if (downloading)
+            return AutomaticUpdatesState.DOWNLOADING;
+
+        // At this point we're not downloading anything, but something
+        // might be queued
+        let downloadsQueued = this._proxy.EntryCount > 0;
+        if (downloadsQueued)
+            return AutomaticUpdatesState.SCHEDULED;
+        else
+            return AutomaticUpdatesState.IDLE;
+    }
+
+    _alreadySentNotification() {
+        let connection = this._getActiveConnection();
+
+        if (!connection)
+            return false;
+
+        let userSetting = connection.get_setting(NM.SettingUser.$gtype);
+
+        if (!userSetting)
+            return false;
+
+        return userSetting.get_data(NM_SETTING_AUTOMATIC_UPDATES_NOTIFICATION_TIME) !== null;
+    }
+
+    _getActiveConnection() {
+        let activeConnection = this._client.get_primary_connection();
+        return activeConnection ? activeConnection.get_connection() : null;
+    }
+});
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d975d32f47..f077a6b7a5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -56,6 +56,7 @@ js/ui/search.js
 js/ui/shellEntry.js
 js/ui/shellMountOperation.js
 js/ui/status/accessibility.js
+js/ui/status/automaticUpdates.js
 js/ui/status/bluetooth.js
 js/ui/status/brightness.js
 js/ui/status/dwellClick.js


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