Commit d6d223d0 authored by Michael Terry's avatar Michael Terry

Autostart support for snap

- Use snapcraft's support for registering an autostart command
  to start our monitor
- When deja-dup starts up, start the monitor if it isn't running
- Have the monitor shut down when periodic is disabled
- Only do a prompt check on monitor startup - this lets us have
  the monitor only care about the periodic logic after startup
parent d82406bc
[Desktop Entry]
Version=1.0
Version=1.1
Name=Backup Monitor
Comment=Schedules backups at regular intervals
......@@ -9,7 +9,6 @@ Icon=@icon@
Exec=@pkglibexecdir@/deja-dup-monitor
X-GNOME-Autostart-Delay=120
X-GNOME-AutoRestart=true
StartupNotify=false
NoDisplay=true
......
......@@ -24,9 +24,10 @@ namespace DejaDup {
// Convenience class for adding automatic backup switch to pref shells
public class PreferencesPeriodicSwitch : Gtk.Switch
{
DejaDup.FilteredSettings settings;
construct
{
var settings = DejaDup.get_settings();
settings = DejaDup.get_settings();
settings.bind(DejaDup.PERIODIC_KEY, this, "active", SettingsBindFlags.DEFAULT);
}
}
......
## Multiple monitor daemons
Profiles are a way to get parallel-installed deja-dups. They're used for testing and for containerized deployments
like flatpak and snap.
But parallel-installed deja-dups means multiple monitor daemons. Which, since they all share the same gsettings, would
mean kicking off multiple backups at the same time.
To avoid this, we claim the normal org.gnome.DejaDup.Monitor bus name regardless of what the profile is. We still kick
off the specific profile bus name for the actual backup.
The first monitor to start wins the race. It's possible to put our thumb on the scale by adjusing the installed
autostart desktop file's X-GNOME-Autostart-Delay value.
## Why do we need a monitor program?
This program is a bit of a hack. I'm not terribly happy with it, but it's a comprimise of sorts. I want a periodic scheduler that 'just works' in terms of user perceptions. So, we ideally would:
a) Backup at regular intervals
a.1) preferrably early in the morning (probably configurable by user)
a.2) or roughly ASAP if computer isn't on when scheduled
b) If user isn't logged in, still backup
c) If user is logged in (or becomes logged in while backup is still going),
show notification icon allowing reschedule/cancel
1. Backup at regular intervals
1. preferrably early in the morning (probably configurable by user)
2. or roughly ASAP if computer isn't on when scheduled
2. If user isn't logged in, still backup
3. If user is logged in (or becomes logged in while backup is still going),
show notification icon allowing reschedule/cancel
Given these desired traits, what can we do?
(c) suggests that we need a constantly-running program in the user's session that waits for the scheduled backup and displays an icon. This is unavoidable. It should have as small a footprint as possible.
(3) suggests that we need a constantly-running program in the user's session that waits for the scheduled backup and displays an icon. This is unavoidable. It should have as small a footprint as possible.
(b) suggests we need to run a scheduler as root, most naturally as part of a cron job. Since we can't drop a file in that says 'add this job to each user's crontab' (like we can do with /etc/xdg/autostart for autostart tasks), we'd need to drop in a /etc/cron.d file that runs at the minimum period we allow the user to set (probably a day), checks *all* users' settings to see if they are due for a backup, and kicks off a backup for/as them if needed. But we're not guaranteed to satisfy (a.2) unless user is running anacron.
(2) suggests we need to run a scheduler as root, most naturally as part of a cron job. Since we can't drop a file in that says 'add this job to each user's crontab' (like we can do with /etc/xdg/autostart for autostart tasks), we'd need to drop in a /etc/cron.d file that runs at the minimum period we allow the user to set (probably a day), checks *all* users' settings to see if they are due for a backup, and kicks off a backup for/as them if needed. But we're not guaranteed to satisfy (1.2) unless user is running anacron.
We could avoid the hackery of 'one root task that checks all users' by, when keys that control periodic settings are changed (watched by (c) daemon), doing "crontab -l | cronttab -" goofiness. That would correctly install into the user's crontab and allow nice system cron permission control and such. I'm not sure if that's a bonus or not (the silent refusal to run might confuse user, but surely that's a corner case).
We could avoid the hackery of 'one root task that checks all users' by, when keys that control periodic settings are changed (watched by (3) daemon), doing "crontab -l | cronttab -" goofiness. That would correctly install into the user's crontab and allow nice system cron permission control and such. I'm not sure if that's a bonus or not (the silent refusal to run might confuse user, but surely that's a corner case).
The above points about (b) assumes that the user has a properly set up cron. These days that's probably true, but none-the-less, it would be nice if we could avoid that.
The above points about (2) assumes that the user has a properly set up cron. These days that's probably true, but none-the-less, it would be nice if we could avoid that.
If we went with a cron-based solution, we couldn't truly be sure the user's jobs were being run, and we couldn't be sure if they would be run 'asap' if the computer isn't running at job time. This can be fixed by depending on anacron.
Another issue with (b) is that it requires us to have saved the user's passwords -- and more importantly -- be able to get them from gnome-keyring without a gnome session. This is probably a deal breaker. Without keyring support, we'd have to hardcode passwords...
Another issue with (2) is that it requires us to have saved the user's passwords -- and more importantly -- be able to get them from gnome-keyring without a gnome session. This is probably a deal breaker. Without keyring support, we'd have to hardcode passwords...
Alternatively, we could have the already-required user-space (c) watcher program kick of backups by watching the clock itself. Our scheduling is simple enough, it would suffice. This sacrifices (b) altogether. Which sucks. But we could kick off the build the next time the user does log in. Such a choice would put us squarely in the 'single-user laptop/desktop' use-case, foregoing the 'making backup for administrators easier' camp. I'm mostly OK with this.
Alternatively, we could have the already-required user-space (3) watcher program kick of backups by watching the clock itself. Our scheduling is simple enough, it would suffice. This sacrifices (2) altogether. Which sucks. But we could kick off the build the next time the user does log in. Such a choice would put us squarely in the 'single-user laptop/desktop' use-case, foregoing the 'making backup for administrators easier' camp. I'm mostly OK with this.
For now, we're going to do what the paragraph above suggests: don't use cron, do it ourselves. This issue should be revisited in the future.
......@@ -21,6 +21,7 @@ using GLib;
class Monitor : Object {
static MainLoop loop;
static uint timeout_id;
static uint netcheck_id;
static bool reactive_check;
......@@ -105,30 +106,6 @@ static TimeSpan time_until(DateTime date)
return date.difference(new DateTime.now_local());
}
static async void call_remote(string action, string[] args = {})
{
var vargs = new VariantBuilder(new VariantType("av"));
foreach (string arg in args) {
vargs.add("v", new Variant.string(arg));
}
var platform_args = new VariantBuilder(new VariantType("a{sv}"));
try {
var deja = yield new DBusProxy.for_bus(BusType.SESSION,
DBusProxyFlags.NONE,
null,
Config.APPLICATION_ID,
"/org/gnome/DejaDup" + Config.PROFILE,
"org.freedesktop.Application",
null);
yield deja.call("ActivateAction",
new Variant("(sava{sv})", action, vargs, platform_args),
DBusCallFlags.NONE, -1, null);
}
catch (Error e) {
warning("%s", e.message);
}
}
static async void kickoff()
{
TimeSpan wait_time;
......@@ -156,7 +133,7 @@ static async void kickoff()
if (!ready) {
debug("Postponing the backup.");
if (!was_reactive && when != null)
yield call_remote("delay", {when});
DejaDup.run_deja_dup({"--delay", when});
return;
}
......@@ -167,7 +144,7 @@ static async void kickoff()
DejaDup.update_last_run_timestamp(DejaDup.TimestampType.BACKUP);
}
else {
yield call_remote("backup-auto");
DejaDup.run_deja_dup({"--backup", "--auto"});
}
}
......@@ -177,7 +154,7 @@ static bool time_until_next_run(out TimeSpan time)
var next_date = DejaDup.next_run_date();
if (next_date == null) {
debug("Automatic backups disabled. Not scheduling a backup.");
debug("Automatic backups disabled. Stopping monitor.");
return false;
}
......@@ -222,8 +199,11 @@ static void prepare_next_run()
return;
TimeSpan wait_time;
if (!time_until_next_run(out wait_time))
if (!time_until_next_run(out wait_time)) {
// automatic backups are disabled - just quit for now
loop.quit();
return;
}
prepare_run(wait_time);
}
......@@ -241,11 +221,6 @@ static void make_first_check()
first_check = true;
DejaDup.make_prompt_check();
Timeout.add_seconds(DejaDup.get_prompt_delay(), () => {
DejaDup.make_prompt_check();
return true;
});
prepare_next_run();
}
......@@ -298,10 +273,10 @@ static int main(string[] args)
if (!DejaDup.initialize(null, null))
return 1;
var loop = new MainLoop(null, false);
loop = new MainLoop(null, false);
Idle.add(() => {
// quit if we can't get the bus name or become disconnected
Bus.own_name(BusType.SESSION, Config.APPLICATION_ID + ".Monitor",
Bus.own_name(BusType.SESSION, "org.gnome.DejaDup.Monitor",
BusNameOwnerFlags.NONE, ()=>{},
()=>{begin_monitoring();},
()=>{loop.quit();});
......
......@@ -67,13 +67,37 @@ public void destroy_widget(Gtk.Widget w)
Idle.add(() => {w.destroy(); return false;});
}
bool start_monitor_if_needed(FilteredSettings settings)
{
if (settings.get_boolean(PERIODIC_KEY)) {
var monitor_exec = Environment.get_variable("DEJA_DUP_MONITOR_EXEC");
if (monitor_exec == null || monitor_exec.length == 0) {
monitor_exec = Path.build_filename(Config.PKG_LIBEXEC_DIR, "deja-dup-monitor");
}
// Will quickly and harmlessly bail if it can't claim the bus name
run_deja_dup({}, monitor_exec);
}
// Don't need to worry about else condition: the monitor will shut itself off
// when periodic is disabled.
return Source.CONTINUE;
}
public bool gui_initialize(Gtk.Window? parent, bool show_error = true)
{
string header;
string msg;
var rv = DejaDup.initialize(out header, out msg);
if (!rv && show_error) {
if (rv) {
var settings = get_settings();
Signal.connect(settings, "changed::" + PERIODIC_KEY, (Callback)start_monitor_if_needed, null);
start_monitor_if_needed(settings);
// FIXME: ideally we'd do something more elegant than adding a ref and
// leaking this settings, but we want it to stay around for the lifetime
// of the app.
settings.ref();
}
else if (show_error) {
Gtk.MessageDialog dlg = new Gtk.MessageDialog (parent,
Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
......
......@@ -135,17 +135,21 @@ public string nice_prefix(string command)
return cmd;
}
public void run_deja_dup(string args, AppLaunchContext? ctx = null,
List<File>? files = null)
public void run_deja_dup(string[] args = {}, string exec = "deja-dup")
{
var cmd = nice_prefix("deja-dup %s".printf(args));
var flags = AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION |
AppInfoCreateFlags.SUPPORTS_URIS;
try {
var app = AppInfo.create_from_commandline(cmd, _("Déjà Dup Backup Tool"), flags);
app.launch(files, ctx);
var command = nice_prefix(exec);
string[] argv = command.split(" ");
foreach (string arg in args) {
argv += arg;
}
catch (Error e) {
try {
Process.spawn_async(null, argv, null,
SpawnFlags.SEARCH_PATH/* |
SpawnFlags.STDOUT_TO_DEV_NULL |
SpawnFlags.STDERR_TO_DEV_NULL*/,
null, null);
} catch (Error e) {
warning("%s\n", e.message);
}
}
......@@ -304,7 +308,7 @@ public bool make_prompt_check()
var now = new DateTime.now_local();
if (last_run.compare(now) <= 0) {
run_deja_dup("--prompt");
run_deja_dup({"--prompt"});
return true;
}
else
......
#!/bin/sh
# Without this line we don't seem to properly watch dconf changes.
# The snap default is this dir, plus a "snap.deja-dup" directory.
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
exec "$SNAP/libexec/deja-dup/deja-dup-monitor" "$@"
\ No newline at end of file
#!/bin/sh
# Set up the monitor daemon to autostart
AUTOSTART_DIR="$SNAP_USER_DATA/.config/autostart"
AUTOSTART_FILE="$SNAP/etc/xdg/autostart/org.gnome.DejaDup.Monitor.desktop"
mkdir -p "$AUTOSTART_DIR"
cp --update "$AUTOSTART_FILE" "$AUTOSTART_DIR"
# Without this line we don't seem to properly watch dconf changes.
# The snap default is this dir, plus a "snap.deja-dup" directory.
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export DEJA_DUP_MONITOR_EXEC="/snap/bin/deja-dup.monitor"
exec "$SNAP/bin/deja-dup" "$@"
\ No newline at end of file
......@@ -9,22 +9,33 @@ description: |
https://wiki.gnome.org/Apps/DejaDup
icon: data/icons/org.gnome.DejaDup.svg
license: GPL-3.0+
grade: devel
grade: stable
confinement: classic
base: core18
apps:
deja-dup:
command: bin/deja-dup
command: bin/wrapper
desktop: share/applications/org.gnome.DejaDup.desktop
environment:
PATH: $SNAP/bin:$PATH
PYTHONPATH: $SNAP/lib/python2.7/site-packages
XDG_DATA_DIRS: $SNAP/share:$XDG_DATA_DIRS
monitor:
command: bin/monitor.wrapper
environment:
PATH: /snap/bin:$PATH
XDG_DATA_DIRS: $SNAP/share:$XDG_DATA_DIRS
passthrough:
autostart: org.gnome.DejaDup.Monitor.desktop
parts:
dump:
plugin: dump
source: ./snap/local
duplicity:
plugin: python
python-version: python2
......@@ -34,7 +45,10 @@ parts:
- librsync-dev
stage-packages:
- librsync1
- python-boto
- python-cloudfiles
- python-gi
- python-swiftclient
deja-dup:
plugin: meson
......
......@@ -72,6 +72,7 @@ if [ "$SHNAME" = "shell" ]; then
glib-compile-schemas "${ROOTDIR}/share/glib-2.0/schemas"
export XDG_DATA_DIRS="${ROOTDIR}/share:${XDG_DATA_DIRS}"
export DEJA_DUP_MONITOR_EXEC="${BUILDDIR}/deja-dup/monitor/deja-dup-monitor"
export PATH="${BUILDDIR}/deja-dup/monitor:${BUILDDIR}/deja-dup:${PATH}"
run_shell "sh $ROOTDIR/runscript.sh"
......@@ -117,6 +118,7 @@ if [ "$SHNAME" = "shell-local" ]; then
mkdir -p "${XDG_DATA_HOME}/Trash"
export DEJA_DUP_MONITOR_EXEC="${BUILDDIR}/deja-dup/monitor/deja-dup-monitor"
export PATH="${BUILDDIR}/deja-dup/monitor:${BUILDDIR}/deja-dup:${PATH}"
else
export DEJA_DUP_TEST_SYSTEM=1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment