/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: Michael Terry
 */

using GLib;

public class DejaDup.OperationBackup : Operation
{
  public bool automatic {get; construct;}

  public OperationBackup(Backend backend, bool automatic) {
    Object(mode: ToolJob.Mode.BACKUP, backend: backend, automatic: automatic);
  }

  public async override void start()
  {
    DejaDup.update_last_run_timestamp(DejaDup.LAST_RUN_KEY);
    yield base.start();
  }

  protected override string? get_success_detail()
  {
    string detail = null;

    var error_files = get_local_error_files();
    if (error_files.length() > 0) {
      detail = _("Could not back up the following files.  Please make sure you are able to open them.");
      detail += "\n";
      foreach (var f in error_files) {
        detail += "\n%s".printf(f);
      }
    }

    return detail;
  }

  internal async override void operation_finished(bool success, bool cancelled)
  {
    if (success && !cancelled)
      DejaDup.update_last_run_timestamp(DejaDup.LAST_BACKUP_KEY);

    if (success && !cancelled && chained_op == null) {
      var verify = new OperationVerify(backend, job.tag);
      yield chain_op(verify, _("Verifying backup…"));
    } else {
      yield base.operation_finished(success, cancelled);
    }
  }

  async string[] get_unavailable_includes()
  {
    string[] unavailable = {};
    var install_env = DejaDup.InstallEnv.instance();
    var settings = DejaDup.get_settings();
    var include_list = settings.get_file_list(DejaDup.INCLUDE_LIST_KEY);
    foreach (var include in include_list) {
      if (!install_env.is_file_available(include))
        unavailable += yield DejaDup.get_nickname(include);
    }
    return unavailable;
  }

  protected override async bool prepare()
  {
    // Check that we aren't trying to back up any unavailable files
    var unavailable = yield get_unavailable_includes();
    if (unavailable.length > 0) {
      var msg = _("The following folders cannot be backed up because Backups does not have access to them:");
      msg += " " + string.joinv(", ", unavailable);
      raise_error(msg, null);
      done(false, false, null);
      return false;
    }

    return true;
  }

  protected override List<string>? make_argv()
  {
    var settings = get_settings();
    var include_list = settings.get_file_list(INCLUDE_LIST_KEY);
    var exclude_list = settings.get_file_list(EXCLUDE_LIST_KEY);

    var install_env = InstallEnv.instance();

    // Exclude directories no one wants to backup
    add_always_excluded_dirs(ref job.excludes, ref job.exclude_regexps);
    backend.add_excludes(ref job.excludes);

    // Keep this filtering of includes/excludes in sync
    // with OperationBackup's user-visible warnings.
    foreach (File s in exclude_list) {
      if (s.is_native())
        job.excludes.prepend(s);
    }
    foreach (File s in include_list) {
      if (s.is_native() && install_env.is_file_available(s))
        job.includes.prepend(s);
    }

    job.local = File.new_for_path(install_env.get_read_root());

    return null;
  }

  void add_always_excluded_dirs(ref List<File> files, ref List<File> regexps)
  {
    var cache_dir = Environment.get_user_cache_dir();
    var config_dir = Environment.get_user_config_dir();
    var home_dir = Environment.get_home_dir();

    // User doesn't care about cache
    if (cache_dir != null) {
      var cache = File.new_for_path(cache_dir);
      files.prepend(cache);

      // Always also exclude ~/.cache because we may be running confined with
      // a custom cache path, but we still want to exclude the user's normal
      // cache folder. No way to know if they have an unusual path for that,
      // though. So we just guess at the default path.
      if (home_dir != null) {
        var home = File.new_for_path(home_dir);
        var home_cache = home.get_child(".cache");
        if (!cache.equal(home_cache)) {
          files.prepend(home_cache);
        }
      }

      // We also add our special cache dir because if the user still especially
      // includes the cache dir, we still won't backup our own metadata.
      files.prepend(cache.get_child(Config.PACKAGE));
    }

    // Electron apps keep (large) cache dirs inside the config dir.
    // See https://github.com/electron/electron/issues/8124 for details.
    // Since Electron is being slow to fix this, we'll exclude them for now.
    // But once that is fixed, it would be nice to remove this, on the
    // offchance we'd hit an innocent app's actual config files (stored
    // in a folder called "Cache" for some reason).
    if (config_dir != null) {
      var config = File.new_for_path(config_dir);
      regexps.prepend(config.resolve_relative_path("*/Cache"));
      regexps.prepend(config.resolve_relative_path("*/Code Cache"));
    }
    if (home_dir != null) {
      var home = File.new_for_path(home_dir);
      // And hard code a bunch of other places this will show up (uncontained,
      // flatpak, and snap)
      if (config_dir != Path.build_filename(home_dir, ".config")) {
        regexps.prepend(home.resolve_relative_path(".config/*/Cache"));
        regexps.prepend(home.resolve_relative_path(".config/*/Code Cache"));
      }
      regexps.prepend(home.resolve_relative_path(".var/app/*/config/Cache"));
      regexps.prepend(home.resolve_relative_path(".var/app/*/config/Code Cache"));
      regexps.prepend(home.resolve_relative_path("snap/*/*/.config/Cache"));
      regexps.prepend(home.resolve_relative_path("snap/*/*/.config/Code Cache"));
    }

    // Likewise, user doesn't care about cache-like directories in $HOME.
    // In an ideal world, all of these would be under ~/.cache.  But for
    // historical reasons or for those apps that are both popular enough to
    // warrant special attention, we add some useful exclusions here.
    // When changing this list, remember to update the help documentation too.
    if (home_dir != null) {
      var home = File.new_for_path(home_dir);
      files.prepend(home.resolve_relative_path(".ccache"));
      files.prepend(home.resolve_relative_path(".gvfs"));
      files.prepend(home.resolve_relative_path(".Private")); // encrypted copies of stuff in $HOME
      files.prepend(home.resolve_relative_path(".steam/root"));
      files.prepend(home.resolve_relative_path(".var/app/com.valvesoftware.Steam/.steam/root"));
      files.prepend(home.resolve_relative_path(".xsession-errors"));
      regexps.prepend(home.resolve_relative_path(".var/app/*/cache")); // flatpak
      regexps.prepend(home.resolve_relative_path("snap/*/*/.cache"));
    }

    // Skip all of our temporary directories
    foreach (var tempdir in DejaDup.get_tempdirs())
      files.prepend(File.new_for_path(tempdir));

    // Skip transient directories
    files.prepend(File.new_for_path("/dev"));
    files.prepend(File.new_for_path("/proc"));
    files.prepend(File.new_for_path("/run"));
    files.prepend(File.new_for_path("/sys"));
  }
}
