commit 79ae41d7f63a825866ff67d581b87ad9bb7d615f
Author: Yawning Angel <[email protected]>
Date:   Fri Dec 23 05:33:27 2016 +0000

    Bug 20778: Check updates in the background.
    
    Check for updates in the background and use a Desktop Notification (via
    libnotify) to prompt the user if they want to restart to apply the
    update.
    
    Additionally this sets the env var `TOR_SANDBOX` to `linux-v0` when
    launching firefox.
---
 ChangeLog                                          |   1 +
 README.md                                          |   2 +
 .../internal/sandbox/application.go                |   1 +
 .../internal/sandbox/hugbox.go                     |   4 +-
 .../internal/ui/config/config.go                   |  22 ++
 .../sandboxed-tor-browser/internal/ui/gtk/ui.go    | 170 ++++++++++-
 .../sandboxed-tor-browser/internal/ui/install.go   |  15 +-
 .../sandboxed-tor-browser/internal/ui/launch.go    |   2 +-
 .../internal/ui/notify/notify.go                   | 322 +++++++++++++++++++++
 src/cmd/sandboxed-tor-browser/internal/ui/ui.go    |  49 ++--
 .../sandboxed-tor-browser/internal/ui/update.go    |  61 ++--
 11 files changed, 589 insertions(+), 60 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 5344f14..dbde25a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,5 @@
 Changes in version 0.0.3 - UNRELEASED:
+ * Bug 20778: Check for updates in the background.
  * Bug 20851: If the incremental update fails, fall back to the complete
    update.
  * Bug 21055: Fall back gracefully if the Adwaita theme is not present.
diff --git a/README.md b/README.md
index 9e1ec83..43e199f 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Runtime dependencies:
  * Gtk+ >= 3.14.0
  * (Optional) PulseAudio
  * (Optional) Adwaita Gtk+-2.0 theme
+ * (Optional) libnotify and a Desktop Notification daemon
 
 Build time dependencies:
 
@@ -29,6 +30,7 @@ Build time dependencies:
  * gb (https://getgb.io/ Yes I know it's behind fucking cloudflare)
  * Go (Tested with 1.7.x)
  * libseccomp2 >= 2.2.1
+ * libnotify
 
 Things that the sandbox breaks:
 
diff --git a/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go 
b/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go
index a126e4f..2d016bb 100644
--- a/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go
+++ b/src/cmd/sandboxed-tor-browser/internal/sandbox/application.go
@@ -163,6 +163,7 @@ func RunTorBrowser(cfg *config.Config, manif 
*config.Manifest, tor *tor.Tor) (cm
        h.setenv("TOR_CONTROL_PORT", "9151")
        h.setenv("TOR_SKIP_LAUNCH", "1")
        h.setenv("TOR_NO_DISPLAY_NETWORK_SETTINGS", "1")
+       h.setenv("TOR_SANDBOX", "linux-v0")
 
        // Inject the AF_LOCAL compatibility hack stub into the filesystem, and
        // supply the relevant args required for functionality.
diff --git a/src/cmd/sandboxed-tor-browser/internal/sandbox/hugbox.go 
b/src/cmd/sandboxed-tor-browser/internal/sandbox/hugbox.go
index bbc4333..260be34 100644
--- a/src/cmd/sandboxed-tor-browser/internal/sandbox/hugbox.go
+++ b/src/cmd/sandboxed-tor-browser/internal/sandbox/hugbox.go
@@ -341,8 +341,8 @@ func (h *hugbox) run() (*exec.Cmd, error) {
                Debugf("sandbox: bwrap pid is: %v", cmd.Process.Pid)
                Debugf("sandbox: child pid is: %v", info.Pid)
 
-               // This is more useful to us, since it's fork of bubblewrap 
that will
-               // execvp.
+               // This is more useful to us, since it's the bubblewrap child 
inside
+               // the container.
                cmd.Process.Pid = info.Pid
 
                doneCh <- nil
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
index 9ffbd5c..bddc073 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
@@ -25,6 +25,7 @@ import (
        "os"
        "path/filepath"
        "runtime"
+       "time"
 
        butils "git.schwanenlied.me/yawning/bulb.git/utils"
        xdg "github.com/cep21/xdgbasedir"
@@ -299,6 +300,10 @@ type Config struct {
        // Locale is the Tor Browser locale to install ("en-US", "ja").
        Locale string `json:"locale,omitempty"`
 
+       // LastUpdateCheck is the UNIX time when the last update check was
+       // sucessfully completed.
+       LastUpdateCheck int64 `json:"lastUpdateCheck,omitEmpty"`
+
        // ForceUpdate is set if the installed bundle is known to be obsolete.
        ForceUpdate bool `json:"forceUpdate"`
 
@@ -375,6 +380,23 @@ func (cfg *Config) SetFirstLaunch(b bool) {
        }
 }
 
+// NeedsUpdateCheck returns true if the bundle needs to be checked for updates,
+// and possibly updated.
+func (cfg *Config) NeedsUpdateCheck() bool {
+       const updateInterval = 60 * 60 * 2 // 2 hours, TBB behavior.
+       now := time.Now().Unix()
+       return (now > cfg.LastUpdateCheck+updateInterval) || 
cfg.LastUpdateCheck > now
+}
+
+// SetLastUpdateCheck sets the last update check time and marks the config
+// dirty.
+func (cfg *Config) SetLastUpdateCheck(t int64) {
+       if cfg.LastUpdateCheck != t {
+               cfg.LastUpdateCheck = t
+               cfg.isDirty = true
+       }
+}
+
 // SetForceUpdate sets the bundle as needed an update and marks the config
 // dirty.
 func (cfg *Config) SetForceUpdate(b bool) {
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go
index 4e526bb..0f85c76 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go
@@ -18,17 +18,24 @@
 package gtk
 
 import (
+       "log"
        "path/filepath"
        "strings"
+       "time"
 
        "github.com/gotk3/gotk3/gdk"
        gtk3 "github.com/gotk3/gotk3/gtk"
 
        "cmd/sandboxed-tor-browser/internal/data"
+       "cmd/sandboxed-tor-browser/internal/installer"
        sbui "cmd/sandboxed-tor-browser/internal/ui"
        "cmd/sandboxed-tor-browser/internal/ui/async"
+       "cmd/sandboxed-tor-browser/internal/ui/notify"
+       . "cmd/sandboxed-tor-browser/internal/utils"
 )
 
+const actionRestart = "restart"
+
 type gtkUI struct {
        sbui.Common
 
@@ -39,9 +46,19 @@ type gtkUI struct {
        installDialog  *installDialog
        configDialog   *configDialog
        progressDialog *progressDialog
+
+       updateNotification   *notify.Notification
+       updateNotificationCh chan string
 }
 
 func (ui *gtkUI) Run() error {
+       const (
+               updateMinInterval   = 30 * time.Second
+               updateCheckInterval = 2 * time.Hour
+               updateNagInterval   = 15 * time.Minute
+               gtkPumpInterval     = 1 * time.Second
+       )
+
        if err := ui.Common.Run(); err != nil {
                ui.bitch("Failed to run common UI: %v", err)
                return err
@@ -49,6 +66,9 @@ func (ui *gtkUI) Run() error {
        if ui.PrintVersion {
                return nil
        }
+       if ui.updateNotification == nil {
+               log.Printf("ui: libnotify wasn't found, no desktop 
notifications possible")
+       }
 
        if ui.NeedsInstall() || ui.ForceInstall {
                for {
@@ -80,7 +100,7 @@ func (ui *gtkUI) Run() error {
                                continue
                        }
                }
-               ui.ForceConfig = true
+               ui.ForceConfig = true // Drop back to the config on failures.
 
                // Launch
                if err := ui.launch(); err != nil {
@@ -88,12 +108,121 @@ func (ui *gtkUI) Run() error {
                                ui.bitch("Failed to launch Tor Browser: %v", 
err)
                        }
                        continue
-               } else {
-                       // Wait till the sandboxed process finishes.
-                       ui.Cfg.SetFirstLaunch(false)
-                       ui.Cfg.Sync()
-                       return ui.Sandbox.Wait()
                }
+
+               // Unset the first launch flag to skip the config on subsequent
+               // launches.
+               ui.Cfg.SetFirstLaunch(false)
+               ui.Cfg.Sync()
+
+               waitCh := make(chan error)
+               go func() {
+                       waitCh <- ui.Sandbox.Wait()
+               }()
+
+               // Determine the time for the initial update check.
+               initialUpdateInterval := updateMinInterval
+               oldScheduledTime := time.Unix(ui.Cfg.LastUpdateCheck, 
0).Add(updateCheckInterval)
+               Debugf("update: Previous scheduled update check: %v", 
oldScheduledTime)
+
+               if oldScheduledTime.After(time.Now()) {
+                       deltaT := oldScheduledTime.Sub(time.Now())
+                       if deltaT > updateMinInterval {
+                               initialUpdateInterval = deltaT
+                       }
+               }
+               Debugf("update: Initial scheduled update check: %v", 
initialUpdateInterval)
+
+               updateTimer := time.NewTimer(initialUpdateInterval)
+               defer updateTimer.Stop()
+
+               gtkPumpTicker := time.NewTicker(gtkPumpInterval)
+               defer gtkPumpTicker.Stop()
+
+               var update *installer.UpdateEntry
+       browserRunningLoop:
+               for {
+                       select {
+                       case err := <-waitCh:
+                               return err
+                       case <-gtkPumpTicker.C:
+                               // This is so stupid, but is needed for 
notification actions
+                               // to work.
+                               gtk3.MainIteration()
+                               continue
+                       case action := <-ui.updateNotificationCh:
+                               // Notification action was triggered, probably 
a restart.
+                               log.Printf("update: Received notification 
action: %v", action)
+                               if action == actionRestart {
+                                       break browserRunningLoop
+                               }
+                               continue
+                       case <-updateTimer.C:
+                       }
+
+                       updateTimer.Stop()
+
+                       // Only re-check for updates if we think we are up to 
date.
+                       // Skipping re-fetching the metadata is fine, because 
we will
+                       // do it as part of doUpdate() after the restart if it 
has
+                       // aged too much.
+                       if !ui.Cfg.ForceUpdate {
+                               log.Printf("update: Starting scheduled update 
check.")
+
+                               // Check for an update in the background.
+                               async := async.NewAsync()
+                               async.UpdateProgress = func(s string) {}
+
+                               go func() {
+                                       update = ui.CheckUpdate(async)
+                                       async.Done <- true
+                               }()
+
+                               /// Wait for the check to complete.
+                               select {
+                               case err := <-waitCh: // User exited browser 
while checking.
+                                       return err
+                               case <-async.Done:
+                               }
+
+                               if async.Err != nil {
+                                       log.Printf("update: Failed background 
update check: %v", async.Err)
+                               }
+
+                               if update != nil {
+                                       log.Printf("update: An update is 
available: %v", update.DisplayVersion)
+                               } else {
+                                       log.Printf("update: The bundle is up to 
date")
+                               }
+                       }
+
+                       if ui.Cfg.ForceUpdate {
+                               log.Printf("update: Displaying notification.")
+                               ui.notifyUpdate(update)
+                               updateTimer.Reset(updateNagInterval)
+                       } else {
+                               updateTimer.Reset(updateCheckInterval)
+                       }
+               }
+
+               // If we are here, the user wants to restart to apply an update.
+               gtkPumpTicker.Stop()
+
+               if ui.updateNotification != nil {
+                       ui.updateNotification.Close()
+               }
+
+               // Kill the browser.  It's not as if firefox does the right 
thing on
+               // SIGTERM/SIGINT and we have the pid of the bubblewrap child 
instead
+               // of the firefox process anyway...
+               //
+               // https://bugzilla.mozilla.org/show_bug.cgi?id=336193
+               ui.Sandbox.Process.Kill()
+               <-waitCh
+
+               ui.PendingUpdate = update
+               ui.ForceConfig = false
+               ui.NoKillTor = true // Don't re-lauch tor on the first pass.
        }
 }
 
@@ -101,6 +230,12 @@ func (ui *gtkUI) Term() {
        // By the time this is run, we have exited the Gtk+ event loop, so we
        // can assume we have exclusive ownership of the UI state.
        ui.Common.Term()
+
+       if ui.updateNotification != nil {
+               ui.updateNotification.Close()
+               ui.updateNotification = nil
+               notify.Uninit()
+       }
 }
 
 func Init() (sbui.UI, error) {
@@ -150,6 +285,16 @@ func Init() (sbui.UI, error) {
                }
        }
 
+       // Initialize the Desktop Notification interface.
+       if err = notify.Init("Sandboxed Tor Browser"); err == nil {
+               ui.updateNotification = notify.New("", "", ui.iconPixbuf)
+               ui.updateNotification.SetTimeout(15 * 1000)
+               ui.updateNotification.AddAction(actionRestart, "Restart Now")
+               ui.updateNotificationCh = ui.updateNotification.ActionChan()
+       } else {
+               ui.updateNotificationCh = make(chan string)
+       }
+
        return ui, nil
 }
 
@@ -159,7 +304,7 @@ func (ui *gtkUI) onDestroy() {
 
 func (ui *gtkUI) launch() error {
        // If we don't need to update, and would just launch, quash the UI.
-       checkUpdate := ui.Cfg.ForceUpdate
+       checkUpdate := ui.Cfg.ForceUpdate || ui.Cfg.NeedsUpdateCheck()
        squelchUI := !checkUpdate && ui.Cfg.UseSystemTor
 
        async := async.NewAsync()
@@ -184,6 +329,17 @@ func (ui *gtkUI) bitch(format string, a ...interface{}) {
        ui.forceRedraw()
 }
 
+func (ui *gtkUI) notifyUpdate(update *installer.UpdateEntry) {
+       if update == nil {
+               panic("ui: notifyUpdate called with no update metadata")
+       }
+
+       if ui.updateNotification != nil {
+               ui.updateNotification.Update("A Tor Browser update is 
available.", "Please restart to update to version "+update.DisplayVersion+".", 
ui.iconPixbuf)
+               ui.updateNotification.Show()
+       }
+}
+
 func (ui *gtkUI) pixbufFromAsset(asset string) (*gdk.Pixbuf, error) {
        d, err := data.Asset(asset)
        if err != nil {
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/install.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/install.go
index ac9f246..4ae4256 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/install.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/install.go
@@ -20,12 +20,15 @@ import (
        "fmt"
        "io/ioutil"
        "log"
+       "net"
        "os"
        "path/filepath"
        "runtime"
+       "time"
 
        "cmd/sandboxed-tor-browser/internal/data"
        "cmd/sandboxed-tor-browser/internal/installer"
+       "cmd/sandboxed-tor-browser/internal/tor"
        . "cmd/sandboxed-tor-browser/internal/ui/async"
        "cmd/sandboxed-tor-browser/internal/ui/config"
        "cmd/sandboxed-tor-browser/internal/utils"
@@ -59,8 +62,14 @@ func (c *Common) DoInstall(async *Async) {
        }
 
        // Get the Dial() routine used to reach the external network.
-       dialFn, err := c.launchTor(async, true)
-       if err != nil {
+       var dialFn dialFunc
+       if err := c.launchTor(async, true); err != nil {
+               async.Err = err
+               return
+       }
+       if dialFn, err = c.getTorDialFunc(); err == tor.ErrTorNotRunning {
+               dialFn = net.Dial
+       } else if err != nil {
                async.Err = err
                return
        }
@@ -85,6 +94,7 @@ func (c *Common) DoInstall(async *Async) {
                        return
                }
        }
+       checkAt := time.Now().Unix()
 
        log.Printf("install: Version: %v Downloads: %v", version, downloads)
 
@@ -143,6 +153,7 @@ func (c *Common) DoInstall(async *Async) {
        }
 
        // Set the appropriate bits in the config.
+       c.Cfg.SetLastUpdateCheck(checkAt)
        c.Cfg.SetForceUpdate(false)
        c.Cfg.SetFirstLaunch(true)
 
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/launch.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/launch.go
index 49b7663..e929fa7 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/launch.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/launch.go
@@ -63,7 +63,7 @@ func (c *Common) DoLaunch(async *Async, checkUpdates bool) {
        // Start tor if required.
        log.Printf("launch: Connecting to the Tor network.")
        async.UpdateProgress("Connecting to the Tor network.")
-       if _, async.Err = c.launchTor(async, false); async.Err != nil {
+       if async.Err = c.launchTor(async, false); async.Err != nil {
                return
        }
 
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/notify/notify.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/notify/notify.go
new file mode 100644
index 0000000..50f156c
--- /dev/null
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/notify/notify.go
@@ -0,0 +1,322 @@
+// notify.go - Desktop Notification interface.
+// Copyright (C) 2016  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// Package notify interfaces with the Destop Notification daemon, as defined
+// by the desktop notifications spec, via the libnotify library.
+//
+// Note: Instead of linking libnotify, the library is opportunistically loaded
+// at runtime via dlopen().  This is not applied to glib/gdk as those are
+// pulled in by virtue of the application being a Gtk app.
+package notify
+
+// #cgo pkg-config: glib-2.0 gdk-3.0
+// #cgo LDFLAGS: -ldl
+//
+// #include <libnotify/notify.h>
+// #include <dlfcn.h>
+// #include <stdio.h>
+// #include <stdlib.h>
+// #include <string.h>
+// #include <assert.h>
+//
+// extern void actionCallbackHandler(void *, char *);
+//
+// static int initialized = 0;
+// static int supports_actions = 0;
+//
+// static gboolean (*init_fn)(const char *) = NULL;
+// static void (*uninit_fn)(void) = NULL;
+// static GList *(*get_server_caps_fn)(void) = NULL;
+//
+// static NotifyNotification *(*new_fn)(const char *, const char *, const char 
*) = NULL;
+// static void (*update_fn) (NotifyNotification *, const char *, const char *, 
const char *) = NULL;
+// static gboolean (*show_fn)(NotifyNotification *, GError **) = NULL;
+// static void (*set_timeout_fn)(NotifyNotification *, gint timeout) = NULL;
+// static void (*set_image_fn)(NotifyNotification *, GdkPixbuf *) = NULL;
+// static void (*add_action_fn)(NotifyNotification *, const char *, const char 
*, NotifyActionCallback, gpointer, GFreeFunc) = NULL;
+// static void (*close_fn)(NotifyNotification *, GError **) = NULL;
+//
+// static void
+// notify_action_cb(NotifyNotification *notification, char *action, gpointer 
user_data) {
+//   actionCallbackHandler(user_data, action);
+// }
+//
+// static int
+// init_libnotify(const char *app_name) {
+//    void *handle = NULL;
+//    GList *caps;
+//
+//    if (initialized != 0) {
+//      return initialized;
+//    }
+//    initialized = -1;
+//
+//    handle = dlopen("libnotify.so.4", RTLD_LAZY);
+//    if (handle == NULL) {
+//      fprintf(stderr, "ui: Failed to dlopen() 'libnotify.so.4': %s\n", 
dlerror());
+//      goto out;
+//    }
+//
+//    // Load all the symbols that we need.
+//    if ((init_fn = dlsym(handle, "notify_init")) == NULL) {
+//      fprintf(stderr, "ui: Failed to find 'notify_init()': %s\n", dlerror());
+//      goto out;
+//    }
+//    if ((uninit_fn = dlsym(handle, "notify_uninit")) == NULL) {
+//      fprintf(stderr, "ui: Failed to find 'notify_uninit()': %s\n", 
dlerror());
+//      goto out;
+//    }
+//    if ((get_server_caps_fn = dlsym(handle, "notify_get_server_caps")) == 
NULL) {
+//      fprintf(stderr, "ui: Failed to find 'notify_get_server_caps()': %s\n", 
dlerror());
+//      goto out;
+//    }
+//    if ((new_fn = dlsym(handle, "notify_notification_new")) == NULL) {
+//      fprintf(stderr, "ui: Failed to find 'notify_notification_new()': 
%s\n", dlerror());
+//      goto out;
+//    }
+//    if ((update_fn = dlsym(handle, "notify_notification_update")) == NULL) {
+//      fprintf(stderr, "ui: Failed to find 'notify_notification_update()': 
%s\n", dlerror());
+//      goto out;
+//    }
+//    if ((show_fn = dlsym(handle, "notify_notification_show")) == NULL) {
+//      fprintf(stderr, "ui: Failed to find 'notify_notification_show()': 
%s\n", dlerror());
+//      goto out;
+//    }
+//    if ((set_timeout_fn = dlsym(handle, "notify_notification_set_timeout")) 
== NULL) {
+//      fprintf(stderr, "ui: Failed to find 
'notify_notification_set_timeout()': %s\n", dlerror());
+//      goto out;
+//    }
+//    if ((set_image_fn = dlsym(handle, 
"notify_notification_set_image_from_pixbuf")) == NULL) {
+//      fprintf(stderr, "ui: Failed to 
find'notify_notification_set_image_from_pixbuf': %s\n", dlerror());
+//      goto out;
+//    }
+//    if ((add_action_fn = dlsym(handle, "notify_notification_add_action")) == 
 NULL) {
+//      fprintf(stderr, "ui: Failed to find'notify_notification_add_action': 
%s\n", dlerror());
+//      goto out;
+//    }
+//    if ((close_fn = dlsym(handle, "notify_notification_close")) == NULL) {
+//      fprintf(stderr, "ui: Failed to find'notify_notification_close': %s\n", 
dlerror());
+//      goto out;
+//    }
+//
+//    // Initialize libnotify.
+//    if (init_fn(app_name) == TRUE) {
+//      initialized = 0;
+//    }
+//
+//    // Figure out if we are talking to the stupid fucking Ubuntu notification
+//    // daemon, which doesn't support actions.
+//    caps = get_server_caps_fn();
+//    if (caps != NULL) {
+//      GList *c;
+//      for (c = caps; c != NULL; c = c->next) {
+//         if (strcmp((char*)c->data, "actions") == 0) {
+//           supports_actions = 1;
+//         }
+//      }
+//      g_list_foreach(caps, (GFunc)g_free, NULL);
+//      g_list_free(caps);
+//    }
+//
+// out:
+//    if (initialized != 0 && handle != NULL) {
+//      dlclose(handle);
+//   }
+//    return initialized;
+// }
+//
+// static void
+// uninit_libnotify(void) {
+//   if (initialized != 0) {
+//     return;
+//   }
+//   initialized = -1;
+//   uninit_fn();
+// }
+//
+// static NotifyNotification *
+// n_new(const char *summary, const char *body) {
+//   if (initialized != 0) {
+//     return NULL;
+//   }
+//   return new_fn(summary, body, NULL);
+// }
+//
+// static void
+// n_update(NotifyNotification *n, const char *summary, const char *body) {
+//   assert(n != NULL);
+//   update_fn(n, summary, body, NULL);
+// }
+//
+// static void
+// n_show(NotifyNotification *n) {
+//   assert(n != NULL);
+//   show_fn(n, NULL);
+// }
+//
+// static void
+// n_set_timeout(NotifyNotification *n, int timeout) {
+//   assert(n != NULL);
+//   set_timeout_fn(n, timeout);
+// }
+//
+// static void
+// n_set_image(NotifyNotification *n, void *pixbuf) {
+//   assert(n != NULL);
+//   set_image_fn(n, GDK_PIXBUF(pixbuf));
+// }
+//
+// static void
+// n_add_action(NotifyNotification *n, const char *action, const char *label, 
void *user_data) {
+//   assert(n != NULL);
+//   if (supports_actions) {
+//     add_action_fn(n, action, label, 
NOTIFY_ACTION_CALLBACK(notify_action_cb), user_data, NULL);
+//   }
+// }
+//
+// static void
+// n_close(NotifyNotification *n) {
+//   assert(n != NULL);
+//   close_fn(n, NULL);
+// }
+import "C"
+
+import (
+       "errors"
+       "runtime"
+       "unsafe"
+
+       "github.com/gotk3/gotk3/gdk"
+)
+
+const (
+       // EXPIRES_DEFAULT is the default expiration timeout.
+       EXPIRES_DEFAULT = C.NOTIFY_EXPIRES_DEFAULT
+
+       // EXPIRES_NEVER is the infinite expiration timeout.
+       EXPIRES_NEVER = C.NOTIFY_EXPIRES_NEVER
+)
+
+var callbackChans map[unsafe.Pointer]chan string
+
+// Notification is a `NotifyNotification` instance.
+type Notification struct {
+       n *C.NotifyNotification
+}
+
+// ActionChan returns the channel that actions will be written to.
+func (n *Notification) ActionChan() chan string {
+       return callbackChans[unsafe.Pointer(n.n)]
+}
+
+// Update updates the notification.  Like the libnotify counterpart, Show()
+// must be called to refresh the notification.
+func (n *Notification) Update(summary, body string, icon *gdk.Pixbuf) {
+       cSummary := C.CString(summary)
+       defer C.free(unsafe.Pointer(cSummary))
+       cBody := C.CString(body)
+       defer C.free(unsafe.Pointer(cBody))
+
+       C.n_update(n.n, cSummary, cBody)
+       n.SetImage(icon)
+}
+
+// Show (re-)displays the notification.
+func (n *Notification) Show() {
+       C.n_show(n.n)
+}
+
+// SetTimeout sets the notification timeout to the value specified in
+// milliseconds.
+func (n *Notification) SetTimeout(timeout int) {
+       C.n_set_timeout(n.n, C.int(timeout))
+}
+
+// SetImage sets the notification image to the specified GdkPixbuf.
+func (n *Notification) SetImage(pixbuf *gdk.Pixbuf) {
+       C.n_set_image(n.n, unsafe.Pointer(pixbuf.GObject))
+}
+
+// AddAction adds an action to the notification.
+func (n *Notification) AddAction(action, label string) {
+       cAction := C.CString(action)
+       defer C.free(unsafe.Pointer(cAction))
+       cLabel := C.CString(label)
+       defer C.free(unsafe.Pointer(cLabel))
+
+       C.n_add_action(n.n, cAction, cLabel, unsafe.Pointer(n))
+}
+
+// Close hides the specified nitification.
+func (n *Notification) Close() {
+       C.n_close(n.n)
+}
+
+// ErrNotSupported is the error returned when libnotify is missing or has
+// failed to initialize.
+var ErrNotSupported = errors.New("libnotify not installed or service not 
running")
+
+// Init initializes the Desktop Notification interface.
+func Init(appName string) error {
+       cstr := C.CString(appName)
+       defer C.free(unsafe.Pointer(cstr))
+       if C.init_libnotify(cstr) != 0 {
+               return ErrNotSupported
+       }
+       return nil
+}
+
+// Uninit cleans up the Desktop Notification interface, prior to termination.
+func Uninit() {
+       C.uninit_libnotify()
+}
+
+// New returns a new Notification.
+func New(summary, body string, icon *gdk.Pixbuf) *Notification {
+       cSummary := C.CString(summary)
+       defer C.free(unsafe.Pointer(cSummary))
+       cBody := C.CString(body)
+       defer C.free(unsafe.Pointer(cBody))
+
+       n := new(Notification)
+       n.n = C.n_new(cSummary, cBody)
+       if n.n == nil {
+               panic("libnotify: notify_notification_new() returned NULL")
+       }
+       callbackChans[unsafe.Pointer(n.n)] = make(chan string)
+
+       runtime.SetFinalizer(n, func(n *Notification) {
+               delete(callbackChans, unsafe.Pointer(n.n))
+               C.g_object_unref(n.n)
+       })
+       n.SetImage(icon)
+
+       return n
+}
+
+//export actionCallbackHandler
+func actionCallbackHandler(nPtr unsafe.Pointer, actionPtr *C.char) {
+       action := C.GoString(actionPtr)
+       n := (*Notification)(nPtr)
+       ch := n.ActionChan()
+       go func() {
+               ch <- action
+       }()
+}
+
+func init() {
+       callbackChans = make(map[unsafe.Pointer]chan string)
+}
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/ui.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/ui.go
index ba0f293..d30d74c 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/ui.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/ui.go
@@ -104,8 +104,11 @@ type Common struct {
        logPath  string
        logFile  *os.File
 
+       PendingUpdate *installer.UpdateEntry
+
        ForceInstall   bool
        ForceConfig    bool
+       NoKillTor      bool
        AdvancedConfig bool
        PrintVersion   bool
 }
@@ -272,7 +275,19 @@ func (c *Common) NeedsInstall() bool {
 
 type dialFunc func(string, string) (net.Conn, error)
 
-func (c *Common) launchTor(async *Async, onlySystem bool) (dialFunc, error) {
+func (c *Common) getTorDialFunc() (dialFunc, error) {
+       if c.tor == nil {
+               return nil, tor.ErrTorNotRunning
+       }
+
+       dialer, err := c.tor.Dialer()
+       if err != nil {
+               return nil, err
+       }
+       return dialer.Dial, nil
+}
+
+func (c *Common) launchTor(async *Async, onlySystem bool) error {
        var err error
        defer func() {
                if async.Err != nil && c.tor != nil {
@@ -281,23 +296,27 @@ func (c *Common) launchTor(async *Async, onlySystem bool) 
(dialFunc, error) {
                }
        }()
 
-       if c.tor != nil {
+       if c.tor != nil && !c.NoKillTor {
                log.Printf("launch: Shutting down old tor.")
                c.tor.Shutdown()
                c.tor = nil
        }
 
-       if c.Cfg.UseSystemTor {
+       if c.tor != nil && c.NoKillTor {
+               // Only the first re-launch should be skipped.
+               log.Printf("launch: Reusing old tor.")
+               c.NoKillTor = false
+       } else if c.Cfg.UseSystemTor {
                if c.tor, err = tor.NewSystemTor(c.Cfg); err != nil {
                        async.Err = err
-                       return nil, err
+                       return err
                }
        } else if !onlySystem {
                // Build the torrc.
                torrc, err := tor.CfgToSandboxTorrc(c.Cfg, Bridges)
                if err != nil {
                        async.Err = err
-                       return nil, err
+                       return err
                }
 
                os.Remove(filepath.Join(c.Cfg.TorDataDir, "control_port"))
@@ -306,36 +325,28 @@ func (c *Common) launchTor(async *Async, onlySystem bool) 
(dialFunc, error) {
                cmd, err := sandbox.RunTor(c.Cfg, c.Manif, torrc)
                if err != nil {
                        async.Err = err
-                       return nil, err
+                       return err
                }
 
                async.UpdateProgress("Waiting on Tor bootstrap.")
                c.tor = tor.NewSandboxedTor(c.Cfg, cmd)
                if err = c.tor.DoBootstrap(c.Cfg, async); err != nil {
                        async.Err = err
-                       return nil, err
+                       return err
                }
        } else if !(c.NeedsInstall() || c.ForceInstall) {
                // That's odd, we only asked for a system tor, but we should be 
capable
                // of launching tor ourselves.  Don't use a direct connection.
                err = fmt.Errorf("tor bootstrap would be skipped, when we could 
launch")
                async.Err = err
-               return nil, err
+               return err
        }
 
-       // If we managed to launch tor...
-       if c.tor != nil {
-               // Query the socks port, setup the dialer.
-               if dialer, err := c.tor.Dialer(); err != nil {
-                       async.Err = err
-                       return nil, err
-               } else {
-                       return dialer.Dial, nil
-               }
+       if c.tor != nil || onlySystem {
+               return nil
        }
 
-       // We must be installing, without a tor daemon already running.
-       return net.Dial, nil
+       return tor.ErrTorNotRunning
 }
 
 type lockFile struct {
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/update.go 
b/src/cmd/sandboxed-tor-browser/internal/ui/update.go
index 1994e64..da69562 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/update.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/update.go
@@ -22,6 +22,7 @@ import (
        "encoding/hex"
        "fmt"
        "log"
+       "time"
 
        "cmd/sandboxed-tor-browser/internal/installer"
        "cmd/sandboxed-tor-browser/internal/sandbox"
@@ -41,13 +42,13 @@ func (c *Common) CheckUpdate(async *Async) 
*installer.UpdateEntry {
                async.Err = tor.ErrTorNotRunning
                return nil
        }
-       dialer, err := c.tor.Dialer()
+       dialFn, err := c.getTorDialFunc()
        if err != nil {
                async.Err = err
                return nil
        }
 
-       client := newHPKPGrabClient(dialer.Dial)
+       client := newHPKPGrabClient(dialFn)
 
        // Determine where the update metadata should be fetched from.
        updateURLs := []string{}
@@ -90,15 +91,21 @@ func (c *Common) CheckUpdate(async *Async) 
*installer.UpdateEntry {
                async.Err = fmt.Errorf("failed to download update metadata")
                return nil
        }
+       checkAt := time.Now().Unix()
 
        // If there is an update, tag the installed bundle as stale...
        if update == nil {
                log.Printf("update: Installed bundle is current.")
                c.Cfg.SetForceUpdate(false)
+       } else if !c.Manif.BundleUpdateVersionValid(update.AppVersion) {
+               log.Printf("update: Update server provided a downgrade: '%v'", 
update.AppVersion)
+               async.Err = fmt.Errorf("update server provided a downgrade: 
'%v'", update.AppVersion)
+               return nil
        } else {
                log.Printf("update: Installed bundle needs updating.")
                c.Cfg.SetForceUpdate(true)
        }
+       c.Cfg.SetLastUpdateCheck(checkAt)
 
        // ... and flush the config.
        if async.Err = c.Cfg.Sync(); async.Err != nil {
@@ -112,22 +119,17 @@ func (c *Common) CheckUpdate(async *Async) 
*installer.UpdateEntry {
 // validates it with the hash in the patch datastructure, and the known MAR
 // signing keys.
 func (c *Common) FetchUpdate(async *Async, patch *installer.Patch) []byte {
-       var dialFn dialFunc
-
        // Launch the tor daemon if needed.
        if c.tor == nil {
-               dialFn, async.Err = c.launchTor(async, false)
+               async.Err = c.launchTor(async, false)
                if async.Err != nil {
                        return nil
                }
-       } else {
-               // Otherwise, retreive the dialer.
-               dialer, err := c.tor.Dialer()
-               if err != nil {
-                       async.Err = err
-                       return nil
-               }
-               dialFn = dialer.Dial
+       }
+       dialFn, err := c.getTorDialFunc()
+       if err != nil {
+               async.Err = err
+               return nil
        }
 
        // Download the MAR file.
@@ -178,20 +180,20 @@ func (c *Common) doUpdate(async *Async) {
                patchComplete = "complete"
        )
 
-       // Check for updates.
-       update := c.CheckUpdate(async)
-       if async.Err != nil || update == nil {
-               // Something either broke, or the bundle is up to date.  The 
caller
-               // needs to check async.Err, and either way there's nothing 
more that
-               // can be done.
-               return
-       }
-
-       // Ensure that the update entry version is actually neweer.
-       if !c.Manif.BundleUpdateVersionValid(update.AppVersion) {
-               log.Printf("update: Update server provided a downgrade: '%v'", 
update.AppVersion)
-               async.Err = fmt.Errorf("update server provided a downgrade: 
'%v'", update.AppVersion)
-               return
+       // Check for updates, unless we have sufficiently fresh metatdata 
already.
+       var update *installer.UpdateEntry
+       if c.PendingUpdate != nil && !c.Cfg.NeedsUpdateCheck() {
+               update = c.PendingUpdate
+               c.PendingUpdate = nil
+       } else {
+               update = c.CheckUpdate(async)
+               if async.Err != nil || update == nil {
+                       // Something either broke, or the bundle is up to date. 
 The caller
+                       // needs to check async.Err, and either way there's 
nothing more that
+                       // can be done.
+                       return
+               }
+               c.PendingUpdate = nil
        }
 
        // Figure out the best MAR to download.
@@ -258,7 +260,7 @@ func (c *Common) doUpdate(async *Async) {
                        continue
                }
 
-               // Failues past this point are catastrophic in that, the on-disk
+               // Failures past this point are catastrophic in that, the 
on-disk
                // bundle is up to date, but the post-update tasks have failed.
 
                // Reinstall the autoconfig stuff.
@@ -283,8 +285,9 @@ func (c *Common) doUpdate(async *Async) {
                if !c.Cfg.UseSystemTor {
                        log.Printf("launch: Reconnecting to the Tor network.")
                        async.UpdateProgress("Reconnecting to the Tor network.")
-                       _, async.Err = c.launchTor(async, false)
+                       async.Err = c.launchTor(async, false)
                }
+
                return
        }
 

_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to