[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [sandboxed-tor-browser/master] Bug 20778: Check updates in the background.
commit 79ae41d7f63a825866ff67d581b87ad9bb7d615f
Author: Yawning Angel <yawning@xxxxxxxxxxxxxxx>
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
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits