← Back to team overview

deja-dup-team team mailing list archive

[Merge] lp:~deja-dup-hackers/deja-dup/tools into lp:deja-dup

 

Michael Terry has proposed merging lp:~deja-dup-hackers/deja-dup/tools into lp:deja-dup.

Requested reviews:
  Ken VanDine (ken-vandine)

For more details, see:
https://code.launchpad.net/~deja-dup-hackers/deja-dup/tools/+merge/104155

Refactor duplicity backend into a libpeas plugin.

There is no change of functionality here.  This is just for cleaner code separation and to get some experience before I do the same thing to the cloud backends.
-- 
https://code.launchpad.net/~deja-dup-hackers/deja-dup/tools/+merge/104155
Your team Déjà Dup Developers is subscribed to branch lp:deja-dup.
=== modified file 'Makefile.am'
--- Makefile.am	2011-10-07 16:57:12 +0000
+++ Makefile.am	2012-04-30 17:55:21 +0000
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
 
-SUBDIRS = data po vapi common widgets monitor preferences deja-dup nautilus help man tests
+SUBDIRS = data po vapi common tools widgets monitor preferences deja-dup nautilus help man tests
 
 # Distribute pot file
 update-pot:

=== modified file 'common/Backend.vala'
--- common/Backend.vala	2012-03-21 03:01:36 +0000
+++ common/Backend.vala	2012-04-30 17:55:21 +0000
@@ -46,7 +46,7 @@
   
   // Arguments needed only when the particular mode is active
   // If mode == INVALID, arguments needed any time the backup is referenced.
-  public virtual void add_argv(Operation.Mode mode, ref List<string> argv) {}
+  public virtual void add_argv(ToolJob.Mode mode, ref List<string> argv) {}
   
   public abstract Backend clone();
   

=== modified file 'common/BackendFile.vala'
--- common/BackendFile.vala	2012-03-22 14:44:44 +0000
+++ common/BackendFile.vala	2012-04-30 17:55:21 +0000
@@ -208,15 +208,15 @@
   }
 
   // will be mounted by this time
-  public override void add_argv(Operation.Mode mode, ref List<string> argv)
+  public override void add_argv(ToolJob.Mode mode, ref List<string> argv)
   {
-    if (mode == Operation.Mode.BACKUP) {
+    if (mode == ToolJob.Mode.BACKUP) {
       var file = get_file_from_settings();
       if (file != null && file.is_native())
         argv.prepend("--exclude=%s".printf(file.get_path()));
     }
     
-    if (mode == Operation.Mode.INVALID)
+    if (mode == ToolJob.Mode.INVALID)
       argv.prepend("--gio");
   }
   

=== modified file 'common/BackendS3.vala'
--- common/BackendS3.vala	2012-03-21 03:01:36 +0000
+++ common/BackendS3.vala	2012-04-30 17:55:21 +0000
@@ -38,8 +38,8 @@
     return new BackendS3();
   }
   
-  public override void add_argv(Operation.Mode mode, ref List<string> argv) {
-    if (mode == Operation.Mode.INVALID)
+  public override void add_argv(ToolJob.Mode mode, ref List<string> argv) {
+    if (mode == ToolJob.Mode.INVALID)
       argv.append("--s3-use-new-style");
   }
   

=== modified file 'common/CommonUtils.vala'
--- common/CommonUtils.vala	2012-03-22 14:44:44 +0000
+++ common/CommonUtils.vala	2012-04-30 17:55:21 +0000
@@ -228,11 +228,6 @@
   settings.set_string(PROMPT_CHECK_KEY, cur_time_str);
 }
 
-public string get_trash_path()
-{
-  return Path.build_filename(Environment.get_user_data_dir(), "Trash");
-}
-
 public string get_folder_key(SimpleSettings settings, string key)
 {
   string folder = settings.get_string(key);
@@ -245,53 +240,6 @@
   return folder;
 }
 
-public File? parse_dir(string dir)
-{
-  string s = dir;
-  if (s == "$HOME")
-    s = Environment.get_home_dir();
-  else if (s == "$DESKTOP")
-    s = Environment.get_user_special_dir(UserDirectory.DESKTOP);
-  else if (s == "$DOCUMENTS")
-    s = Environment.get_user_special_dir(UserDirectory.DOCUMENTS);
-  else if (s == "$DOWNLOAD")
-    s = Environment.get_user_special_dir(UserDirectory.DOWNLOAD);
-  else if (s == "$MUSIC")
-    s = Environment.get_user_special_dir(UserDirectory.MUSIC);
-  else if (s == "$PICTURES")
-    s = Environment.get_user_special_dir(UserDirectory.PICTURES);
-  else if (s == "$PUBLIC_SHARE")
-    s = Environment.get_user_special_dir(UserDirectory.PUBLIC_SHARE);
-  else if (s == "$TEMPLATES")
-    s = Environment.get_user_special_dir(UserDirectory.TEMPLATES);
-  else if (s == "$TRASH")
-    s = get_trash_path();
-  else if (s == "$VIDEOS")
-    s = Environment.get_user_special_dir(UserDirectory.VIDEOS);
-  else if (Uri.parse_scheme(s) == null && !Path.is_absolute(s))
-    s = Path.build_filename(Environment.get_home_dir(), s);
-  else
-    return File.parse_name(s);
-
-  if (s != null)
-    return File.new_for_path(s);
-  else
-    return null;
-}
-
-public File[] parse_dir_list(string*[] dirs)
-{
-  File[] rv = new File[0];
-  
-  foreach (string s in dirs) {
-    var f = parse_dir(s);
-    if (f != null)
-      rv += f;
-  }
-  
-  return rv;
-}
-
 bool settings_read_only = false;
 HashTable<string, SimpleSettings> settings_table = null;
 public void set_settings_read_only(bool ro)
@@ -389,15 +337,53 @@
   }
 }
 
-public bool meet_requirements(out string header, out string msg)
-{
-  return DuplicityInfo.get_default().check_duplicity_version(out header, out msg);
+ToolPlugin tool = null;
+void initialize_tool_plugin() throws Error
+{
+  var engine = new Peas.Engine ();
+
+  // For testing, we'll often point to in-tree tools
+  string search_path = Environment.get_variable("DEJA_DUP_TOOLS_PATH");
+  if (search_path == null || search_path == "")
+    search_path = Path.build_filename(Config.PKG_LIBEXEC_DIR, "tools");
+  engine.add_search_path(search_path, null);
+
+  var info = engine.get_plugin_info("libduplicity.so");
+  if (info == null)
+    throw new SpawnError.FAILED(_("Could not find backup tool in %s.  Your installation is incomplete.").printf(search_path));
+  if (!engine.try_load_plugin(info))
+    throw new SpawnError.FAILED(_("Could not load backup tool.  Your installation is incomplete."));
+
+  var extset = new Peas.ExtensionSet(engine, typeof(Peas.Activatable));
+  var ext = extset.get_extension(info);
+  tool = ext as ToolPlugin;
+  if (tool == null)
+    throw new SpawnError.FAILED(_("Backup tool is broken.  Your installation is incomplete."));
+
+  tool.activate();
+}
+
+public ToolJob make_tool_job() throws Error
+{
+  if (tool == null)
+    initialize_tool_plugin();
+  return tool.create_job();
 }
 
 public bool initialize(out string header, out string msg)
 {
-  if (!meet_requirements(out header, out msg))
+  header = null;
+  msg = null;
+
+  // Get tool plugin to use
+  try {
+    initialize_tool_plugin();
+  }
+  catch (Error e) {
+    header = _("Could not start backup tool");
+    msg = e.message;
     return false;
+  }
 
   convert_ssh_to_file();
   convert_s3_folder_to_hostname();

=== added file 'common/DirHandling.vala'
--- common/DirHandling.vala	1970-01-01 00:00:00 +0000
+++ common/DirHandling.vala	2012-04-30 17:55:21 +0000
@@ -0,0 +1,77 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
+/*
+    This file is part of Déjà Dup.
+    For copyright information, see AUTHORS.
+
+    Déjà Dup is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Déjà Dup is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib;
+
+namespace DejaDup {
+
+public string get_trash_path()
+{
+  return Path.build_filename(Environment.get_user_data_dir(), "Trash");
+}
+
+public File? parse_dir(string dir)
+{
+  string s = dir;
+  if (s == "$HOME")
+    s = Environment.get_home_dir();
+  else if (s == "$DESKTOP")
+    s = Environment.get_user_special_dir(UserDirectory.DESKTOP);
+  else if (s == "$DOCUMENTS")
+    s = Environment.get_user_special_dir(UserDirectory.DOCUMENTS);
+  else if (s == "$DOWNLOAD")
+    s = Environment.get_user_special_dir(UserDirectory.DOWNLOAD);
+  else if (s == "$MUSIC")
+    s = Environment.get_user_special_dir(UserDirectory.MUSIC);
+  else if (s == "$PICTURES")
+    s = Environment.get_user_special_dir(UserDirectory.PICTURES);
+  else if (s == "$PUBLIC_SHARE")
+    s = Environment.get_user_special_dir(UserDirectory.PUBLIC_SHARE);
+  else if (s == "$TEMPLATES")
+    s = Environment.get_user_special_dir(UserDirectory.TEMPLATES);
+  else if (s == "$TRASH")
+    s = get_trash_path();
+  else if (s == "$VIDEOS")
+    s = Environment.get_user_special_dir(UserDirectory.VIDEOS);
+  else if (Uri.parse_scheme(s) == null && !Path.is_absolute(s))
+    s = Path.build_filename(Environment.get_home_dir(), s);
+  else
+    return File.parse_name(s);
+
+  if (s != null)
+    return File.new_for_path(s);
+  else
+    return null;
+}
+
+public File[] parse_dir_list(string*[] dirs)
+{
+  File[] rv = new File[0];
+  
+  foreach (string s in dirs) {
+    var f = parse_dir(s);
+    if (f != null)
+      rv += f;
+  }
+  
+  return rv;
+}
+
+} // end namespace
+

=== removed file 'common/DuplicityInfo.vala'
--- common/DuplicityInfo.vala	2011-09-26 05:15:03 +0000
+++ common/DuplicityInfo.vala	1970-01-01 00:00:00 +0000
@@ -1,128 +0,0 @@
-/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
-/*
-    This file is part of Déjà Dup.
-    For copyright information, see AUTHORS.
-
-    Déjà Dup is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Déjà Dup is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-using GLib;
-
-namespace DejaDup {
-
-public class DuplicityInfo : Object
-{
-  public static const int REQUIRED_MAJOR = 0;
-  public static const int REQUIRED_MINOR = 6;
-  public static const int REQUIRED_MICRO = 14;
-
-  public bool reports_encryption {get; private set; default = false;}
-  
-  static DuplicityInfo info = null;
-  public static DuplicityInfo get_default() {
-    if (info == null)
-      info = new DuplicityInfo();
-    return info;
-  }
-  
-  // Returns true if everything is OK.  If false, program will close.  A dialog
-  // will already have been thrown up.
-  public bool check_duplicity_version(out string header, out string msg) {
-    string output;
-
-    header = null;
-    msg = null;
-    
-    try {
-      Process.spawn_command_line_sync("duplicity --version", out output, null, null);
-    }
-    catch (Error e) {
-      set_missing_duplicity_error(out header, out msg, e.message);
-      return false;
-    }
-    
-    var tokens = output.split(" ", 2);
-    if (tokens == null || tokens[0] == null || tokens[1] == null) {
-      set_missing_duplicity_error(out header, out msg, null);
-      return false;
-    }
-    
-    // First token is 'duplicity' and is ignorable.  Second looks like '0.5.03'
-    version_string = tokens[1].strip();
-    var ver_tokens = version_string.split(".");
-    if (ver_tokens == null || ver_tokens[0] == null) {
-      set_missing_duplicity_error(out header, out msg, null);
-      return false;
-    }
-    major = int.parse(ver_tokens[0]);
-    // Don't error out if no minor or micro.  Duplicity might not have them?
-    if (ver_tokens[1] != null) {
-      minor = int.parse(ver_tokens[1]);
-      if (ver_tokens[2] != null)
-        micro = int.parse(ver_tokens[2]);
-    }
-    
-    var good_enough = meets_requirements();
-    if (!good_enough) {
-      set_bad_version_error(out header, out msg);
-      return false;
-    }
-
-    //if (meets_version(0, 6, 15))
-    //  reports_encryption = true;
-
-    return true;
-  }
-  
-  string version_string = null;
-  int major = 0;
-  int minor = 0;
-  int micro = 0;
-  
-  bool meets_version(int vmaj, int vmin, int vmic) {
-    return (major > vmaj) ||
-           (major == vmaj && minor > vmin) ||
-           (major == vmaj && minor == vmin && micro >= vmic);
-  }
-/*
-  bool equals_version(int vmaj, int vmin, int vmic) {
-    return major == vmaj && minor == vmin && micro == vmic;
-  }
-*/
-  // Doesn't yet handle a blacklist of versions.  We'll cross that bridge when we come to it
-  bool meets_requirements() {
-    return meets_version(REQUIRED_MAJOR, REQUIRED_MINOR, REQUIRED_MICRO);
-  }
-  
-  void set_missing_duplicity_error(out string header, out string msg, string? msg_in) {
-    header = _("Could not run duplicity");
-    msg = msg_in;
-    if (msg != null)
-      msg = msg.chomp() + "\n\n";
-    else if (version_string == null)
-        msg = _("Could not understand duplicity version.\n\n");
-    else
-        msg = _("Could not understand duplicity version ‘%s’.\n\n").printf(version_string);
-
-    msg += _("Without duplicity, Déjà Dup Backup Tool cannot function. It will close now.");
-  }
-  
-  void set_bad_version_error(out string header, out string msg) {
-    header = _("Duplicity’s version is too old");
-    msg = _("Déjà Dup Backup Tool requires at least version %d.%d.%.2d of duplicity, but only found version %d.%d.%.2d").printf(REQUIRED_MAJOR, REQUIRED_MINOR, REQUIRED_MICRO, major, minor, micro);
-  }
-}
-
-} // end namespace
-

=== modified file 'common/Makefile.am'
--- common/Makefile.am	2012-02-02 18:56:52 +0000
+++ common/Makefile.am	2012-04-30 17:55:21 +0000
@@ -42,9 +42,7 @@
 	BackendU1.vala \
 	Checker.vala \
 	CommonUtils.vala \
-	Duplicity.vala \
-	DuplicityInfo.vala \
-	DuplicityInstance.vala \
+	DirHandling.vala \
 	Network.vala \
 	Operation.vala \
 	OperationBackup.vala \
@@ -52,10 +50,8 @@
 	OperationStatus.vala \
 	OperationFiles.vala \
 	PythonChecker.vala \
-	RecursiveDelete.vala \
-	RecursiveMove.vala \
-	RecursiveOp.vala \
-	SimpleSettings.vala
+	SimpleSettings.vala \
+	ToolPlugin.vala
 
 libcommon_la_SOURCES = \
 	chacks.c \
@@ -71,6 +67,7 @@
 	--pkg gio-2.0 \
 	--pkg gio-unix-2.0 \
 	--pkg gnome-keyring-1 \
+	--pkg libpeas-1.0 \
 	--pkg uriutils \
 	--pkg posix \
 	--pkg config

=== modified file 'common/Operation.vala'
--- common/Operation.vala	2012-04-10 19:35:31 +0000
+++ common/Operation.vala	2012-04-30 17:55:21 +0000
@@ -43,36 +43,26 @@
 
   public bool needs_password {get; set;}
   public Backend backend {get; private set;}
-  public bool use_progress {get {return dup.use_progress;}
-                            set {dup.use_progress = value;}}
+  public bool use_progress {get {return (job.flags & ToolJob.Flags.NO_PROGRESS) == 0;}
+                            set {
+                              if (value)
+                                job.flags = job.flags | ToolJob.Flags.NO_PROGRESS;
+                              else
+                                job.flags = job.flags ^ ToolJob.Flags.NO_PROGRESS;
+                            }}
 
-  public enum Mode {
-    /*
-   * Mode of operation of instance
-   *
-   * Every instance of class that inherit its methods and properties from
-   * this class must define in which mode it operates. Based on this Duplicity
-   * attaches appropriate argument.
-   */
-    INVALID,
-    BACKUP,
-    RESTORE,
-    STATUS,
-    LIST,
-    FILEHISTORY
-  }
-  public Mode mode {get; construct; default = Mode.INVALID;}
+  public ToolJob.Mode mode {get; construct; default = ToolJob.Mode.INVALID;}
   
-  public static string mode_to_string(Mode mode)
+  public static string mode_to_string(ToolJob.Mode mode)
   {
     switch (mode) {
-    case Operation.Mode.BACKUP:
+    case ToolJob.Mode.BACKUP:
       return _("Backing up…");
-    case Operation.Mode.RESTORE:
+    case ToolJob.Mode.RESTORE:
       return _("Restoring…");
-    case Operation.Mode.STATUS:
+    case ToolJob.Mode.STATUS:
       return _("Checking for backups…");
-    case Operation.Mode.LIST:
+    case ToolJob.Mode.LIST:
       return _("Listing files…");
     default:
       return _("Preparing…");
@@ -97,7 +87,7 @@
   }
 
   SimpleSettings settings;
-  internal Duplicity dup;
+  internal ToolJob job;
   protected string passphrase;
   bool finished = false;
   construct
@@ -136,16 +126,27 @@
       settings = null;
     }
 
-    if (dup != null) {
-      SignalHandler.disconnect_matched(dup, SignalMatchType.DATA,
+    if (job != null) {
+      SignalHandler.disconnect_matched(job, SignalMatchType.DATA,
                                        0, 0, null, null, this);
-      dup.stop();
-      dup = null;
-    }
-
-    dup = new Duplicity(mode);
-
-    connect_to_dup();
+      job.stop();
+      job = null;
+    }
+
+    try {
+      job = make_tool_job();
+    }
+    catch (Error e) {
+      raise_error(e.message, null);
+      done(false, false, null);
+      return;
+    }
+
+    job.mode = mode;
+    job.backend = backend;
+
+    make_argv();
+    connect_to_job();
 
     ref(); // don't know what might happen in passphrase_required call
 
@@ -155,38 +156,38 @@
       passphrase_required(); // will block and call set_passphrase when ready
     }
     else
-      dup.encrypt_password = passphrase;
+      job.encrypt_password = passphrase;
 
     if (!finished)
-      continue_with_passphrase();
+      job.start();
 
     unref();
   }
 
   public void cancel()
   {
-    dup.cancel();
+    job.cancel();
   }
   
   public void stop()
   {
-    dup.stop();
+    job.stop();
   }
   
-  protected virtual void connect_to_dup()
+  protected virtual void connect_to_job()
   {
     /*
      * Connect Deja Dup to signals
      */
-    dup.done.connect((d, o, c, detail) => {operation_finished(d, o, c, detail);});
-    dup.raise_error.connect((d, s, detail) => {raise_error(s, detail);});
-    dup.action_desc_changed.connect((d, s) => {action_desc_changed(s);});
-    dup.action_file_changed.connect((d, f, b) => {action_file_changed(f, b);});
-    dup.progress.connect((d, p) => {progress(p);});
-    dup.question.connect((d, t, m) => {question(t, m);});
-    dup.is_full.connect((first) => {is_full(first);});
-    dup.bad_encryption_password.connect(() => {
-      // If duplicity gives us a gpg error, we set needs_password so that
+    job.done.connect((d, o, c, detail) => {operation_finished(d, o, c, detail);});
+    job.raise_error.connect((d, s, detail) => {raise_error(s, detail);});
+    job.action_desc_changed.connect((d, s) => {action_desc_changed(s);});
+    job.action_file_changed.connect((d, f, b) => {action_file_changed(f, b);});
+    job.progress.connect((d, p) => {progress(p);});
+    job.question.connect((d, t, m) => {question(t, m);});
+    job.is_full.connect((first) => {is_full(first);});
+    job.bad_encryption_password.connect(() => {
+      // If tool gives us a gpg error, we set needs_password so that
       // we will prompt for it.
       needs_password = true;
       passphrase = null;
@@ -198,55 +199,11 @@
   {
     needs_password = false;
     this.passphrase = passphrase;
-    if (dup != null)
-      dup.encrypt_password = passphrase;
-  }
-
-  async void continue_with_passphrase()
-  {
-   /*
-    * Continues with operation after passphrase has been acquired.
-    */
-    try {
-      backend.envp_ready.connect(continue_with_envp);
-      yield backend.get_envp();
-    }
-    catch (Error e) {
-      raise_error(e.message, null);
-      operation_finished(dup, false, false, null);
-    }
-  }
-  
-  void continue_with_envp(DejaDup.Backend b, bool success, List<string>? envp, string? error)
-  {
-    /*
-     * Starts Duplicity backup with added enviroment variables
-     * 
-     * Start Duplicity backup process with costum values for enviroment variables.
-     */
-    backend.envp_ready.disconnect(continue_with_envp);
-
-    if (!success) {
-      if (error != null)
-        raise_error(error, null);
-      operation_finished(dup, false, false, null);
-      return;
-    }
-
-    try {
-      List<string> argv = make_argv();
-      backend.add_argv(mode, ref argv);
-      
-      dup.start(backend, argv, envp);
-    }
-    catch (Error e) {
-      raise_error(e.message, null);
-      operation_finished(dup, false, false, null);
-      return;
-    }
-  }
-  
-  internal async virtual void operation_finished(Duplicity dup, bool success, bool cancelled, string? detail)
+    if (job != null)
+      job.encrypt_password = passphrase;
+  }
+
+  internal async virtual void operation_finished(ToolJob job, bool success, bool cancelled, string? detail)
   {
     finished = true;
 
@@ -255,7 +212,7 @@
     done(success, cancelled, detail);
   }
   
-  protected virtual List<string>? make_argv() throws Error
+  protected virtual List<string>? make_argv()
   {
   /**
    * Abstract method that prepares arguments that will be sent to duplicity

=== modified file 'common/OperationBackup.vala'
--- common/OperationBackup.vala	2012-04-10 02:22:06 +0000
+++ common/OperationBackup.vala	2012-04-30 17:55:21 +0000
@@ -24,19 +24,19 @@
 public class OperationBackup : Operation
 {
   public OperationBackup() {
-    Object(mode: Mode.BACKUP);
+    Object(mode: ToolJob.Mode.BACKUP);
   }
   
-  internal async override void operation_finished(Duplicity dup, bool success, bool cancelled, string? detail)
+  internal async override void operation_finished(ToolJob job, bool success, bool cancelled, string? detail)
   {
     /* If successfully completed, update time of last backup and run base operation_finished */
     if (success)
       DejaDup.update_last_run_timestamp(DejaDup.TimestampType.BACKUP);
     
-    base.operation_finished(dup, success, cancelled, detail);
+    base.operation_finished(job, success, cancelled, detail);
   }
   
-  protected override List<string>? make_argv() throws Error
+  protected override List<string>? make_argv()
   {
     var settings = get_settings();
     
@@ -45,21 +45,19 @@
     var exclude_val = settings.get_value(EXCLUDE_LIST_KEY);
     var exclude_list = parse_dir_list(exclude_val.get_strv());
     
-    List<string> rv = new List<string>();
-    
     // Exclude directories no one wants to backup
     var always_excluded = get_always_excluded_dirs();
     foreach (string dir in always_excluded)
-      dup.excludes.prepend(File.new_for_path(dir));
+      job.excludes.prepend(File.new_for_path(dir));
     
     foreach (File s in exclude_list)
-      dup.excludes.prepend(s);
+      job.excludes.prepend(s);
     foreach (File s in include_list)
-      dup.includes.prepend(s);
-    
-    dup.local = File.new_for_path("/");
-    
-    return rv;
+      job.includes.prepend(s);
+    
+    job.local = File.new_for_path("/");
+    
+    return null;
   }
   
   List<string> get_always_excluded_dirs()

=== modified file 'common/OperationFiles.vala'
--- common/OperationFiles.vala	2012-04-10 02:22:06 +0000
+++ common/OperationFiles.vala	2012-04-30 17:55:21 +0000
@@ -27,28 +27,25 @@
     
   public OperationFiles(Time? time_in,
                         File source) {
-    Object(mode: Mode.LIST, source: source);
+    Object(mode: ToolJob.Mode.LIST, source: source);
     if (time_in != null)
         time = time_in;
   }
 
-  protected override void connect_to_dup()
+  protected override void connect_to_job()
   {
-    dup.listed_current_files.connect((d, date, file) => {listed_current_files(date, file);});
-    base.connect_to_dup();
+    job.listed_current_files.connect((d, date, file) => {listed_current_files(date, file);});
+    base.connect_to_job();
   }
 
-  protected override List<string>? make_argv() throws Error
+  protected override List<string>? make_argv()
   {
-    List<string> argv = new List<string>();
-    //if (time != null) - no need, we don't allow null anyway
-    //stdout.printf("timedefault: %i", time.year);
     if (time.format("%s") != "-1")
-        argv.append("--time=%s".printf(time.format("%s")));
-    
-    dup.local = source;
-      
-    return argv;
+      job.time = time.format("%s");
+    else
+      job.time = null;
+    job.local = source;
+    return null;
   }
 }
 }

=== modified file 'common/OperationRestore.vala'
--- common/OperationRestore.vala	2012-04-10 02:22:06 +0000
+++ common/OperationRestore.vala	2012-04-30 17:55:21 +0000
@@ -43,7 +43,7 @@
                           string? time_in = null,
                           List<File>? files_in = null) {
     Object(dest: dest_in, time: time_in, restore_files: files_in,
-           mode: Mode.RESTORE);
+           mode: ToolJob.Mode.RESTORE);
   }
   
   public async override void start()
@@ -52,29 +52,25 @@
     base.start();
   }
 
-  protected override void connect_to_dup()
+  protected override void connect_to_job()
   {
-    base.connect_to_dup();
-    dup.restore_files = restore_files;
+    base.connect_to_job();
+    job.restore_files = restore_files;
   }
 
-  protected override List<string>? make_argv() throws Error
+  protected override List<string>? make_argv()
   {
-    List<string> argv = new List<string>();
-    if (time != null)
-      argv.append("--restore-time=%s".printf(time));
-    
-    dup.local = File.new_for_path(dest);
-    
-    return argv;
+    job.time = time;
+    job.local = File.new_for_path(dest);
+    return null;
   }
   
-  internal async override void operation_finished(Duplicity dup, bool success, bool cancelled, string? detail)
+  internal async override void operation_finished(ToolJob job, bool success, bool cancelled, string? detail)
   {
     if (success)
       DejaDup.update_last_run_timestamp(DejaDup.TimestampType.RESTORE);
 
-    base.operation_finished(dup, success, cancelled, detail);
+    base.operation_finished(job, success, cancelled, detail);
   }
 }
 

=== modified file 'common/OperationStatus.vala'
--- common/OperationStatus.vala	2012-04-10 02:22:06 +0000
+++ common/OperationStatus.vala	2012-04-30 17:55:21 +0000
@@ -26,13 +26,13 @@
   public signal void collection_dates(List<string>? dates);
   
   public OperationStatus() {
-    Object(mode: Mode.STATUS);
+    Object(mode: ToolJob.Mode.STATUS);
   }
   
-  protected override void connect_to_dup()
+  protected override void connect_to_job()
   {
-    dup.collection_dates.connect((d, dates) => {collection_dates(dates);});
-    base.connect_to_dup();
+    job.collection_dates.connect((d, dates) => {collection_dates(dates);});
+    base.connect_to_job();
   }
 }
 

=== added file 'common/ToolPlugin.vala'
--- common/ToolPlugin.vala	1970-01-01 00:00:00 +0000
+++ common/ToolPlugin.vala	2012-04-30 17:55:21 +0000
@@ -0,0 +1,100 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
+/*
+    This file is part of Déjà Dup.
+    For copyright information, see AUTHORS.
+
+    Déjà Dup is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Déjà Dup is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib;
+
+namespace DejaDup {
+
+public abstract class ToolJob : Object
+{
+  // life cycle signals
+  public signal void done(bool success, bool cancelled, string? detail);
+  public signal void raise_error(string errstr, string? detail);
+
+  // hints to UI
+  public signal void action_desc_changed(string action);
+  public signal void action_file_changed(File file, bool actual);
+  public signal void progress(double percent);
+  public signal void is_full(bool first);
+
+  // hints that interaction is needed
+  public signal void bad_encryption_password();
+  public signal void question(string title, string msg);
+
+  // type-specific signals
+  public signal void collection_dates(List<string>? dates); // HISTORY
+  public signal void listed_current_files(string date, string file); // LIST
+
+  // life cycle control
+  public abstract void start ();
+  public abstract void cancel (); // destroy progress so far
+  public abstract void stop (); // just abruptly stop
+  public abstract void pause (string? reason);
+  public abstract void resume ();
+
+  public enum Mode {
+    INVALID, BACKUP, RESTORE, STATUS, LIST, HISTORY,
+  }
+  public Mode mode {get; set; default = Mode.INVALID;}
+
+  public enum Flags {
+    NO_PROGRESS,
+  }
+  public Flags flags {get; set;}
+
+  public File local {get; set;}
+  public Backend backend {get; set;}
+  public string encrypt_password {get; set;}
+
+  public List<File> includes; // BACKUP
+  public List<File> excludes; // BACKUP
+
+  protected List<File> _restore_files;
+  public List<File> restore_files { // RESTORE
+    get {
+      return this._restore_files;
+    }
+    set {
+      // Deep copy
+      foreach (File f in this._restore_files)
+        f.unref();
+      this._restore_files = value.copy();
+      foreach (File f in this._restore_files)
+        f.ref();
+    }
+  }
+
+  public string time {get; set;} // RESTORE
+}
+
+public abstract class ToolPlugin : Peas.ExtensionBase, Peas.Activatable
+{
+  // Peas methods
+  public Object object {owned get; construct;}
+  public virtual void activate () {}
+  public virtual void deactivate () {}
+  public virtual void update_state () {}
+
+  // Deja Dup methods
+  public string name {get; protected set;}
+  public abstract ToolJob create_job () throws Error;
+}
+
+} // end namespace
+

=== modified file 'configure.ac'
--- configure.ac	2012-04-18 14:02:29 +0000
+++ configure.ac	2012-04-30 17:55:21 +0000
@@ -77,26 +77,31 @@
                   gio-2.0 >= $GIO_REQ_VER
                   gio-unix-2.0 >= $GIO_REQ_VER
                   gnome-keyring-1
+                  libpeas-1.0
                   gmodule-2.0 >= $GLIB_REQ_VER
                   libnotify >= $NOTIFY_REQ_VER)
 
 PKG_CHECK_MODULES(PREF,
                   $GTK_MODULE >= $GTK_REQ_VER
-                  gio-2.0 >= $GIO_REQ_VER)
+                  gio-2.0 >= $GIO_REQ_VER
+                  libpeas-1.0)
 
 PKG_CHECK_MODULES(COMMON,
                   gio-2.0 >= $GIO_REQ_VER
                   gio-unix-2.0 >= $GIO_REQ_VER
                   gnome-keyring-1
+                  libpeas-1.0
                   gmodule-2.0 >= $GLIB_REQ_VER)
 
 PKG_CHECK_MODULES(WIDGETS,
                   gmodule-2.0 >= $GLIB_REQ_VER
                   $GTK_MODULE >= $GTK_REQ_VER
+                  libpeas-1.0
                   libnotify >= $NOTIFY_REQ_VER)
 
 PKG_CHECK_MODULES(MONITOR,
                   gio-2.0 >= $GIO_REQ_VER
+                  libpeas-1.0
                   libnotify >= $NOTIFY_REQ_VER)
 
 AC_ARG_WITH([extensiondir],
@@ -181,6 +186,8 @@
                  tests/runner/Makefile
                  tests/scripts/Makefile
                  tests/unit/Makefile
+                 tools/Makefile
+                 tools/duplicity/Makefile
                  vapi/Makefile
                  widgets/Makefile])
 AC_OUTPUT

=== modified file 'deja-dup/Makefile.am'
--- deja-dup/Makefile.am	2012-04-10 02:36:52 +0000
+++ deja-dup/Makefile.am	2012-04-30 17:55:21 +0000
@@ -56,6 +56,7 @@
 	--pkg @GTK_MODULE@ \
 	--pkg gio-2.0 \
 	--pkg gnome-keyring-1 \
+	--pkg libpeas-1.0 \
 	--pkg libnotify \
 	--pkg libcommon \
 	--pkg libwidgets \

=== modified file 'deja-dup/StatusIcon.vala'
--- deja-dup/StatusIcon.vala	2011-12-23 18:36:10 +0000
+++ deja-dup/StatusIcon.vala	2012-04-30 17:55:21 +0000
@@ -121,7 +121,7 @@
       }
     }
 
-    if (success && !cancelled && op.mode == DejaDup.Operation.Mode.BACKUP) {
+    if (success && !cancelled && op.mode == DejaDup.ToolJob.Mode.BACKUP) {
       string msg = _("Backup completed");
 
       string more = null;
@@ -198,7 +198,7 @@
   {
     Dbusmenu.Menuitem menu = null;
 
-    if (op.mode == DejaDup.Operation.Mode.BACKUP) {
+    if (op.mode == DejaDup.ToolJob.Mode.BACKUP) {
       menu = new Dbusmenu.Menuitem();
 
       var item = new Dbusmenu.Menuitem();
@@ -227,7 +227,7 @@
   }
 
   construct {
-    if (automatic && op.mode == DejaDup.Operation.Mode.BACKUP) {
+    if (automatic && op.mode == DejaDup.ToolJob.Mode.BACKUP) {
       Notify.init(_("Backup"));
       note = new Notify.Notification(_("Starting scheduled backup"), null,
                                      "deja-dup");
@@ -286,7 +286,7 @@
     progressitem.activate.connect((i) => {show_window();});
     menu.append(progressitem);
 
-    if (op.mode == DejaDup.Operation.Mode.BACKUP) {
+    if (op.mode == DejaDup.ToolJob.Mode.BACKUP) {
       Gtk.MenuItem item;
 
       menu.append(new Gtk.SeparatorMenuItem());

=== modified file 'monitor/Makefile.am'
--- monitor/Makefile.am	2012-02-02 18:56:52 +0000
+++ monitor/Makefile.am	2012-04-30 17:55:21 +0000
@@ -42,6 +42,7 @@
 	$(SHARED_VALAFLAGS) \
 	--pkg config \
 	--pkg gio-2.0 \
+	--pkg libpeas-1.0 \
 	--pkg libcommon \
 	--pkg libnotify
 

=== modified file 'nautilus/NautilusExtension.c'
--- nautilus/NautilusExtension.c	2011-10-07 15:26:41 +0000
+++ nautilus/NautilusExtension.c	2012-04-30 17:55:21 +0000
@@ -18,7 +18,7 @@
     along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include "CommonUtils.c"
+#include "DirHandling.c"
 
 #include "NautilusExtension.h"
 #include "config.h"
@@ -57,10 +57,8 @@
   if (settings == NULL)
     return;
 
-  gchar **includes_strv = g_settings_get_strv(settings,
-                                              DEJA_DUP_INCLUDE_LIST_KEY);
-  gchar **excludes_strv = g_settings_get_strv(settings,
-                                              DEJA_DUP_EXCLUDE_LIST_KEY);
+  gchar **includes_strv = g_settings_get_strv(settings, "include-list");
+  gchar **excludes_strv = g_settings_get_strv(settings, "exclude-list");
 
   gchar **p;
   for (p = includes_strv; p && *p; p++) {
@@ -318,9 +316,9 @@
   bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 
   settings = g_settings_new("org.gnome.DejaDup");
-  g_signal_connect(settings, "changed::" DEJA_DUP_INCLUDE_LIST_KEY,
+  g_signal_connect(settings, "changed::include-list",
                    update_include_excludes, NULL);
-  g_signal_connect(settings, "changed::" DEJA_DUP_EXCLUDE_LIST_KEY,
+  g_signal_connect(settings, "changed::exclude-list",
                    update_include_excludes, NULL);
   g_idle_add(update_include_excludes_idle_cb, NULL);
 }

=== modified file 'po/POTFILES.in'
--- po/POTFILES.in	2011-10-11 14:03:56 +0000
+++ po/POTFILES.in	2012-04-30 17:55:21 +0000
@@ -17,18 +17,12 @@
 common/BackendU1.vala
 common/Backend.vala
 common/CommonUtils.vala
-common/DuplicityInfo.vala
-common/DuplicityInstance.vala
-common/Duplicity.vala
 common/Network.vala
 common/OperationBackup.vala
 common/OperationFiles.vala
 common/OperationRestore.vala
 common/OperationStatus.vala
 common/Operation.vala
-common/RecursiveDelete.vala
-common/RecursiveMove.vala
-common/RecursiveOp.vala
 common/SimpleSettings.vala
 deja-dup/AssistantBackup.vala
 deja-dup/AssistantOperation.vala
@@ -42,6 +36,12 @@
 monitor/monitor.vala
 preferences/Preferences.vala
 preferences/preferences-main.vala
+tools/duplicity/DuplicityInstance.vala
+tools/duplicity/DuplicityJob.vala
+tools/duplicity/DuplicityPlugin.vala
+tools/duplicity/RecursiveDelete.vala
+tools/duplicity/RecursiveMove.vala
+tools/duplicity/RecursiveOp.vala
 widgets/ConfigBool.vala
 widgets/ConfigChoice.vala
 widgets/ConfigDelete.vala

=== modified file 'po/POTFILES.skip'
--- po/POTFILES.skip	2011-06-12 05:00:21 +0000
+++ po/POTFILES.skip	2012-04-30 17:55:21 +0000
@@ -6,18 +6,12 @@
 common/BackendU1.c
 common/Backend.c
 common/CommonUtils.c
-common/DuplicityInfo.c
-common/DuplicityInstance.c
-common/Duplicity.c
 common/Network.c
 common/OperationBackup.c
 common/OperationFiles.c
 common/OperationRestore.c
 common/OperationStatus.c
 common/Operation.c
-common/RecursiveDelete.c
-common/RecursiveMove.c
-common/RecursiveOp.c
 common/SimpleSettings.c
 deja-dup/AssistantBackup.c
 deja-dup/AssistantOperation.c
@@ -31,6 +25,12 @@
 monitor/monitor.c
 preferences/Preferences.c
 preferences/preferences-main.c
+tools/duplicity/DuplicityInstance.c
+tools/duplicity/DuplicityJob.c
+tools/duplicity/DuplicityPlugin.c
+tools/duplicity/RecursiveDelete.c
+tools/duplicity/RecursiveMove.c
+tools/duplicity/RecursiveOp.c
 widgets/ConfigBool.c
 widgets/ConfigChoice.c
 widgets/ConfigDelete.c

=== modified file 'po/deja-dup.pot'
--- po/deja-dup.pot	2012-01-19 22:40:33 +0000
+++ po/deja-dup.pot	2012-04-30 17:55:21 +0000
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: mike@xxxxxxxxxxx\n"
-"POT-Creation-Date: 2012-01-17 23:17-0500\n"
+"POT-Creation-Date: 2012-04-29 23:17-0700\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@xxxxxx>\n"
@@ -322,21 +322,21 @@
 msgid "Scanning…"
 msgstr ""
 
-#: ../nautilus/NautilusExtension.c:176
+#: ../nautilus/NautilusExtension.c:174
 msgid "Restore Missing Files…"
 msgstr ""
 
-#: ../nautilus/NautilusExtension.c:177
+#: ../nautilus/NautilusExtension.c:175
 msgid "Restore deleted files from backup"
 msgstr ""
 
-#: ../nautilus/NautilusExtension.c:219
+#: ../nautilus/NautilusExtension.c:217
 msgid "Revert to Previous Version…"
 msgid_plural "Revert to Previous Versions…"
 msgstr[0] ""
 msgstr[1] ""
 
-#: ../nautilus/NautilusExtension.c:223
+#: ../nautilus/NautilusExtension.c:221
 msgid "Restore file from backup"
 msgid_plural "Restore files from backup"
 msgstr[0] ""
@@ -344,72 +344,68 @@
 
 #. Translators: %2$s is the name of a removable drive, %1$s is a folder
 #. on that removable drive.
-#: ../common/BackendFile.vala:86 ../common/CommonUtils.vala:447
+#: ../common/BackendFile.vala:135 ../common/CommonUtils.vala:433
 #, c-format
 msgid "%1$s on %2$s"
 msgstr ""
 
-#: ../common/BackendFile.vala:124
+#: ../common/BackendFile.vala:168
 #, c-format
 msgid "Backup will begin when %s becomes connected."
 msgstr ""
 
-#: ../common/BackendFile.vala:131 ../common/BackendRackspace.vala:49
+#: ../common/BackendFile.vala:175 ../common/BackendRackspace.vala:49
 #: ../common/BackendS3.vala:59 ../common/BackendU1.vala:150
 msgid "Backup will begin when a network connection becomes available."
 msgstr ""
 
-#: ../common/BackendFile.vala:357 ../common/BackendFile.vala:421
+#: ../common/BackendFile.vala:384 ../common/BackendFile.vala:448
 msgid "Backup location not available"
 msgstr ""
 
-#: ../common/BackendFile.vala:358
+#: ../common/BackendFile.vala:385
 msgid "Waiting for a network connection…"
 msgstr ""
 
-#: ../common/BackendFile.vala:421
+#: ../common/BackendFile.vala:448
 #, c-format
 msgid "Waiting for ‘%s’ to become connected…"
 msgstr ""
 
-#: ../common/BackendRackspace.vala:58
-msgid "You must specify a Rackspace container in your preferences."
-msgstr ""
-
-#: ../common/BackendRackspace.vala:67 ../widgets/ConfigLocation.vala:194
+#: ../common/BackendRackspace.vala:69 ../widgets/ConfigLocation.vala:194
 msgid "Rackspace Cloud Files"
 msgstr ""
 
 #. Translators: %s is a folder.
-#: ../common/BackendRackspace.vala:70
+#: ../common/BackendRackspace.vala:72
 #, c-format
 msgid "%s on Rackspace Cloud Files"
 msgstr ""
 
-#: ../common/BackendRackspace.vala:117 ../common/BackendS3.vala:172
+#: ../common/BackendRackspace.vala:119 ../common/BackendS3.vala:172
 msgid "Permission denied"
 msgstr ""
 
-#: ../common/BackendRackspace.vala:138
+#: ../common/BackendRackspace.vala:140
 #, c-format
 msgid ""
 "You can sign up for a Rackspace Cloud Files account <a href=\"%s\">online</"
 "a>."
 msgstr ""
 
-#: ../common/BackendRackspace.vala:139
+#: ../common/BackendRackspace.vala:141
 msgid "Connect to Rackspace Cloud Files"
 msgstr ""
 
-#: ../common/BackendRackspace.vala:140
+#: ../common/BackendRackspace.vala:142
 msgid "_API access key"
 msgstr ""
 
-#: ../common/BackendRackspace.vala:141
+#: ../common/BackendRackspace.vala:143
 msgid "S_how API access key"
 msgstr ""
 
-#: ../common/BackendRackspace.vala:142
+#: ../common/BackendRackspace.vala:144
 msgid "_Remember API access key"
 msgstr ""
 
@@ -466,208 +462,68 @@
 msgid "Sign into Ubuntu One…"
 msgstr ""
 
+#: ../common/CommonUtils.vala:353
+#, c-format
+msgid "Could not find backup tool in %s.  Your installation is incomplete."
+msgstr ""
+
+#: ../common/CommonUtils.vala:355
+msgid "Could not load backup tool.  Your installation is incomplete."
+msgstr ""
+
+#: ../common/CommonUtils.vala:361
+msgid "Backup tool is broken.  Your installation is incomplete."
+msgstr ""
+
+#: ../common/CommonUtils.vala:383
+msgid "Could not start backup tool"
+msgstr ""
+
 #. Translators: this is the home folder and %s is the user's username
-#: ../common/CommonUtils.vala:498
+#: ../common/CommonUtils.vala:484
 #, c-format
 msgid "Home (%s)"
 msgstr ""
 
 #. Translators: this is the home folder
-#: ../common/CommonUtils.vala:503
+#: ../common/CommonUtils.vala:489
 msgid "Home"
 msgstr ""
 
 #. Translators: this is the trash folder
-#: ../common/CommonUtils.vala:508
+#: ../common/CommonUtils.vala:494
 msgid "Trash"
 msgstr ""
 
-#: ../common/DuplicityInfo.vala:109
-msgid "Could not run duplicity"
-msgstr ""
-
-#: ../common/DuplicityInfo.vala:114
-msgid ""
-"Could not understand duplicity version.\n"
-"\n"
-msgstr ""
-
-#: ../common/DuplicityInfo.vala:116
-#, c-format
-msgid ""
-"Could not understand duplicity version ‘%s’.\n"
-"\n"
-msgstr ""
-
-#: ../common/DuplicityInfo.vala:118
-msgid ""
-"Without duplicity, Déjà Dup Backup Tool cannot function. It will close now."
-msgstr ""
-
-#: ../common/DuplicityInfo.vala:122
-msgid "Duplicity’s version is too old"
-msgstr ""
-
-#: ../common/DuplicityInfo.vala:123
-#, c-format
-msgid ""
-"Déjà Dup Backup Tool requires at least version %d.%d.%.2d of duplicity, but "
-"only found version %d.%d.%.2d"
-msgstr ""
-
-#: ../common/Duplicity.vala:135 ../common/Duplicity.vala:198
-msgid "Paused (no network)"
-msgstr ""
-
-#: ../common/Duplicity.vala:405 ../common/Duplicity.vala:412
-#: ../common/Duplicity.vala:431 ../common/Duplicity.vala:436
-#: ../common/Operation.vala:79 ../common/Operation.vala:111
-msgid "Preparing…"
-msgstr ""
-
-#. Was not even a file path (maybe something goofy like computer://)
-#: ../common/Duplicity.vala:463
-#, c-format
-msgid "Could not restore ‘%s’: Not a valid file location"
-msgstr ""
-
-#. Tiny backup location.  Suggest they get a larger one.
-#: ../common/Duplicity.vala:529
-msgid "Backup location is too small.  Try using one with more space."
-msgstr ""
-
-#: ../common/Duplicity.vala:551
-msgid "Backup location does not have enough free space."
-msgstr ""
-
-#: ../common/Duplicity.vala:570 ../common/Duplicity.vala:584
-msgid "Cleaning up…"
-msgstr ""
-
-#. OK, we succeeded yay!  But some files didn't make it into the backup
-#. because we couldn't read them.  So tell the user so they don't think
-#. everything is hunky dory.
-#: ../common/Duplicity.vala:679
-msgid ""
-"Could not back up the following files.  Please make sure you are able to "
-"open them."
-msgstr ""
-
-#: ../common/Duplicity.vala:700 ../common/Duplicity.vala:1089
-#: ../deja-dup/AssistantOperation.vala:537
-msgid "Failed with an unknown error."
-msgstr ""
-
-#: ../common/Duplicity.vala:929
-#, c-format
-msgid "Could not restore ‘%s’: File not found in backup"
-msgstr ""
-
-#. notify upper layers, if they want to do anything
-#. Duplicity tried to ask the user what the encryption password is.
-#. notify upper layers, if they want to do anything
-#: ../common/Duplicity.vala:935 ../common/Duplicity.vala:1037
-#: ../common/Duplicity.vala:1041
-msgid "Bad encryption password."
-msgstr ""
-
-#: ../common/Duplicity.vala:940
-msgid "Computer name changed"
-msgstr ""
-
-#: ../common/Duplicity.vala:940
-#, c-format
-msgid ""
-"The existing backup is of a computer named %s, but the current computer’s "
-"name is %s.  If this is unexpected, you should back up to a different "
-"location."
-msgstr ""
-
-#: ../common/Duplicity.vala:975
-#, c-format
-msgid "Permission denied when trying to create ‘%s’."
-msgstr ""
-
-#. assume error is on backend side
-#: ../common/Duplicity.vala:979 ../common/Duplicity.vala:983
-#, c-format
-msgid "Permission denied when trying to read ‘%s’."
-msgstr ""
-
-#: ../common/Duplicity.vala:987
-#, c-format
-msgid "Permission denied when trying to delete ‘%s’."
-msgstr ""
-
-#: ../common/Duplicity.vala:994
-#, c-format
-msgid "Backup location ‘%s’ does not exist."
-msgstr ""
-
-#: ../common/Duplicity.vala:1000 ../common/Duplicity.vala:1060
-msgid "No space left."
-msgstr ""
-
-#: ../common/Duplicity.vala:1014
-msgid "Invalid ID."
-msgstr ""
-
-#: ../common/Duplicity.vala:1016
-msgid "Invalid secret key."
-msgstr ""
-
-#: ../common/Duplicity.vala:1018
-msgid "Your Amazon Web Services account is not signed up for the S3 service."
-msgstr ""
-
-#: ../common/Duplicity.vala:1031
-msgid "S3 bucket name is not available."
-msgstr ""
-
-#: ../common/Duplicity.vala:1045
-#, c-format
-msgid "Error reading file ‘%s’."
-msgstr ""
-
-#: ../common/Duplicity.vala:1047
-#, c-format
-msgid "Error writing file ‘%s’."
-msgstr ""
-
-#: ../common/Duplicity.vala:1062
-#, c-format
-msgid "No space left in ‘%s’."
-msgstr ""
-
-#: ../common/Duplicity.vala:1070
-msgid "No backup files found"
-msgstr ""
-
-#: ../common/Duplicity.vala:1120
-msgid "Uploading…"
-msgstr ""
-
-#: ../common/OperationRestore.vala:52
+#: ../common/OperationRestore.vala:51
 msgid "Restoring files…"
 msgstr ""
 
-#: ../common/Operation.vala:71
+#: ../common/Operation.vala:60
 msgid "Backing up…"
 msgstr ""
 
-#: ../common/Operation.vala:73 ../deja-dup/AssistantRestore.vala:484
+#: ../common/Operation.vala:62 ../deja-dup/AssistantRestore.vala:484
 msgid "Restoring…"
 msgstr ""
 
-#: ../common/Operation.vala:75
+#: ../common/Operation.vala:64
 msgid "Checking for backups…"
 msgstr ""
 
-#: ../common/Operation.vala:77
+#: ../common/Operation.vala:66
 msgid "Listing files…"
 msgstr ""
 
-#: ../common/Operation.vala:281
+#: ../common/Operation.vala:68 ../common/Operation.vala:100
+#: ../tools/duplicity/DuplicityJob.vala:386
+#: ../tools/duplicity/DuplicityJob.vala:393
+#: ../tools/duplicity/DuplicityJob.vala:412
+#: ../tools/duplicity/DuplicityJob.vala:417
+msgid "Preparing…"
+msgstr ""
+
+#: ../common/Operation.vala:237
 msgid "Another backup operation is already running"
 msgstr ""
 
@@ -681,11 +537,11 @@
 msgid "_Back Up"
 msgstr ""
 
+#: ../deja-dup/AssistantBackup.vala:49
+msgid "Creating the first backup.  This may take a while."
+msgstr ""
+
 #: ../deja-dup/AssistantBackup.vala:50
-msgid "Creating the first backup.  This may take a while."
-msgstr ""
-
-#: ../deja-dup/AssistantBackup.vala:51
 msgid ""
 "Creating a fresh backup to protect against backup corruption.  This will "
 "take longer than normal."
@@ -693,19 +549,19 @@
 
 #. Translators:  This is the phrase 'Backing up' in the larger phrase
 #. "Backing up '%s'".  %s is a filename.
-#: ../deja-dup/AssistantBackup.vala:81
+#: ../deja-dup/AssistantBackup.vala:80
 msgid "Backing up:"
 msgstr ""
 
-#: ../deja-dup/AssistantBackup.vala:90
+#: ../deja-dup/AssistantBackup.vala:89
 msgid "Backup Failed"
 msgstr ""
 
-#: ../deja-dup/AssistantBackup.vala:93
+#: ../deja-dup/AssistantBackup.vala:92
 msgid "Backup Finished"
 msgstr ""
 
-#: ../deja-dup/AssistantBackup.vala:101
+#: ../deja-dup/AssistantBackup.vala:100
 msgid "Backing Up…"
 msgstr ""
 
@@ -753,6 +609,12 @@
 msgid "Summary"
 msgstr ""
 
+#: ../deja-dup/AssistantOperation.vala:537
+#: ../tools/duplicity/DuplicityJob.vala:694
+#: ../tools/duplicity/DuplicityJob.vala:1076
+msgid "Failed with an unknown error."
+msgstr ""
+
 #: ../deja-dup/AssistantOperation.vala:730
 msgid "Require Password?"
 msgstr ""
@@ -823,7 +685,7 @@
 
 #. Translators:  This is the word 'Restoring' in the phrase
 #. "Restoring '%s'".  %s is a filename.
-#: ../deja-dup/AssistantRestore.vala:307
+#: ../deja-dup/AssistantRestore.vala:306
 msgid "Restoring:"
 msgstr ""
 
@@ -831,38 +693,38 @@
 #. This will be in a list with other strings that just have %x (the
 #. current date).  So make sure if you change this, it still makes
 #. sense in that context.
-#: ../deja-dup/AssistantRestore.vala:349
+#: ../deja-dup/AssistantRestore.vala:348
 #, c-format
 msgid "%x %X"
 msgstr ""
 
-#: ../deja-dup/AssistantRestore.vala:362
+#: ../deja-dup/AssistantRestore.vala:361
 msgid "No backups to restore"
 msgstr ""
 
-#: ../deja-dup/AssistantRestore.vala:439
+#: ../deja-dup/AssistantRestore.vala:437
 msgid "Original location"
 msgstr ""
 
-#: ../deja-dup/AssistantRestore.vala:450
+#: ../deja-dup/AssistantRestore.vala:448
 msgid "File to restore"
 msgid_plural "Files to restore"
 msgstr[0] ""
 msgstr[1] ""
 
+#: ../deja-dup/AssistantRestore.vala:469
+msgid "Restore Failed"
+msgstr ""
+
 #: ../deja-dup/AssistantRestore.vala:471
-msgid "Restore Failed"
-msgstr ""
-
-#: ../deja-dup/AssistantRestore.vala:473
 msgid "Restore Finished"
 msgstr ""
 
-#: ../deja-dup/AssistantRestore.vala:475
+#: ../deja-dup/AssistantRestore.vala:474
 msgid "Your files were successfully restored."
 msgstr ""
 
-#: ../deja-dup/AssistantRestore.vala:478
+#: ../deja-dup/AssistantRestore.vala:477
 msgid "Your file was successfully restored."
 msgid_plural "Your files were successfully restored."
 msgstr[0] ""
@@ -906,7 +768,7 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: ../deja-dup/AssistantRestoreMissing.vala:455
+#: ../deja-dup/AssistantRestoreMissing.vala:454
 msgid "Scanning finished"
 msgstr ""
 
@@ -990,20 +852,20 @@
 msgid "S_how password"
 msgstr ""
 
-#: ../deja-dup/MountOperationAssistant.vala:82
+#: ../deja-dup/MountOperationAssistant.vala:80
 msgid "Location not available"
 msgstr ""
 
-#: ../deja-dup/MountOperationAssistant.vala:168
+#: ../deja-dup/MountOperationAssistant.vala:166
 msgid "Connect _anonymously"
 msgstr ""
 
-#: ../deja-dup/MountOperationAssistant.vala:172
+#: ../deja-dup/MountOperationAssistant.vala:170
 msgid "Connect as u_ser"
 msgstr ""
 
 #. Translators: this is a Windows networking domain
-#: ../deja-dup/MountOperationAssistant.vala:215
+#: ../deja-dup/MountOperationAssistant.vala:213
 msgid "_Domain"
 msgstr ""
 
@@ -1140,6 +1002,157 @@
 msgid "Categories"
 msgstr ""
 
+#: ../tools/duplicity/DuplicityJob.vala:89
+#: ../tools/duplicity/DuplicityJob.vala:173
+msgid "Paused (no network)"
+msgstr ""
+
+#. Was not even a file path (maybe something goofy like computer://)
+#: ../tools/duplicity/DuplicityJob.vala:444
+#, c-format
+msgid "Could not restore ‘%s’: Not a valid file location"
+msgstr ""
+
+#. Tiny backup location.  Suggest they get a larger one.
+#: ../tools/duplicity/DuplicityJob.vala:510
+msgid "Backup location is too small.  Try using one with more space."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:532
+msgid "Backup location does not have enough free space."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:551
+#: ../tools/duplicity/DuplicityJob.vala:565
+msgid "Cleaning up…"
+msgstr ""
+
+#. OK, we succeeded yay!  But some files didn't make it into the backup
+#. because we couldn't read them.  So tell the user so they don't think
+#. everything is hunky dory.
+#: ../tools/duplicity/DuplicityJob.vala:661
+msgid ""
+"Could not back up the following files.  Please make sure you are able to "
+"open them."
+msgstr ""
+
+#. OK, we succeeded yay!  But some files didn't actually restore
+#. because we couldn't write to them.  So tell the user so they
+#. don't think everything is hunky dory.
+#: ../tools/duplicity/DuplicityJob.vala:677
+msgid ""
+"Could not restore the following files.  Please make sure you are able to "
+"write to them."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:924
+#, c-format
+msgid "Could not restore ‘%s’: File not found in backup"
+msgstr ""
+
+#. notify upper layers, if they want to do anything
+#. Duplicity tried to ask the user what the encryption password is.
+#. notify upper layers, if they want to do anything
+#: ../tools/duplicity/DuplicityJob.vala:930
+#: ../tools/duplicity/DuplicityJob.vala:1028
+#: ../tools/duplicity/DuplicityJob.vala:1032
+msgid "Bad encryption password."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:935
+msgid "Computer name changed"
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:935
+#, c-format
+msgid ""
+"The existing backup is of a computer named %s, but the current computer’s "
+"name is %s.  If this is unexpected, you should back up to a different "
+"location."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:970
+#, c-format
+msgid "Permission denied when trying to create ‘%s’."
+msgstr ""
+
+#. assume error is on backend side
+#: ../tools/duplicity/DuplicityJob.vala:974
+#: ../tools/duplicity/DuplicityJob.vala:978
+#, c-format
+msgid "Permission denied when trying to read ‘%s’."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:982
+#, c-format
+msgid "Permission denied when trying to delete ‘%s’."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:989
+#, c-format
+msgid "Backup location ‘%s’ does not exist."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:995
+#: ../tools/duplicity/DuplicityJob.vala:1047
+msgid "No space left."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1009
+msgid "Invalid ID."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1011
+msgid "Invalid secret key."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1013
+msgid "Your Amazon Web Services account is not signed up for the S3 service."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1022
+msgid "S3 bucket name is not available."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1036
+#, c-format
+msgid "Error reading file ‘%s’."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1038
+#, c-format
+msgid "Error writing file ‘%s’."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1049
+#, c-format
+msgid "No space left in ‘%s’."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1057
+msgid "No backup files found"
+msgstr ""
+
+#: ../tools/duplicity/DuplicityJob.vala:1107
+msgid "Uploading…"
+msgstr ""
+
+#: ../tools/duplicity/DuplicityPlugin.vala:41
+msgid "Could not understand duplicity version."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityPlugin.vala:47
+#, c-format
+msgid "Could not understand duplicity version ‘%s’."
+msgstr ""
+
+#: ../tools/duplicity/DuplicityPlugin.vala:64
+#, c-format
+msgid ""
+"Déjà Dup Backup Tool requires at least version %d.%d.%.2d of duplicity, but "
+"only found version %d.%d.%.2d"
+msgstr ""
+
 #: ../widgets/ConfigDelete.vala:43
 msgid "At least a month"
 msgstr ""
@@ -1301,7 +1314,7 @@
 msgid "Local Folder"
 msgstr ""
 
-#: ../widgets/ConfigLocationCustom.vala:31
+#: ../widgets/ConfigLocationCustom.vala:33
 msgid "_URI"
 msgstr ""
 
@@ -1320,17 +1333,17 @@
 msgstr ""
 
 #: ../widgets/ConfigLocationDAV.vala:46 ../widgets/ConfigLocationFTP.vala:38
-#: ../widgets/ConfigLocationFile.vala:44 ../widgets/ConfigLocationS3.vala:33
+#: ../widgets/ConfigLocationFile.vala:45 ../widgets/ConfigLocationS3.vala:33
 #: ../widgets/ConfigLocationSMB.vala:34 ../widgets/ConfigLocationSSH.vala:37
-#: ../widgets/ConfigLocationU1.vala:31 ../widgets/ConfigLocationVolume.vala:31
+#: ../widgets/ConfigLocationU1.vala:33 ../widgets/ConfigLocationVolume.vala:33
 msgid "_Folder"
 msgstr ""
 
-#: ../widgets/ConfigLocationFile.vala:38
+#: ../widgets/ConfigLocationFile.vala:39
 msgid "_Choose Folder…"
 msgstr ""
 
-#: ../widgets/ConfigLocationFile.vala:49
+#: ../widgets/ConfigLocationFile.vala:50
 msgid "Choose Folder"
 msgstr ""
 

=== modified file 'preferences/Makefile.am'
--- preferences/Makefile.am	2012-02-02 18:56:52 +0000
+++ preferences/Makefile.am	2012-04-30 17:55:21 +0000
@@ -67,6 +67,7 @@
 	$(SHARED_VALAFLAGS) \
 	--pkg @GTK_MODULE@ \
 	--pkg gio-2.0 \
+	--pkg libpeas-1.0 \
 	--pkg libcommon \
 	--pkg libwidgets \
 	--pkg config

=== modified file 'tests/runner/Makefile.am'
--- tests/runner/Makefile.am	2012-04-24 00:14:48 +0000
+++ tests/runner/Makefile.am	2012-04-30 17:55:21 +0000
@@ -45,6 +45,7 @@
 	$(NETWORKMONITOR_VALAFLAGS) \
 	--pkg config \
 	--pkg gio-2.0 \
+	--pkg libpeas-1.0 \
 	--pkg posix \
 	--pkg libcommon
 

=== modified file 'tests/runner/mock/duplicity'
--- tests/runner/mock/duplicity	2012-04-18 02:24:54 +0000
+++ tests/runner/mock/duplicity	2012-04-30 17:55:21 +0000
@@ -46,6 +46,10 @@
 
 import sys, os, shlex, getpass, time
 
+if sys.argv[1] == '--version':
+  print 'duplicity 9.9.99'
+  sys.exit(0)
+
 if not os.path.exists(os.environ['DEJA_DUP_TEST_MOCKSCRIPT']):
   print >> logfd, "TESTFAIL: no mockscript"
   sys.exit(-1)

=== modified file 'tests/runner/runner.vala'
--- tests/runner/runner.vala	2012-04-18 13:52:15 +0000
+++ tests/runner/runner.vala	2012-04-30 17:55:21 +0000
@@ -45,6 +45,7 @@
   var cachedir = Path.build_filename(dir, "cache");
   DirUtils.create_with_parents(Path.build_filename(cachedir, "deja-dup"), 0700);
 
+  Environment.set_variable("DEJA_DUP_TOOLS_PATH", "../../tools/duplicity", true);
   Environment.set_variable("DEJA_DUP_TEST_MOCKSCRIPT", Path.build_filename(dir, "mockscript"), true);
   Environment.set_variable("XDG_CACHE_HOME", cachedir, true);
   Environment.set_variable("PATH", "./mock:" + Environment.get_variable("PATH"), true);

=== modified file 'tests/unit/Makefile.am'
--- tests/unit/Makefile.am	2012-04-24 00:14:48 +0000
+++ tests/unit/Makefile.am	2012-04-30 17:55:21 +0000
@@ -50,6 +50,7 @@
 	$(NETWORKMONITOR_VALAFLAGS) \
 	--pkg config \
 	--pkg gio-2.0 \
+	--pkg libpeas-1.0 \
 	--pkg posix \
 	--pkg libcommon
 

=== added directory 'tools'
=== added file 'tools/Makefile.am'
--- tools/Makefile.am	1970-01-01 00:00:00 +0000
+++ tools/Makefile.am	2012-04-30 17:55:21 +0000
@@ -0,0 +1,20 @@
+# -*- Mode: Makefile; indent-tabs-mode: t; tab-width: 2 -*-
+#
+# This file is part of Déjà Dup.
+# For copyright information, see AUTHORS.
+#
+# Déjà Dup is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Déjà Dup is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
+
+SUBDIRS = duplicity
+

=== added directory 'tools/duplicity'
=== renamed file 'common/DuplicityInstance.vala' => 'tools/duplicity/DuplicityInstance.vala'
--- common/DuplicityInstance.vala	2012-03-19 15:27:18 +0000
+++ tools/duplicity/DuplicityInstance.vala	2012-04-30 17:55:21 +0000
@@ -19,8 +19,6 @@
 
 using GLib;
 
-namespace DejaDup {
-
 internal class DuplicityInstance : Object
 {
   public signal void done(bool success, bool cancelled);
@@ -38,8 +36,8 @@
       verbose = true;
 
     if (as_root) {
-      var settings = get_settings();
-      if (!settings.get_boolean(ROOT_PROMPT_KEY))
+      var settings = DejaDup.get_settings();
+      if (!settings.get_boolean(DejaDup.ROOT_PROMPT_KEY))
         as_root = false;
     }
     
@@ -591,5 +589,3 @@
   }
 }
 
-} // end namespace
-

=== renamed file 'common/Duplicity.vala' => 'tools/duplicity/DuplicityJob.vala'
--- common/Duplicity.vala	2012-03-22 14:44:44 +0000
+++ tools/duplicity/DuplicityJob.vala	2012-04-30 17:55:21 +0000
@@ -19,57 +19,12 @@
 
 using GLib;
 
-namespace DejaDup {
-
-internal class Duplicity : Object
+internal class DuplicityJob : DejaDup.ToolJob
 {
-  /*
-   * Vala implementation of various methods for accessing duplicity
-   *
-   * Vala implementation of various methods for accessing duplicity from
-   * vala withot the need of manually running duplicity command.
-   */
+  DejaDup.ToolJob.Mode original_mode {get; private set;}
+  bool error_issued {get; private set; default = false;}
+  bool was_stopped {get; private set; default = false;}
 
-  public signal void done(bool success, bool cancelled, string? detail);
-  public signal void raise_error(string errstr, string? detail);
-  public signal void action_desc_changed(string action);
-  public signal void action_file_changed(File file, bool actual);
-  public signal void progress(double percent);
-  /*
-   * Signal emitted when collection dates are retrieved from duplicity
-   */
-  public signal void collection_dates(List<string>? dates);
-  public signal void listed_current_files(string date, string file);
-  public signal void question(string title, string msg);
-  public signal void is_full(bool first);
-  public signal void bad_encryption_password();
-  
-  public Operation.Mode original_mode {get; construct;}
-  public Operation.Mode mode {get; private set; default = Operation.Mode.INVALID;}
-  public bool error_issued {get; private set; default = false;}
-  public bool was_stopped {get; private set; default = false;}
-  
-  public File local {get; set;}
-  public Backend backend {get; set;}
-  public List<File> includes;
-  public List<File> excludes;
-  public bool use_progress {get; set; default = true;}
-  public string encrypt_password {private get; set; default = null;}
-  
-  private List<File> _restore_files;
-  public List<File> restore_files {
-    get {
-      return this._restore_files;
-    }
-    set {
-      foreach (File f in this._restore_files)
-        f.unref();
-      this._restore_files = value.copy();
-      foreach (File f in this._restore_files)
-        f.ref();
-    }
-  }
-  
   protected enum State {
     NORMAL,
     DRY_RUN, // used when backing up, and we need to first get time estimate
@@ -128,16 +83,12 @@
 
   void network_changed()
   {
-    if (Network.get().connected)
+    if (DejaDup.Network.get().connected)
       resume();
     else
       pause(_("Paused (no network)"));
   }
 
-  public Duplicity(Operation.Mode mode) {
-    Object(original_mode: mode);
-  }
-  
   construct {
     if (slash == null) {
       slash = File.new_for_path("/");
@@ -157,34 +108,67 @@
   }
 
   ~Duplicity() {
-    Network.get().notify["connected"].disconnect(network_changed);
+    DejaDup.Network.get().notify["connected"].disconnect(network_changed);
   }
 
-  public virtual void start(Backend backend,
-                            List<string>? argv, List<string>? envp)
+  public override void start()
   {
     // save arguments for calling duplicity again later
+    if (original_mode == DejaDup.ToolJob.Mode.INVALID)
+      original_mode = mode;
     mode = original_mode;
-    this.backend = backend;
     saved_argv = new List<string>();
     saved_envp = new List<string>();
     backend_argv = new List<string>();
-    foreach (string s in argv) saved_argv.append(s);
-    foreach (string s in envp) saved_envp.append(s);
-    backend.add_argv(Operation.Mode.INVALID, ref backend_argv);
+    backend.add_argv(DejaDup.ToolJob.Mode.INVALID, ref backend_argv);
+    backend.add_argv(mode, ref saved_argv);
     
-    if (mode == Operation.Mode.BACKUP)
+    if (mode == DejaDup.ToolJob.Mode.BACKUP)
       process_include_excludes();
     
-    var settings = get_settings();
-    delete_age = settings.get_int(DELETE_AFTER_KEY);
+    var settings = DejaDup.get_settings();
+    delete_age = settings.get_int(DejaDup.DELETE_AFTER_KEY);
+
+    get_envp();
+  }
+
+  async void get_envp()
+  {
+    try {
+      backend.envp_ready.connect(continue_with_envp);
+      yield backend.get_envp();
+    }
+    catch (Error e) {
+      raise_error(e.message, null);
+      done(false, false, null);
+    }
+  }
+
+  void continue_with_envp(DejaDup.Backend b, bool success, List<string>? envp, string? error)
+  {
+    /*
+     * Starts Duplicity backup with added enviroment variables
+     * 
+     * Start Duplicity backup process with costum values for enviroment variables.
+     */
+    backend.envp_ready.disconnect(continue_with_envp);
+
+    if (!success) {
+      if (error != null)
+        raise_error(error, null);
+      done(false, false, null);
+      return;
+    }
+
+    foreach (string s in envp)
+      saved_envp.append(s);
 
     if (!restart())
       done(false, false, null);
 
     if (!backend.is_native()) {
-      Network.get().notify["connected"].connect(network_changed);
-      if (!Network.get().connected) {
+      DejaDup.Network.get().notify["connected"].connect(network_changed);
+      if (!DejaDup.Network.get().connected) {
         debug("No connection found. Postponing the backup.");
         pause(_("Paused (no network)"));
       }
@@ -337,11 +321,11 @@
     saved_argv.append("--exclude=**");
   }
   
-  public void cancel() {
+  public override void cancel() {
     var prev_mode = mode;
-    mode = Operation.Mode.INVALID;
+    mode = DejaDup.ToolJob.Mode.INVALID;
     
-    if (prev_mode == Operation.Mode.BACKUP && state == State.NORMAL) {
+    if (prev_mode == DejaDup.ToolJob.Mode.BACKUP && state == State.NORMAL) {
       if (cleanup())
         return;
     }
@@ -349,14 +333,14 @@
     cancel_inst();
   }
   
-  public void stop() {
+  public override void stop() {
     // just abruptly stop, without a cleanup, duplicity will resume
     was_stopped = true;
-    mode = Operation.Mode.INVALID;
+    mode = DejaDup.ToolJob.Mode.INVALID;
     cancel_inst();
   }
 
-  public void pause(string? reason)
+  public override void pause(string? reason)
   {
     if (inst != null) {
       inst.pause();
@@ -365,7 +349,7 @@
     }
   }
 
-  public void resume()
+  public override void resume()
   {
     if (inst != null) {
       inst.resume();
@@ -385,7 +369,7 @@
     if (restore_files == null) // only clear if we're not in middle of restore sequence
       local_error_files = null;
     
-    if (mode == Operation.Mode.INVALID)
+    if (mode == DejaDup.ToolJob.Mode.INVALID)
       return false;
     
     var extra_argv = new List<string>();
@@ -393,18 +377,18 @@
     File custom_local = null;
     
     switch (original_mode) {
-    case Operation.Mode.BACKUP:
+    case DejaDup.ToolJob.Mode.BACKUP:
       // We need to first check the backup status to see if we need to start
       // a full backup and to see if we should use encryption.
       if (!checked_collection_info) {
-        mode = Operation.Mode.STATUS;
+        mode = DejaDup.ToolJob.Mode.STATUS;
         state = State.STATUS;
         action_desc = _("Preparing…");
       }
       // If we're backing up, and the version of duplicity supports it, we should
       // first run using --dry-run to get the total size of the backup, to make
       // accurate progress bars.
-      else if (use_progress && !has_progress_total) {
+      else if ((flags & DejaDup.ToolJob.Flags.NO_PROGRESS) == 0 && !has_progress_total) {
         state = State.DRY_RUN;
         action_desc = _("Preparing…");
         extra_argv.append("--dry-run");
@@ -419,16 +403,16 @@
       }
       
       break;
-    case Operation.Mode.RESTORE:
+    case DejaDup.ToolJob.Mode.RESTORE:
       // We need to first check the backup status to see if we should use
       // encryption.
       if (!checked_collection_info) {
-        mode = Operation.Mode.STATUS;
+        mode = DejaDup.ToolJob.Mode.STATUS;
         state = State.STATUS;
         action_desc = _("Preparing…");
       }
       else if (!has_checked_contents) {
-        mode = Operation.Mode.LIST;
+        mode = DejaDup.ToolJob.Mode.LIST;
         state = State.CHECK_CONTENTS;
         action_desc = _("Preparing…");
       }
@@ -494,7 +478,7 @@
     // Send appropriate description for what we're about to do.  Is often
     // very quickly overridden by a message like "Backing up file X"
     if (action_desc == null)
-      action_desc = Operation.mode_to_string(mode);
+      action_desc = DejaDup.Operation.mode_to_string(mode);
     set_status(action_desc);
     
     connect_and_start(extra_argv, null, null, custom_local);
@@ -633,7 +617,7 @@
 
         /* Set full backup threshold and determine whether we should trigger
            a full backup. */
-        if (mode == Operation.Mode.BACKUP && got_collection_info) {
+        if (mode == DejaDup.ToolJob.Mode.BACKUP && got_collection_info) {
           Date threshold = DejaDup.get_full_backup_threshold_date();
           Date full_backup = Date();
           foreach (DateInfo info in collection_info) {
@@ -661,7 +645,7 @@
         break;
       
       case State.NORMAL:
-        if (mode == Operation.Mode.RESTORE && restore_files != null) {
+        if (mode == DejaDup.ToolJob.Mode.RESTORE && restore_files != null) {
           _restore_files.delete_link(_restore_files);
           if (restore_files != null) {
             if (restart())
@@ -669,7 +653,7 @@
           }
         }
 
-        if (mode == Operation.Mode.BACKUP) {
+        if (mode == DejaDup.ToolJob.Mode.BACKUP) {
           if (local_error_files != null) {
             // OK, we succeeded yay!  But some files didn't make it into the backup
             // because we couldn't read them.  So tell the user so they don't think
@@ -681,11 +665,11 @@
             }
           }
 
-          mode = Operation.Mode.INVALID; // mark 'done' so when we delete, we don't restart
+          mode = DejaDup.ToolJob.Mode.INVALID; // mark 'done' so when we delete, we don't restart
           if (delete_files_if_needed())
             return;
         }
-        else if (mode == Operation.Mode.RESTORE) {
+        else if (mode == DejaDup.ToolJob.Mode.RESTORE) {
           if (local_error_files != null) {
             // OK, we succeeded yay!  But some files didn't actually restore
             // because we couldn't write to them.  So tell the user so they
@@ -899,7 +883,7 @@
   {
     disconnect_inst();
     question(t, m);
-    var rv = mode != Operation.Mode.INVALID; // return whether we were canceled
+    var rv = mode != DejaDup.ToolJob.Mode.INVALID; // return whether we were canceled
     if (!rv)
       handle_done(null, false, true);
     return rv;
@@ -964,7 +948,7 @@
         // If it's still bad, we'll do a full cleanup and try again.
         // If it's *still* bad, tell the user, but I'm not sure what they can
         // do about it.
-        if (mode == Operation.Mode.BACKUP) {
+        if (mode == DejaDup.ToolJob.Mode.BACKUP) {
           // strip date info from volume (after cleanup below, we'll get new date)
           var this_volume = parse_duplicity_file(firstline[2], 2);
           if (last_bad_volume != this_volume) {
@@ -1030,7 +1014,7 @@
       break;
     case "S3CreateError":
       if (text.contains("<Code>BucketAlreadyExists</Code>")) {
-        if (((BackendS3)backend).bump_bucket()) {
+        if (((DejaDup.BackendS3)backend).bump_bucket()) {
           if (restart()) // get_remote() will eventually grab new bucket name
             return;
         }
@@ -1048,14 +1032,14 @@
         show_error(_("Bad encryption password."));
       else if (text.contains("[Errno 5]") && // I/O Error
                last_touched_file != null) {
-        if (mode == Operation.Mode.BACKUP)
+        if (mode == DejaDup.ToolJob.Mode.BACKUP)
           show_error(_("Error reading file ‘%s’.").printf(last_touched_file.get_parse_name()));
         else
           show_error(_("Error writing file ‘%s’.").printf(last_touched_file.get_parse_name()));
       }
       else if (text.contains("[Errno 28]")) { // No space left on device
         string where = null;
-        if (mode == Operation.Mode.BACKUP)
+        if (mode == DejaDup.ToolJob.Mode.BACKUP)
           where = backend.get_location_pretty();
         else
           where = local.get_path();
@@ -1138,8 +1122,8 @@
     if (firstline.length > 1) {
       switch (int.parse(firstline[1])) {
       case DEBUG_GENERIC:
-        if (mode == Operation.Mode.STATUS &&
-            !DuplicityInfo.get_default().reports_encryption &&
+        if (mode == DejaDup.ToolJob.Mode.STATUS &&
+            /*!DuplicityInfo.get_default().reports_encryption &&*/
             !detected_encryption) {
           if (gpg_regex != null && gpg_regex.match(text)) {
             detected_encryption = true;
@@ -1153,7 +1137,7 @@
 
   void process_file_stat(string date, string file, List<string> data, string text)
   {
-    if (mode != Operation.Mode.LIST)
+    if (mode != DejaDup.ToolJob.Mode.LIST)
       return;
     if (state == State.CHECK_CONTENTS) {
       var gfile = make_file_obj(file);
@@ -1223,7 +1207,7 @@
      * this all up and report back to caller via a signal.
      * We're really only interested in the list of entries in the complete chain.
      */
-    if (mode != Operation.Mode.STATUS || got_collection_info)
+    if (mode != DejaDup.ToolJob.Mode.STATUS || got_collection_info)
       return;
     
     var timeval = TimeVal();
@@ -1247,6 +1231,7 @@
           info.full = tokens[1] == "full";
           infos.append(info);
 
+/*
           if (DuplicityInfo.get_default().reports_encryption &&
               !detected_encryption &&
               tokens.length > 4) {
@@ -1255,6 +1240,7 @@
             detected_encryption = true;
             existing_encrypted = tokens[4] == "enc";
           }
+*/
         }
       }
       else if (in_chain)
@@ -1283,7 +1269,7 @@
         // up before we continue.  We don't want to wait until we finish to
         // clean them up, since we may want that space, and if there's a bug
         // in ourselves, we may never get to it.
-        if (mode == Operation.Mode.BACKUP && !this.cleaned_up_once)
+        if (mode == DejaDup.ToolJob.Mode.BACKUP && !this.cleaned_up_once)
           cleanup(); // stops current backup, cleans up, then resumes
         break;
 
@@ -1349,7 +1335,7 @@
     //
     // For local filesystems, we'll choose large volsize.
     // For remote FSs, we'll go smaller.
-    if (in_testing_mode())
+    if (DejaDup.in_testing_mode())
       return 1;
     else if (backend.is_native())
       return 50;
@@ -1403,24 +1389,26 @@
     if (argv_entire == null) {
       // add operation, local, and remote args
       switch (mode) {
-      case Operation.Mode.BACKUP:
+      case DejaDup.ToolJob.Mode.BACKUP:
         if (is_full_backup)
           argv.prepend("full");
         argv.append("--volsize=%d".printf(get_volsize()));
         argv.append(local_arg.get_path());
         argv.append(get_remote());
         break;
-      case Operation.Mode.RESTORE:
+      case DejaDup.ToolJob.Mode.RESTORE:
         argv.prepend("restore");
+        if (time != null)
+          argv.append("--time=%s".printf(time));
         argv.append("--force");
         argv.append(get_remote());
         argv.append(local_arg.get_path());
         break;
-      case Operation.Mode.STATUS:
+      case DejaDup.ToolJob.Mode.STATUS:
         argv.prepend("collection-status");
         argv.append(get_remote());
         break;
-      case Operation.Mode.LIST:
+      case DejaDup.ToolJob.Mode.LIST:
         argv.prepend("list-current-files");
         argv.append(get_remote());
         break;
@@ -1462,5 +1450,3 @@
   }
 }
 
-} // end namespace
-

=== added file 'tools/duplicity/DuplicityPlugin.vala'
--- tools/duplicity/DuplicityPlugin.vala	1970-01-01 00:00:00 +0000
+++ tools/duplicity/DuplicityPlugin.vala	2012-04-30 17:55:21 +0000
@@ -0,0 +1,84 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
+/*
+    This file is part of Déjà Dup.
+    For copyright information, see AUTHORS.
+
+    Déjà Dup is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Déjà Dup is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib;
+
+public class DuplicityPlugin : DejaDup.ToolPlugin
+{
+  bool has_been_setup = false;
+
+  construct
+  {
+    name = "Duplicity";
+  }
+
+  static const int REQUIRED_MAJOR = 0;
+  static const int REQUIRED_MINOR = 6;
+  static const int REQUIRED_MICRO = 14;
+  void do_initial_setup () throws Error
+  {
+    string output;
+    Process.spawn_command_line_sync("duplicity --version", out output, null, null);
+
+    var tokens = output.split(" ", 2);
+    if (tokens == null || tokens[0] == null || tokens[1] == null)
+      throw new SpawnError.FAILED(_("Could not understand duplicity version."));
+
+    // First token is 'duplicity' and is ignorable.  Second looks like '0.5.03'
+    var version_string = tokens[1].strip();
+    var ver_tokens = version_string.split(".");
+    if (ver_tokens == null || ver_tokens[0] == null)
+      throw new SpawnError.FAILED(_("Could not understand duplicity version ‘%s’.").printf(version_string));
+
+    int major = 0;
+    int minor = 0;
+    int micro = 0;
+    major = int.parse(ver_tokens[0]);
+    // Don't error out if no minor or micro.  Duplicity might not have them?
+    if (ver_tokens[1] != null) {
+      minor = int.parse(ver_tokens[1]);
+      if (ver_tokens[2] != null)
+        micro = int.parse(ver_tokens[2]);
+    }
+
+    var meets = (major > REQUIRED_MAJOR) ||
+                (major == REQUIRED_MAJOR && minor > REQUIRED_MINOR) ||
+                (major == REQUIRED_MAJOR && minor == REQUIRED_MINOR && micro >= REQUIRED_MICRO);
+    if (!meets)
+      throw new SpawnError.FAILED(_("Déjà Dup Backup Tool requires at least version %d.%d.%.2d of duplicity, but only found version %d.%d.%.2d").printf(REQUIRED_MAJOR, REQUIRED_MINOR, REQUIRED_MICRO, major, minor, micro));
+  }
+
+  public override DejaDup.ToolJob create_job () throws Error
+  {
+    if (!has_been_setup) {
+      do_initial_setup();
+      has_been_setup = true;
+    }
+    return new DuplicityJob();
+  }
+}
+
+[ModuleInit]
+public void peas_register_types (GLib.TypeModule module)
+{
+  var objmodule = module as Peas.ObjectModule;
+  objmodule.register_extension_type (typeof (Peas.Activatable),
+                                     typeof (DuplicityPlugin));
+}
+

=== added file 'tools/duplicity/Makefile.am'
--- tools/duplicity/Makefile.am	1970-01-01 00:00:00 +0000
+++ tools/duplicity/Makefile.am	2012-04-30 17:55:21 +0000
@@ -0,0 +1,63 @@
+# -*- Mode: Makefile; indent-tabs-mode: t; tab-width: 2 -*-
+#
+# This file is part of Déjà Dup.
+# For copyright information, see AUTHORS.
+#
+# Déjà Dup is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Déjà Dup is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Déjà Dup.  If not, see <http://www.gnu.org/licenses/>.
+
+toolsdir = $(pkglibexecdir)/tools
+tools_LTLIBRARIES = libduplicity.la
+
+libduplicity_la_CFLAGS = \
+	-I$(top_srcdir)/common \
+	$(COMMON_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	-DPKG_LIBEXEC_DIR=\"$(pkglibexecdir)\" \
+	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\"
+
+libduplicity_la_LDFLAGS = -module -avoid-version
+
+libduplicity_la_LIBADD = \
+	$(COMMON_LIBS) \
+	@INTLLIBS@
+
+libduplicity_la_VALASOURCES = \
+	DuplicityInstance.vala \
+	DuplicityJob.vala \
+	DuplicityPlugin.vala \
+	RecursiveDelete.vala \
+	RecursiveMove.vala \
+	RecursiveOp.vala
+
+libduplicity_la_SOURCES = \
+	$(libduplicity_la_VALASOURCES)
+
+AM_VALAFLAGS = \
+	--library=libduplicity \
+	--vapidir $(top_srcdir)/common \
+	--vapidir=$(top_srcdir)/vapi \
+	$(SHARED_VALAFLAGS) \
+	--pkg gio-2.0 \
+	--pkg libpeas-1.0 \
+	--pkg gio-unix-2.0 \
+	--pkg posix \
+	--pkg libcommon \
+	--pkg config
+
+libduplicity_la_vala.stamp: $(top_srcdir)/config.h
+
+dist-hook:
+	cd $(distdir) && rm -f $(libduplicity_la_VALASOURCES:.vala=.c) \
+	                       libduplicity_la_vala.stamp
+

=== renamed file 'common/RecursiveDelete.vala' => 'tools/duplicity/RecursiveDelete.vala'
--- common/RecursiveDelete.vala	2011-11-06 01:16:05 +0000
+++ tools/duplicity/RecursiveDelete.vala	2012-04-30 17:55:21 +0000
@@ -19,8 +19,6 @@
 
 using GLib;
 
-namespace DejaDup {
-
 internal class RecursiveDelete : RecursiveOp
 {
   public RecursiveDelete(File source)
@@ -59,5 +57,3 @@
   }
 }
 
-} // end namespace
-

=== renamed file 'common/RecursiveMove.vala' => 'tools/duplicity/RecursiveMove.vala'
--- common/RecursiveMove.vala	2011-11-06 01:16:05 +0000
+++ tools/duplicity/RecursiveMove.vala	2012-04-30 17:55:21 +0000
@@ -19,8 +19,6 @@
 
 using GLib;
 
-namespace DejaDup {
-
 /**
  * Recursively moves one directory into another, merging files.  And by merge,
  * I mean it overwrites.  It skips any files it can't move and reports an
@@ -164,5 +162,3 @@
   }
 }
 
-} // end namespace
-

=== renamed file 'common/RecursiveOp.vala' => 'tools/duplicity/RecursiveOp.vala'
--- common/RecursiveOp.vala	2012-03-22 14:44:44 +0000
+++ tools/duplicity/RecursiveOp.vala	2012-04-30 17:55:21 +0000
@@ -19,8 +19,6 @@
 
 using GLib;
 
-namespace DejaDup {
-
 internal abstract class RecursiveOp : Object
 {
   public signal void done();
@@ -131,5 +129,3 @@
   }
 }
 
-} // end namespace
-

=== added file 'tools/duplicity/duplicity.plugin'
--- tools/duplicity/duplicity.plugin	1970-01-01 00:00:00 +0000
+++ tools/duplicity/duplicity.plugin	2012-04-30 17:55:21 +0000
@@ -0,0 +1,8 @@
+[Plugin]
+Loader=c
+Module=libduplicity.so
+Name=Duplicity Backend
+Description=Backs up with duplicity
+Authors=Michael Terry <michael.terry@xxxxxxxxxxxxx>
+Copyright=Copyright © 2012 Canonical Ltd
+Website=https://launchpad.net/deja-dup

=== modified file 'widgets/Makefile.am'
--- widgets/Makefile.am	2012-02-02 18:56:52 +0000
+++ widgets/Makefile.am	2012-04-30 17:55:21 +0000
@@ -78,6 +78,7 @@
 	$(UNITY_VALAFLAGS) \
 	--pkg libcommon \
 	--pkg @GTK_MODULE@ \
+	--pkg libpeas-1.0 \
 	--pkg uriutils \
 	--pkg libnotify \
 	--pkg config


Follow ups