Commit 062fbd1a authored by Michael Terry's avatar Michael Terry

add nag page

parent b31f888b
......@@ -30,6 +30,7 @@ public const string LAST_RUN_KEY = "last-run";
public const string LAST_BACKUP_KEY = "last-backup";
public const string LAST_RESTORE_KEY = "last-restore";
public const string PROMPT_CHECK_KEY = "prompt-check";
public const string NAG_CHECK_KEY = "nag-check";
public const string PERIODIC_KEY = "periodic";
public const string PERIODIC_PERIOD_KEY = "periodic-period";
public const string DELETE_AFTER_KEY = "delete-after";
......@@ -209,11 +210,11 @@ public void make_prompt_check()
run_deja_dup("--prompt");
}
public void update_prompt_time(bool cancel = false)
private void update_time_key(string key, bool cancel)
{
var settings = DejaDup.get_settings();
if (settings.get_string(PROMPT_CHECK_KEY) == "disabled")
if (settings.get_string(key) == "disabled")
return; // never re-enable
string cur_time_str;
......@@ -226,7 +227,53 @@ public void update_prompt_time(bool cancel = false)
cur_time_str = cur_time.to_iso8601();
}
settings.set_string(PROMPT_CHECK_KEY, cur_time_str);
settings.set_string(key, cur_time_str);
}
public void update_prompt_time(bool cancel = false)
{
update_time_key(PROMPT_CHECK_KEY, cancel);
}
public void update_nag_time(bool cancel = false)
{
update_time_key(NAG_CHECK_KEY, cancel);
}
// In seconds
public int get_nag_delay()
{
TimeSpan span = 0;
if (DejaDup.in_testing_mode())
span = TimeSpan.MINUTE * 2;
else
span = TimeSpan.DAY * 30 * 2;
return (int)(span / TimeSpan.SECOND);
}
// This makes the check of whether we should remind user about their password.
public bool is_nag_time()
{
var settings = DejaDup.get_settings();
var nag = settings.get_string(NAG_CHECK_KEY);
var last_run_string = last_run_date(TimestampType.BACKUP);
if (nag == "disabled" || last_run_string == "")
return false;
else if (nag == "") {
update_nag_time();
return false;
}
TimeVal last_check_tval = TimeVal();
if (!last_check_tval.from_iso8601(nag))
return false;
var last_check = new DateTime.from_timeval_local(last_check_tval);
last_check = last_check.add_seconds(get_nag_delay());
var now = new DateTime.now_local();
return (last_check.compare(now) <= 0);
}
public string get_folder_key(SimpleSettings settings, string key)
......
......@@ -41,6 +41,7 @@ public abstract class Operation : Object
public signal void question(string title, string msg);
public signal void is_full(bool first);
public bool use_cached_password {get; protected set; default = true;}
public bool needs_password {get; set;}
public Backend backend {get; private set;}
public bool use_progress {get {return (job.flags & ToolJob.Flags.NO_PROGRESS) == 0;}
......@@ -91,6 +92,7 @@ public abstract class Operation : Object
protected string passphrase;
bool finished = false;
string saved_detail = null;
Operation chained_op = null;
construct
{
backend = Backend.get_default();
......@@ -168,12 +170,18 @@ public abstract class Operation : Object
public void cancel()
{
job.cancel();
if (chained_op != null)
chained_op.cancel();
else
job.cancel();
}
public void stop()
{
job.stop();
if (chained_op != null)
chained_op.stop();
else
job.stop();
}
protected virtual void connect_to_job()
......@@ -246,23 +254,26 @@ public abstract class Operation : Object
* Sometimes an operation wants to chain to a separate operation.
* Here is the glue to make that happen.
*/
subop.ref();
assert(chained_op == null);
chained_op = subop;
subop.done.connect((s, c, d) => {
done(s, c, combine_details(saved_detail, d));
subop.unref();
chained_op = null;
});
subop.raise_error.connect((e, d) => {raise_error(e, d);});
subop.progress.connect((p) => {progress(p);});
subop.passphrase_required.connect(() => {
needs_password = true;
passphrase_required();
subop.needs_password = needs_password;
subop.passphrase = passphrase;
if (!needs_password)
subop.set_passphrase(passphrase);
});
subop.question.connect((t, m) => {question(t, m);});
use_cached_password = subop.use_cached_password;
saved_detail = combine_details(saved_detail, detail);
subop.set_state(get_state());
job = subop.job;
action_desc_changed(desc);
progress(0);
......
......@@ -28,19 +28,40 @@ public class OperationVerify : Operation
{
File metadir;
File destdir;
bool nag;
public OperationVerify() {
Object(mode: ToolJob.Mode.RESTORE);
}
construct {
// Should we nag user about password, etc? What this really means is that
// we try to do our normal verification routine in as close an emulation
// to a fresh restore after a disaster as possible. So fresh cache, no
// saved password, etc. We do *not* explicitly unmount the backend,
// because we may not be the only consumers.
if (is_nag_time()) {
use_cached_password = false;
nag = true;
}
}
public async override void start(bool try_claim_bus = true)
{
if (nag) {
var fake_state = new State();
fake_state.backend = backend.clone();
set_state(fake_state);
}
action_desc_changed(_("Verifying backup…"));
yield base.start(try_claim_bus);
}
protected override void connect_to_job()
{
if (nag)
job.flags |= ToolJob.Flags.NO_CACHE;
string cachedir = Environment.get_user_cache_dir();
metadir = File.new_for_path(Path.build_filename(cachedir, Config.PACKAGE, "metadata"));
job.restore_files.append(metadir);
......@@ -73,6 +94,9 @@ public class OperationVerify : Operation
raise_error(_("Your backup appears to be corrupted. You should delete the backup and try again."), null);
success = false;
}
if (nag)
update_nag_time();
}
new RecursiveDelete(metadir).start();
......
......@@ -55,6 +55,7 @@ public abstract class ToolJob : Object
public enum Flags {
NO_PROGRESS,
NO_CACHE,
}
public Flags flags {get; set;}
......
......@@ -40,7 +40,7 @@ LT_INIT
PKG_PROG_PKG_CONFIG([0.24])
DD_PROG_VALAC([0.16.0], [valac-0.18 valac-0.16 valac])
DD_PROG_VALAC([0.16.0], [valac-0.16 valac])
AS_IF([test "x$(basename $VALAC)" = xvalac-0.16],
[AC_SUBST(SHARED_VALAFLAGS, ["-D HAVE_VALAC_16"])]
)
......
......@@ -47,9 +47,14 @@
</key>
<key name="prompt-check" type="s">
<default>''</default>
<_summary>The first time Déjà Dup checked whether it should prompt about backing up</_summary>
<_summary>The last time Déjà Dup checked whether it should prompt about backing up</_summary>
<_description>When a user logs in, the Déjà Dup monitor checks whether it should prompt about backing up. This is used to increase discoverability for users that don’t know about backups. This time should be either ‘disabled’ to turn off this check or in ISO 8601 format.</_description>
</key>
<key name="nag-check" type="s">
<default>''</default>
<_summary>The last time Déjà Dup checked whether it should prompt about your password</_summary>
<_description>In order to prevent you from forgetting your passwords, Déjà Dup will occasionally notify you to confirm the password. This time should be either ‘disabled’ to turn off this check or in ISO 8601 format.</_description>
</key>
<key name="delete-after" type="i">
<default>0</default>
<_summary>How long to keep backup files</_summary>
......
......@@ -39,7 +39,7 @@ public abstract class Assistant : Gtk.Window
public bool last_op_was_back {get; private set; default = false;}
public enum Type {
NORMAL, INTERRUPT, SUMMARY, PROGRESS, FINISH
NORMAL, INTERRUPT, CHECK, SUMMARY, PROGRESS, FINISH
}
Gtk.Label header_title;
......@@ -165,6 +165,11 @@ public abstract class Assistant : Gtk.Window
go_forward();
}
static bool is_interrupt_type(Type type)
{
return type == Type.INTERRUPT || type == Type.CHECK;
}
public void go_back()
{
weak List<PageInfo> next;
......@@ -172,7 +177,7 @@ public abstract class Assistant : Gtk.Window
next = interrupted.prev;
else {
next = current.prev;
while (next != null && next.data.type == Type.INTERRUPT)
while (next != null && is_interrupt_type(next.data.type))
next = next.prev;
}
......@@ -194,7 +199,7 @@ public abstract class Assistant : Gtk.Window
}
else {
next = (current == null) ? infos : current.next;
while (next != null && next.data.type == Type.INTERRUPT)
while (next != null && is_interrupt_type(next.data.type))
next = next.next;
}
......@@ -309,6 +314,11 @@ public abstract class Assistant : Gtk.Window
forward_text = _("Co_ntinue");
}
break;
case Type.CHECK:
show_close = true;
show_forward = true;
forward_text = C_("verb", "_Test");
break;
case Type.PROGRESS:
show_cancel = true;
show_resume = true;
......
......@@ -45,11 +45,13 @@ public abstract class AssistantOperation : Assistant
protected StatusIcon status_icon;
protected bool succeeded = false;
Gtk.Entry nag_entry;
Gtk.Entry encrypt_entry;
Gtk.Entry encrypt_confirm_entry;
Gtk.RadioButton encrypt_enabled;
Gtk.CheckButton encrypt_remember;
protected Gtk.Widget password_page {get; private set;}
protected Gtk.Widget nag_page {get; private set;}
List<Gtk.Widget> first_password_widgets;
MainLoop password_ask_loop;
MainLoop password_find_loop;
......@@ -94,6 +96,7 @@ public abstract class AssistantOperation : Assistant
add_setup_pages();
add_confirm_page();
add_password_page();
add_nag_page();
add_question_page();
add_progress_page();
add_summary_page();
......@@ -384,6 +387,59 @@ public abstract class AssistantOperation : Assistant
return page;
}
protected Gtk.Widget make_nag_page()
{
int rows = 0;
Gtk.Widget w, label;
var page = new Gtk.Grid();
page.set("row-spacing", 6,
"column-spacing", 6,
"border-width", 12);
w = new Gtk.Label(_("In order to check that you will be able to retrieve your files in the case of an emergency, please enter your encryption password again to perform a brief restore test."));
w.set("xalign", 0.0f,
"max-width-chars", 25,
"wrap", true);
page.attach(w, 0, rows, 3, 1);
w.hide();
++rows;
w = new Gtk.Entry();
w.set("visibility", false,
"hexpand", true,
"activates-default", true);
((Gtk.Entry)w).changed.connect((entry) => {check_nag_validity();});
label = new Gtk.Label(_("E_ncryption password"));
label.set("mnemonic-widget", w,
"use-underline", true,
"xalign", 1.0f);
page.attach(label, 1, rows, 1, 1);
page.attach(w, 2, rows, 1, 1);
nag_entry = w as Gtk.Entry;
++rows;
w = new Gtk.CheckButton.with_mnemonic(_("_Show password"));
((Gtk.CheckButton)w).toggled.connect((button) => {
nag_entry.visibility = button.get_active();
});
page.attach(w, 2, rows, 1, 1);
++rows;
w = new Gtk.CheckButton.with_mnemonic(_("Test every two _months"));
page.attach(w, 0, rows, 3, 1);
w.hide();
((Gtk.CheckButton)w).active = true;
w.vexpand = true;
w.valign = Gtk.Align.END;
((Gtk.CheckButton)w).toggled.connect((button) => {
DejaDup.update_nag_time(!button.get_active());
});
++rows;
return page;
}
protected Gtk.Widget make_question_page()
{
int rows = 0;
......@@ -462,6 +518,14 @@ public abstract class AssistantOperation : Assistant
password_page = page;
}
void add_nag_page()
{
var page = make_nag_page();
append_page(page, Type.CHECK);
set_page_title(page, _("Restore Test"));
nag_page = page;
}
void add_question_page()
{
var page = make_question_page();
......@@ -588,7 +652,7 @@ public abstract class AssistantOperation : Assistant
else if (op == null)
do_apply.begin();
}
else if (page == password_page)
else if (page == password_page || page == nag_page)
set_header_icon(Gtk.Stock.DIALOG_AUTHENTICATION);
}
......@@ -677,8 +741,8 @@ public abstract class AssistantOperation : Assistant
protected void get_passphrase()
{
// DEJA_DUP_TESTING only set when we are in test suite
if (!searched_for_passphrase && !DejaDup.in_testing_mode()) {
if (!searched_for_passphrase && !DejaDup.in_testing_mode() &&
op.use_cached_password) {
// First, try user's keyring
GnomeKeyring.find_password(PASSPHRASE_SCHEMA,
found_passphrase,
......@@ -730,14 +794,31 @@ public abstract class AssistantOperation : Assistant
set_page_title(password_page, _("Require Password?"));
else
set_page_title(password_page, _("Encryption Password Needed"));
foreach (Gtk.Widget w in first_password_widgets) {
foreach (Gtk.Widget w in first_password_widgets)
w.visible = first;
}
check_password_validity();
encrypt_entry.select_region(0, -1);
encrypt_entry.grab_focus();
}
void check_nag_validity()
{
var passphrase = nag_entry.get_text();
if (passphrase == "")
allow_forward(false);
else
allow_forward(true);
}
void configure_nag_page()
{
check_nag_validity();
nag_entry.set_text("");
nag_entry.grab_focus();
}
void stop_password_loop(Assistant dlg, int resp)
{
Idle.add(() => {
......@@ -751,8 +832,14 @@ public abstract class AssistantOperation : Assistant
protected void ask_passphrase(bool first = false)
{
op.needs_password = true;
interrupt(password_page);
configure_password_page(first);
if (op.use_cached_password) {
interrupt(password_page);
configure_password_page(first);
}
else {
interrupt(nag_page);
configure_nag_page();
}
force_visible(false);
// pause until we can provide password by entering new main loop
password_ask_loop = new MainLoop(null);
......@@ -764,23 +851,30 @@ public abstract class AssistantOperation : Assistant
{
var passphrase = "";
if (encrypt_enabled.active) {
passphrase = encrypt_entry.get_text().strip();
if (passphrase == "") // all whitespace password? allow it...
passphrase = encrypt_entry.get_text();
}
if (op.use_cached_password) {
if (encrypt_enabled.active) {
passphrase = encrypt_entry.get_text().strip();
if (passphrase == "") // all whitespace password? allow it...
passphrase = encrypt_entry.get_text();
}
if (passphrase != "") {
// Save it
if (encrypt_remember.active) {
GnomeKeyring.store_password(PASSPHRASE_SCHEMA,
GnomeKeyring.DEFAULT,
_("Backup encryption password"),
passphrase, save_password_callback,
"owner", Config.PACKAGE,
"type", "passphrase");
if (passphrase != "") {
// Save it
if (encrypt_remember.active) {
GnomeKeyring.store_password(PASSPHRASE_SCHEMA,
GnomeKeyring.DEFAULT,
_("Backup encryption password"),
passphrase, save_password_callback,
"owner", Config.PACKAGE,
"type", "passphrase");
}
}
}
else {
passphrase = nag_entry.get_text().strip();
if (passphrase == "") // all whitespace password? allow it...
passphrase = nag_entry.get_text();
}
op.set_passphrase(passphrase);
}
......
This diff is collapsed.
......@@ -77,6 +77,7 @@ expected_args = []
delay = 0
script = ''
passphrase = None
tmp_archive = False
while len(lines) > curline and lines[curline].strip():
tokens = lines[curline].split()
......@@ -90,8 +91,19 @@ while len(lines) > curline and lines[curline].strip():
script = ' '.join(tokens[1:])
elif tokens[0] == 'PASSPHRASE:':
passphrase = tokens[1] if len(tokens) > 1 else ''
elif lines[curline].strip() == 'TMP_ARCHIVE':
tmp_archive = True
curline += 1
if tmp_archive:
for i in xrange(len(sys.argv)):
split = sys.argv[i].split('=', 1)
if len(split) > 1 and split[0] == "--archive-dir":
if split[1].find("/cache/") != -1:
print >> logfd, "TESTFAIL: expected random /tmp archive dir"
sys.exit(-1)
sys.argv[i] = "--archive-dir=?"
if expected_args != sys.argv[1:]:
print >> logfd, "TESTFAIL: expected\n%s\nvs\n%s" % (expected_args, sys.argv[1:])
sys.exit(-1)
......
......@@ -84,8 +84,8 @@ void backup_teardown()
}
}
if (Posix.system("rm -r %s".printf(Environment.get_variable("DEJA_DUP_TEST_HOME"))) != 0)
warning("Could not clean TEST_HOME %s", Environment.get_variable("DEJA_DUP_TEST_HOME"));
// if (Posix.system("rm -r %s".printf(Environment.get_variable("DEJA_DUP_TEST_HOME"))) != 0)
// warning("Could not clean TEST_HOME %s", Environment.get_variable("DEJA_DUP_TEST_HOME"));
Environment.unset_variable("DEJA_DUP_TEST_MOCKSCRIPT");
Environment.unset_variable("XDG_CACHE_HOME");
......@@ -103,21 +103,23 @@ public enum Mode {
LIST,
}
string default_args(BackupRunner br, Mode mode = Mode.NONE, bool encrypted = false, string extra = "")
string default_args(BackupRunner br, Mode mode = Mode.NONE, bool encrypted = false, string extra = "", bool tmp_archive = false)
{
var cachedir = Environment.get_variable("XDG_CACHE_HOME");
var test_home = Environment.get_variable("DEJA_DUP_TEST_HOME");
var backupdir = Path.build_filename(test_home, "backup");
var restoredir = Path.build_filename(test_home, "restore");
var archive = tmp_archive ? "?" : "%s/deja-dup".printf(cachedir);
if (mode == Mode.CLEANUP)
return "cleanup '--force' 'file://%s' '--gio' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(backupdir, encrypted ? "" : "'--no-encryption' ", cachedir);
return "cleanup '--force' 'file://%s' '--gio' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s' '--log-fd=?'".printf(backupdir, encrypted ? "" : "'--no-encryption' ", archive);
else if (mode == Mode.RESTORE)
return "'restore' '--gio' '--force' 'file://%s' '%s' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(backupdir, restoredir, encrypted ? "" : "'--no-encryption' ", cachedir);
return "'restore' '--gio' '--force' 'file://%s' '%s' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s' '--log-fd=?'".printf(backupdir, restoredir, encrypted ? "" : "'--no-encryption' ", archive);
else if (mode == Mode.VERIFY)
return "'restore' '--file-to-restore=%s/deja-dup/metadata' '--gio' '--force' 'file://%s' '%s/deja-dup/metadata' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(cachedir.substring(1), backupdir, cachedir, encrypted ? "" : "'--no-encryption' ", cachedir);
return "'restore' '--file-to-restore=%s/deja-dup/metadata' '--gio' '--force' 'file://%s' '%s/deja-dup/metadata' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s' '--log-fd=?'".printf(cachedir.substring(1), backupdir, cachedir, encrypted ? "" : "'--no-encryption' ", archive);
else if (mode == Mode.LIST)
return "'list-current-files' '--gio' 'file://%s' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(backupdir, encrypted ? "" : "'--no-encryption' ", cachedir);
return "'list-current-files' '--gio' 'file://%s' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s' '--log-fd=?'".printf(backupdir, encrypted ? "" : "'--no-encryption' ", archive);
string source_str = "";
if (mode == Mode.DRY || mode == Mode.BACKUP)
......@@ -164,7 +166,7 @@ string default_args(BackupRunner br, Mode mode = Mode.NONE, bool encrypted = fal
args += "'--exclude=%s/deja-dup' '--exclude=%s' '--exclude=**' ".printf(cachedir, cachedir);
}
args += "%s%s'--gio' %s'file://%s' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s/deja-dup' '--log-fd=?'".printf(extra, dry_str, source_str, backupdir, enc_str, cachedir);
args += "%s%s'--gio' %s'file://%s' %s'--verbosity=9' '--gpg-options=--no-use-agent' '--archive-dir=%s' '--log-fd=?'".printf(extra, dry_str, source_str, backupdir, enc_str, archive);
return args;
}
......@@ -297,14 +299,20 @@ string replace_keywords(string in)
{
var home = Environment.get_home_dir();
var cachedir = Environment.get_variable("XDG_CACHE_HOME");
return in.replace("@HOME@", home).replace("@XDG_CACHE_HOME@", cachedir);
var test_home = Environment.get_variable("DEJA_DUP_TEST_HOME");
return in.replace("@HOME@", home).
replace("@XDG_CACHE_HOME@", cachedir).
replace("@TEST_HOME@", test_home);
}
string run_script(string in)
{
string output;
string errstr;
try {
Process.spawn_sync(null, {"/bin/sh", "-c", in}, null, 0, null, out output, null, null);
Process.spawn_sync(null, {"/bin/sh", "-c", in}, null, 0, null, out output, out errstr, null);
if (errstr != null && errstr != "")
warning("Error running script: %s", errstr);
}
catch (SpawnError e) {
warning(e.message);
......@@ -343,6 +351,22 @@ void process_operation_block(KeyFile keyfile, string group, BackupRunner br) thr
br.error_detail = keyfile.get_string(group, "ErrorDetail");
if (keyfile.has_key(group, "Passphrases"))
br.passphrases = keyfile.get_integer(group, "Passphrases");
if (keyfile.has_key(group, "Settings")) {
var settings_list = keyfile.get_string_list(group, "Settings");
var settings = DejaDup.get_settings();
foreach (var setting in settings_list) {
try {
var tokens = setting.split("=");
var key = tokens[0];
var val = Variant.parse(null, tokens[1]);
settings.set_value(key, val);
}
catch (Error e) {
warning("%s\n", e.message);
assert_not_reached();
}
}
}
}
void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) throws Error
......@@ -353,6 +377,7 @@ void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) t
bool cancel = false;
bool stop = false;
bool passphrase = false;
bool tmp_archive = false;
string script = null;
Mode mode = Mode.NONE;
......@@ -361,6 +386,8 @@ void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) t
var group = "Duplicity " + run;
if (keyfile.has_group(group)) {
if (keyfile.has_key(group, "ArchiveDirIsTmp"))
tmp_archive = keyfile.get_boolean(group, "ArchiveDirIsTmp");
if (keyfile.has_key(group, "Cancel"))
cancel = keyfile.get_boolean(group, "Cancel");
if (keyfile.has_key(group, "Encrypted"))
......@@ -403,7 +430,10 @@ void process_duplicity_run_block(KeyFile keyfile, string run, BackupRunner br) t
var cachedir = Environment.get_variable("XDG_CACHE_HOME");
var dupscript = "ARGS: " + default_args(br, mode, encrypted, extra_args);
var dupscript = "ARGS: " + default_args(br, mode, encrypted, extra_args, tmp_archive);
if (tmp_archive)
dupscript += "\n" + "TMP_ARCHIVE";
if (cancel) {
dupscript += "\n" + "DELAY: 10";
......
# Tests whether we correctly nag the user about their password during some
# verify checks.
[Operation]
Type=backup
Settings=nag-check='1970-01-01T00:32:08.916885Z'
Passphrases=2
[Duplicity]
Runs=status 1;status 2;dry;backup;status-restore 1;status-restore 2;list;verify;
[Duplicity status 1]
#DEBUG 1
#. ['duplicity.gpg']
#
#ERROR 31
Output=true
[Duplicity status 2]
#DEBUG 1
#. ['duplicity.gpg']
Output=true
Encrypted=true
Passphrase=true
[Duplicity dry]
Encrypted=true
Passphrase=true
[Duplicity backup]
Encrypted=true
Passphrase=true
[Duplicity status-restore 1]
#DEBUG 1
#. ['duplicity.gpg']
#
#ERROR 31
Output=true
ArchiveDirIsTmp=true
[Duplicity status-restore 2]
#DEBUG 1
#. ['duplicity.gpg']
Output=true
Encrypted=true
Passphrase=true
ArchiveDirIsTmp=true
[Duplicity list]
Encrypted=true
Passphrase=true
ArchiveDirIsTmp=true
[Duplicity verify]
Encrypted=true
Passphrase=true
ArchiveDirIsTmp=true
......@@ -27,6 +27,7 @@ internal class DuplicityInstance : Object
string user_text);
public bool verbose {get; private set; default = false;}
public string forced_cache_dir {get; set; default = null;}
public virtual void start(List<string> argv_in, List<string>? envp_in,
bool as_root = false) throws Error
......@@ -71,7 +72,9 @@ internal class DuplicityInstance : Object
argv.append("--gpg-options=--no-use-agent");
// Cache signature files
var cache_dir = Environment.get_user_cache_dir();
var cache_dir = forced_cache_dir;
if (cache_dir == null)
cache_dir = Environment.get_user_cache_dir();
if (cache_dir != null) {
bool add_dir = false;
var cache_file = File.new_for_path(cache_dir);
......
......@@ -80,6 +80,7 @@ internal class DuplicityJob : DejaDup.ToolJob
int delete_age = 0;