diff --git a/.github/workflows/from_commit_to_build_test.yml b/.github/workflows/from_commit_to_build_test.yml
index d9d530e..082981b 100644
--- a/.github/workflows/from_commit_to_build_test.yml
+++ b/.github/workflows/from_commit_to_build_test.yml
@@ -79,5 +79,5 @@ jobs:
if: ${{ env.ACT == '' }}
with:
name: coverage-reports
- path: /home/runner/rusticlone-tests/coverage/
+ path: /home/runner/.cache/rusticlone-tests/coverage/
retention-days: 30
diff --git a/Makefile b/Makefile
index 9d1bf86..12c93e6 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ test:
uv run bash tests/tests.sh
ci:
- act --workflows ".github/workflows/test.yml"
+ act --workflows ".github/workflows/from_commit_to_build_test.yml"
toc:
find * -type f ! -name 'CHANGELOG.md' -exec toc -f {} \; 2>/dev/null
diff --git a/README.md b/README.md
index e045302..0c2c367 100644
--- a/README.md
+++ b/README.md
@@ -242,20 +242,17 @@ keep-quarter-yearly = 4
keep-yearly = 1
```
+As it doesn't contain a "\[repository]" section, it will not be treated as a standalone profile by Rusticlone.
+
This "common.toml" profile can be referenced from our documents by adding to "Documents.toml" the following:
```toml
[global]
use-profile = ["common"]
-```
-To exclude "common.toml" from Rusticlone (since it cannot be used alone), add the `--ignore` argument followed by "common":
-
-```bash
-rusticlone --ignore "common" -r "gdrive:/PC" backup
+# [...]
```
-All the profiles containing "common" in their name will be excluded, but will still be sourced from other profiles when needed.
### Custom log file
@@ -296,10 +293,10 @@ Description=Rusticlone service
[Service]
Type=oneshot
-ExecStart=rusticlone --ignore "common" --remote "gdrive:/PC" backup
+ExecStart=rusticlone --remote "gdrive:/PC" backup
```
-Adjust your `--ignore` and `--remote` as needed.
+Adjust your `--remote` as needed.
Apply your changes and enable the timer:
diff --git a/example/systemd/rusticlone.service b/example/systemd/rusticlone.service
index e5adfde..5ed7d75 100644
--- a/example/systemd/rusticlone.service
+++ b/example/systemd/rusticlone.service
@@ -3,5 +3,5 @@ Description=Rusticlone Backup - service
[Service]
Type=oneshot
-# replace the remote and ignore pattern
-ExecStart=rusticlone --remote "gdrive:/PC" --ignore "common" backup
+# replace the remote
+ExecStart=rusticlone --remote "gdrive:/PC" backup
diff --git a/images/coverage.svg b/images/coverage.svg
index 70b3ad8..683122a 100644
--- a/images/coverage.svg
+++ b/images/coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/rusticlone/cli.py b/rusticlone/cli.py
index eba63c0..a8c5058 100755
--- a/rusticlone/cli.py
+++ b/rusticlone/cli.py
@@ -58,9 +58,8 @@ def parse_args():
"-i",
"--ignore",
type=str,
- default="🫣🫣🫣",
- env_var="IGNORE",
- help="Ignore rustic profiles containing this pattern",
+ default="",
+ help="Deprecated argument, does nothing. Will be removed in a future release",
)
parser.add_argument(
"-l",
diff --git a/rusticlone/helpers/custom.py b/rusticlone/helpers/custom.py
index 29e8773..af48450 100644
--- a/rusticlone/helpers/custom.py
+++ b/rusticlone/helpers/custom.py
@@ -26,8 +26,12 @@
# exit
import sys
+# toml parsing
+import tomllib
+
# rusticlone
from rusticlone.helpers.action import Action
+from rusticlone.helpers.rustic import Rustic
from rusticlone.helpers.notification import notify_user
from rusticlone.processing.parallel import (
system_backup_parallel,
@@ -45,6 +49,7 @@
system_download_sequential,
system_extract_sequential,
)
+from rusticlone.processing.profile import parse_repo, parse_sources
# ################################################################ CLASSES
@@ -82,17 +87,12 @@ def __init__(self, args) -> None:
Path("/etc/rustic"),
]
else:
- self.profiles_dirs = [
- Path.home() / ".config/rustic",
- Path("/etc/rustic"),
- ]
+ self.profiles_dirs = [Path.home() / ".config/rustic", Path("/etc/rustic")]
# remote prefix: rclone remote + subdirectory without trailing slash
if args.remote is not None:
self.remote_prefix = args.remote.rstrip("/")
else:
self.remote_prefix = "None:/"
- # ignore pattern for profiles
- self.ignore_pattern = args.ignore
# test profile
if args.profile:
self.provided_profile = args.profile
@@ -141,15 +141,29 @@ def check_profiles_dirs(self) -> None:
# ################################################################ FUNCTIONS
-def list_profiles(
- profiles_dirs: list, provided_profile: str = "*", ignore_pattern: str = "🫣🫣🫣"
-) -> list:
+def has_needed_config_components(profile_path: Path) -> bool:
+ """
+ Check if a profile should be processed by running rustic show-config
+ """
+ try:
+ profile_name = profile_path.stem
+ rustic = Rustic(profile_name, "show-config")
+ config = tomllib.loads(rustic.stdout)
+ has_repo = parse_repo(config) is not None
+ has_sources = parse_sources(config) is not None
+ return has_repo and has_sources
+ except (AttributeError, tomllib.TOMLDecodeError, KeyError, IndexError):
+ return False
+
+
+def list_profiles(profiles_dirs: list, provided_profile: str = "*") -> list:
"""
Scan profiles from directories if none have been provided explicitely
Don't scan from /etc/rustic if ~/.config/rustic has some profiles'
"""
action = Action("Reading profiles")
profiles: list[str] = []
+ opened_files = 0
if not provided_profile:
provided_profile = "*"
for profiles_dir in profiles_dirs:
@@ -157,10 +171,11 @@ def list_profiles(
action.stop(f'Scanning "{profiles_dir}"', "")
files = sorted(list(profiles_dir.glob(f"{provided_profile}.toml")))
for file in files:
+ opened_files += 1
if (
file.is_file()
- and ignore_pattern not in file.stem
and file.stem not in profiles
+ and has_needed_config_components(file)
):
profiles.append(file.stem)
# remove duplicates
@@ -169,9 +184,10 @@ def list_profiles(
action.stop(f"Profiles: {str(profiles)}", "")
return profiles
else:
- print(provided_profile)
- print(provided_profile)
- action.abort("Could not find any rustic profile")
+ print("")
+ print(f"Glob pattern: {provided_profile}")
+ print(f"Files tried: {opened_files}")
+ action.abort("Could not find any valid profile")
sys.exit(1)
@@ -243,6 +259,7 @@ def process_profiles(
)
case _:
print(f"Invalid command '{command}'")
+ sys.exit(1)
if apprise_url and results:
notify_user(results, apprise_url)
@@ -254,9 +271,7 @@ def load_customizations(args: Namespace):
custom = Custom(args)
custom.check_log_file()
custom.check_profiles_dirs()
- profiles = list_profiles(
- custom.profiles_dirs, custom.provided_profile, custom.ignore_pattern
- )
+ profiles = list_profiles(custom.profiles_dirs, custom.provided_profile)
process_profiles(
profiles,
custom.parallel,
diff --git a/rusticlone/helpers/rclone.py b/rusticlone/helpers/rclone.py
index 12a2997..5f50fe8 100644
--- a/rusticlone/helpers/rclone.py
+++ b/rusticlone/helpers/rclone.py
@@ -48,6 +48,8 @@ def __init__(self, **kwargs):
"--drive-chunk-size=128M",
"--drive-acknowledge-abuse",
"--drive-stop-on-upload-limit",
+ "--no-update-modtime",
+ "--no-update-dir-modtime",
]
default_kwargs: dict[str, Any] = {
"env": {},
diff --git a/rusticlone/processing/atomic.py b/rusticlone/processing/atomic.py
index 3d154e2..16ad446 100644
--- a/rusticlone/processing/atomic.py
+++ b/rusticlone/processing/atomic.py
@@ -40,12 +40,14 @@ def profile_archive(
profile.set_log_file(log_file)
profile.check_sources_exist()
profile.check_local_repo_exists()
+ profile.create_lockfile("archive")
profile.check_local_repo_health()
profile.init()
profile.backup()
profile.forget()
profile.source_stats()
profile.repo_stats()
+ profile.delete_lockfile()
timer.stop()
# action.stop(" ", "")
return profile.result, timer.duration
@@ -64,7 +66,9 @@ def profile_upload(
profile.set_log_file(log_file)
profile.check_rclone_config_exists()
profile.check_local_repo_exists()
+ profile.create_lockfile("upload")
profile.upload(remote_prefix)
+ profile.delete_lockfile()
timer.stop()
# action.stop(" ", "")
return profile.result, timer.duration
@@ -87,7 +91,9 @@ def profile_download(
profile.check_rclone_config_exists()
profile.check_remote_repo_exists(remote_prefix)
profile.check_local_repo_exists()
+ profile.create_lockfile("download")
profile.download(remote_prefix)
+ profile.delete_lockfile()
timer.stop()
# print_stats("", "")
return profile.result, timer.duration
@@ -105,9 +111,11 @@ def profile_extract(
profile.parse_rustic_config()
profile.set_log_file(log_file)
profile.check_local_repo_exists()
+ profile.create_lockfile("extract")
profile.check_latest_snapshot()
profile.check_sources_type()
profile.restore()
+ profile.delete_lockfile()
timer.stop()
# print_stats("", "")
return profile.result, timer.duration
diff --git a/rusticlone/processing/profile.py b/rusticlone/processing/profile.py
index 5689425..c1b2b79 100644
--- a/rusticlone/processing/profile.py
+++ b/rusticlone/processing/profile.py
@@ -8,6 +8,7 @@
# │
# ├── IMPORTS
# ├── CLASSES
+# ├── FUNCTIONS
# │
# └───────────────────────────────────────────────────────────────
@@ -50,16 +51,18 @@ def __init__(self, profile: str, parallel: bool = False) -> None:
"""
self.profile_name = profile
self.parallel = parallel
- self.repo = ""
+ self.repo = Path("")
+ self.lockfile = Path("rusticlone.lock")
self.log_file = Path("rusticlone.log")
self.env: dict[str, str] = {}
+ self.does_forget = False
self.password_provided = ""
# json objects
self.backup_output: list[dict[Any, Any]] = []
- self.sources: list[str] = []
+ self.sources: list[Path] = []
self.sources_number = 0
- self.sources_exist: dict[str, bool] = {}
- self.sources_type: dict[str, str] = {}
+ self.sources_exist: dict[Path, bool] = {}
+ self.sources_type: dict[Path, str] = {}
self.local_repo_exists = False
self.snapshot_exists = False
self.result = True
@@ -80,76 +83,41 @@ def parse_rustic_config(self) -> None:
except (AttributeError, tomllib.TOMLDecodeError):
self.result = action.abort("Could not parse rustic configuration")
else:
- self.result = self.parse_rustic_config_source(action)
- self.result = self.parse_rustic_config_repo(action)
- self.result = self.parse_rustic_config_log(action)
- self.result = self.parse_rustic_config_env(action)
+ self.result = self.parse_rustic_config_component(action, "sources")
+ self.result = self.parse_rustic_config_component(action, "repo")
+ self.result = self.parse_rustic_config_component(action, "log")
+ self.result = self.parse_rustic_config_component(action, "env")
+ self.result = self.parse_rustic_config_component(action, "forget")
if self.result:
action.stop("Parsed rustic configuration")
- def parse_rustic_config_source(self, action) -> bool:
+ def parse_rustic_config_component(self, action, component: str) -> bool:
"""
- Read sources from Rustic profile configuration
+ Store values and return True if successful
"""
try:
- # self.source = self.config["backup"]["sources"][0]["source"]
- # they can be either string or list of strings:
- # https://github.com/rustic-rs/rustic/blob/a88afdd4af295c16e5de50de91ec430920f81f56/config/full.toml
- config_sources = [
- section["sources"] for section in self.config["backup"]["snapshots"]
- ]
- for config_source in config_sources:
- if config_source and isinstance(config_source, list):
- self.sources.extend(config_source)
- elif config_source:
- self.sources.append(config_source)
- # remove eventual duplicates
- self.sources = list(set(self.sources))
+ match component:
+ case "sources":
+ self.sources = parse_sources(self.config)
+ case "repo":
+ self.repo = parse_repo(self.config)
+ self.lockfile = self.repo / "rusticlone.lock"
+ case "log":
+ self.log_file = parse_log(self.config)
+ case "env":
+ self.env = parse_env(self.config)
+ case "forget":
+ self.does_forget = parse_forget(self.config)
except KeyError:
- return action.abort("Could not parse source in config:\n", self.config)
- return True
-
- def parse_rustic_config_repo(self, action) -> bool:
- """
- Read repo from Rustic profile configuration
- """
- try:
- self.repo = self.config["repository"]["repository"]
- except KeyError:
- return action.abort("Could not parse repo in config:\n", self.config)
- return True
-
- def parse_rustic_config_log(self, action) -> bool:
- """
- Read log file from Rustic profile configuration
- """
- try:
- self.log_file = Path(self.config["global"]["log-file"])
- except KeyError:
- return action.abort(f'Invalid log file: "{str(self.log_file)}"')
- return True
-
- def parse_rustic_config_env(self, action) -> bool:
- """
- Read environment variables for Rustic and Rclone
- """
- try:
- self.env = self.config["global"]["env"]
- except KeyError:
- return True
+ match component:
+ case "env":
+ pass
+ case _:
+ action.abort(
+ f"Could not parse {component} in config:\n", str(self.config)
+ )
return True
- def parse_rustic_config_forget(self) -> bool:
- """
- Check if the Rustic config has any "keep-*" keys inside [forget] section
- Returns True if any keep-* keys are found, False otherwise
- """
- try:
- forget_section = self.config["forget"]
- return any(key.startswith("keep-") for key in forget_section.keys())
- except KeyError:
- return False
-
def check_rclone_config_exists(self) -> None:
"""
Parse Rustic configuration and extract rclone config, and rclone config password command.
@@ -171,6 +139,46 @@ def check_rclone_config_exists(self) -> None:
f"Rclone configuration file does not exist: {rclone_config_file}"
)
+ def create_lockfile(self, operation: str) -> None:
+ """
+ Add a lockfile to the repo containing the operation name.
+ If the lockfile already exists, abort and print its contents.
+ """
+ if self.result:
+ action = Action("Creating lockfile", self.parallel)
+ # Create repo directory if it doesn't exist (for new repos)
+ if not self.local_repo_exists:
+ self.repo.mkdir(parents=True, exist_ok=True)
+
+ if self.lockfile.exists():
+ try:
+ with open(self.lockfile, "r") as f:
+ existing_operation = f.read()
+ except Exception:
+ self.result = action.abort("Lockfile already exists")
+ else:
+ self.result = action.abort(
+ f"Found another {existing_operation} lockfile"
+ )
+ else:
+ try:
+ with open(self.lockfile, "w") as f:
+ f.write(operation)
+ except Exception:
+ self.result = action.abort("Could not create lockfile")
+ else:
+ action.stop("Created lockfile")
+
+ def delete_lockfile(self) -> None:
+ """
+ Delete the lockfile from the repo
+ """
+ if self.result:
+ action = Action("Deleting lockfile", self.parallel)
+ if self.lockfile.exists():
+ self.lockfile.unlink()
+ action.stop("Deleted lockfile")
+
def set_log_file(self, passed_log_file: Path) -> None:
"""
set rclone log file
@@ -201,13 +209,13 @@ def check_sources_exist(self) -> None:
action = Action("Checking if sources exists", self.parallel)
# print(self.source)
for source in self.sources:
- source_path = Path(source)
- if source_path.exists():
+ if source.exists():
self.sources_exist[source] = True
else:
self.sources_exist[source] = False
if all(self.sources_exist.values()):
self.sources_number = len(self.sources)
+ action.stop("All sources exist")
else:
self.result = action.abort("Some sources do not exist")
@@ -227,8 +235,7 @@ def check_local_repo_exists(self) -> None:
"""
if self.result:
action = Action("Checking if local repo exists", self.parallel)
- # self.repo_type = "local"
- repo_config_file = Path(self.repo) / "config"
+ repo_config_file = self.repo / "config"
if repo_config_file.exists() and repo_config_file.is_file():
self.local_repo_exists = True
action.stop("Local repo already exists")
@@ -253,7 +260,7 @@ def check_remote_repo_exists(self, remote_prefix: str) -> None:
if self.result:
action = Action("Checking if remote repo exists", self.parallel)
rclone_log_file = str(self.log_file)
- repo_name = str(Path(self.repo).name)
+ repo_name = str(self.repo.name)
rclone_origin = remote_prefix + "/" + repo_name
rclone = Rclone(
env=self.env,
@@ -419,7 +426,7 @@ def forget(self) -> None:
Mark snapshots for deletion and evenually prune them.
"""
if self.result:
- if self.parse_rustic_config_forget():
+ if self.does_forget:
action = Action("Deprecating old snapshots", self.parallel)
Rustic(
self.profile_name,
@@ -439,9 +446,9 @@ def upload(self, remote_prefix: str) -> None:
if self.result:
action = Action("Uploading repo", self.parallel)
rclone_log_file = str(self.log_file)
- rclone_origin = self.repo.replace("\\", "/").replace("//", "/")
+ rclone_origin = str(self.repo).replace("\\", "/").replace("//", "/")
# rclone_destination = remote_prefix + "/" + self.profile_name
- repo_name = str(Path(self.repo).name)
+ repo_name = str(self.repo.name)
rclone_destination = remote_prefix + "/" + repo_name
# print(rclone_destination)
rclone = Rclone(
@@ -467,14 +474,14 @@ def download(self, remote_prefix: str) -> None:
Uploads the remote repository to a local destination using rclone.
"""
if self.result:
- if not self.repo.startswith("rclone:"):
+ if not str(self.repo).startswith("rclone:"):
action = Action("Downloading repo", self.parallel)
if not self.local_repo_exists:
rclone_log_file = str(self.log_file)
# rclone_origin = remote_prefix + "/" + self.profile_name
- repo_name = str(Path(self.repo).name)
+ repo_name = str(self.repo.name)
rclone_origin = remote_prefix + "/" + repo_name
- rclone_destination = self.repo
+ rclone_destination = str(self.repo)
Rclone(
env=self.env,
log_file=rclone_log_file,
@@ -535,13 +542,13 @@ def check_latest_snapshot(self) -> None:
else:
self.result = action.abort("Repo does not have snapshots")
timestamp_pretty = self.latest_snapshot_timestamp.strftime(
- "%Y-%m-%d %H:%M:%S"
+ "%Y-%m-%d %H:%M"
)
clear_line(parallel=self.parallel)
# self.snapshot_exists = True
print_stats(
"Restoring from:",
- f"[{timestamp_pretty}]",
+ timestamp_pretty,
19,
21,
parallel=self.parallel,
@@ -612,3 +619,52 @@ def restore(self) -> None:
if rustic.returncode != 0:
self.result = action.abort(f"Error extracting '{source}'")
action.stop("Snapshot extracted")
+
+
+# ################################################################ FUNCTIONS
+
+
+def parse_repo(config: dict[str, Any]) -> Path:
+ """
+ Extract repository folder from Rustic profile configuration
+ """
+ return Path(config["repository"]["repository"])
+
+
+def parse_sources(config: dict[str, Any]) -> list[Path]:
+ """
+ Extract list of sources from Rustic profile configuration
+ """
+ sources: list[str] = []
+ raw_sources = [snapshot["sources"] for snapshot in config["backup"]["snapshots"]]
+ # raw_sources can be either lists or strings
+ for source in raw_sources:
+ if source and isinstance(source, list):
+ sources.extend(source)
+ else:
+ sources.append(source)
+ # remove eventual duplicates and convert to Path
+ unique_sources = list(set({Path(source) for source in sources}))
+ return unique_sources
+
+
+def parse_log(config: dict[str, Any]) -> Path:
+ """
+ Extract log file location from Rustic profile configuration
+ """
+ return Path(config["global"]["log-file"])
+
+
+def parse_env(config: dict[str, Any]) -> dict[str, Any]:
+ """
+ Extract environment variables from Rustic profile configuration
+ """
+ return config["global"]["env"]
+
+
+def parse_forget(config: dict[str, Any]) -> bool:
+ """
+ Check if the Rustic config has any "keep-*" keys inside [forget] section
+ Returns True if any keep-* keys are found, False otherwise
+ """
+ return any(key.startswith("keep-") for key in config["forget"].keys())
diff --git a/tests/tests.sh b/tests/tests.sh
index 8d3729e..3c2ce07 100755
--- a/tests/tests.sh
+++ b/tests/tests.sh
@@ -35,11 +35,14 @@ set -euo pipefail
RUSTIC_PROFILES_DIR="$HOME/.config/rustic"
# can be any folder
-RUSTICLONE_TEST_DIR="$HOME/rusticlone-tests"
+RUSTICLONE_TEST_DIR="$HOME/.cache/rusticlone-tests"
# ################################ DERIVED
mapfile -d '' profile1Content << CONTENT
+[global]
+use-profiles = ["common"]
+
[repository]
repository = "$RUSTICLONE_TEST_DIR/local/Documents"
cache-dir = "$RUSTICLONE_TEST_DIR/cache"
@@ -111,7 +114,7 @@ RCLONE_ENCRYPT_V0:
LDDUg4mDyUxDwMtntnCaiUN+o9SexiohA8Y74ZYJmPD9KD8UjVtH9XYCL+3A6OGR7msabjvu0Gj2W8JRande
CONTENT
-GOOD_APPRISE_URL="dbus://"
+GOOD_APPRISE_URL="form://example.org"
BAD_APPRISE_URL="moz://a"
# ################################################################ FUNCTIONS
@@ -173,11 +176,15 @@ print_cleanup(){
echo "[OK] Test completed, feel free to read test coverage and remove \"$RUSTIC_PROFILES_DIR\" and \"$RUSTICLONE_TEST_DIR\""
}
-create_dirs(){
- echo "[OK] Creating directories"
+remove_profiles_dir(){
if [[ -d "$RUSTIC_PROFILES_DIR" ]]; then
rm -r "$RUSTIC_PROFILES_DIR"
fi
+}
+
+create_dirs(){
+ echo "[OK] Creating directories"
+ remove_profiles_dir
if [[ -d "$RUSTICLONE_TEST_DIR" ]]; then
rm -r "$RUSTICLONE_TEST_DIR"
fi
@@ -189,12 +196,13 @@ create_confs(){
profile1Conf="$RUSTIC_PROFILES_DIR/Documents-test.toml"
profile2Conf="$RUSTIC_PROFILES_DIR/Pictures-test.toml"
profile3Conf="$RUSTIC_PROFILES_DIR/Passwords-test.toml"
+ profileCommonConf="$RUSTIC_PROFILES_DIR/common.toml"
rcloneConfDecrypted="$RUSTIC_PROFILES_DIR/rclone-decrypted.conf"
rcloneConfEncrypted="$RUSTIC_PROFILES_DIR/rclone-encrypted.conf"
echo "${profile1Content[0]}" > "$profile1Conf"
echo "${profile2Content[0]}" > "$profile2Conf"
echo "${profile3Content[0]}" > "$profile3Conf"
- echo "${profileCommonContent[0]}" >> "$profile1Conf"
+ echo "${profileCommonContent[0]}" > "$profileCommonConf"
echo "${profileCommonContent[0]}" >> "$profile2Conf"
echo "${profileCommonContent[0]}" >> "$profile3Conf"
echo "${rcloneConfContentDecrypted[0]}" > "$rcloneConfDecrypted"
@@ -218,11 +226,28 @@ create_files(){
chmod 0600 "$RUSTICLONE_TEST_DIR/source/passwords.kdbx"
}
+create_new_files(){
+ # 10MB each
+ echo "[OK] Creating files"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/important2.pdf"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/veryimportant2.pdf"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/notsoimportant2.docx"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/screenshot2.png"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/opengraph2.webp"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/funny2.gif"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/photos/photo2.jpeg"
+ head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/photos/deeply/nested/memory2.avif"
+}
+
create_check_source(){
echo "[OK] Creating checksums for source files"
find "$RUSTICLONE_TEST_DIR/source" -type f -exec b2sum {} \; > "$RUSTICLONE_TEST_DIR/check/source.txt"
}
+wait_background(){
+ while wait -n; do : ; done;
+}
+
# ################################ RUSTICLONE BACKUP
# ################ SEQUENTIAL
@@ -246,7 +271,7 @@ rusticlone_backup_flags(){
logecho "[OK] Backing up from Rusticlone" "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log"
coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Pictures-test" --log-file "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log" backup
logecho "[OK] Backing up from Rusticlone"
- coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Documents-test" --ignore "common" backup
+ coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Documents-test" backup
coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Passwords-test" -a "$BAD_APPRISE_URL" backup
}
@@ -267,6 +292,23 @@ rusticlone_upload_parallel(){
coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel upload
}
+# ################ BACKGROUND
+
+rusticlone_backup_background(){
+ echo "[OK] Backing up with Rusticlone in background"
+ coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel backup >/dev/null &
+}
+
+rusticlone_archive_background(){
+ echo "[OK] Archiving with Rusticlone in background"
+ coverage run --append --module rusticlone.cli archive >/dev/null &
+}
+
+rusticlone_upload_background(){
+ echo "[OK] Uploading with Rusticlone in background"
+ coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" upload >/dev/null &
+}
+
# ################################ DISASTER SIMULATION
# ################ LOSING SOURCE FILES
@@ -344,7 +386,7 @@ rusticlone_restore_flags(){
logecho "[OK] Restoring from Rusticlone" "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log"
coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Documents-test" --log-file "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log" restore
logecho "[OK] Restoring from Rusticlone"
- coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Passwords-test" --ignore "common" restore
+ coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Passwords-test" restore
coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Pictures-test" restore
}
@@ -375,6 +417,23 @@ rusticlone_extract_parallel(){
coverage run --append --module rusticlone.cli --parallel extract
}
+# ################ BACKGROUND
+
+rusticlone_restore_background(){
+ echo "[OK] Restoring with Rusticlone in background"
+ coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel restore >/dev/null &
+}
+
+rusticlone_download_background(){
+ echo "[OK] Downloading with Rusticlone in background"
+ coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" download >/dev/null &
+}
+
+rusticlone_extract_background(){
+ echo "[OK] Extracting with Rusticlone in background"
+ coverage run --append --module rusticlone.cli --parallel extract >/dev/null &
+}
+
# ################################ RESULT
check_source(){
@@ -385,6 +444,7 @@ check_source(){
create_coverage(){
coverage html
coverage xml
+ coverage report
rm -rf "tests/coverage"
mv "htmlcov" "$RUSTICLONE_TEST_DIR/coverage"
mv "coverage.xml" "$RUSTICLONE_TEST_DIR/coverage"
@@ -395,7 +455,8 @@ create_badge(){
}
check_coverage(){
- echo "[OK] Read the coverage report by running:"
+ echo " "
+ echo "[OK] Read the coverage report in detail by running:"
echo " "
echo " firefox \"$RUSTICLONE_TEST_DIR/coverage/index.html\""
echo " "
@@ -425,7 +486,16 @@ main(){
destroy_remote1
destroy_local2
print_space
+ rusticlone_backup_background
+ rusticlone_backup_background
+ rusticlone_backup_parallel
+ rusticlone_upload_background
+ rusticlone_archive_background
+ rusticlone_upload_background
+ rusticlone_archive_background
rusticlone_archive
+ rusticlone_backup_background
+ wait_background
print_space
destroy_cache
print_space
@@ -448,20 +518,32 @@ main(){
destroy_source1
destroy_local2
print_space
+ rusticlone_restore_background
rusticlone_restore_parallel
+ wait_background
print_space
check_source
destroy_local1
destroy_source2
print_space
+ rusticlone_restore_background
+ rusticlone_download_background
+ print_space
rusticlone_download_parallel
+ rusticlone_archive_parallel
+ rusticlone_extract
rusticlone_extract
+ wait_background
print_space
check_source
destroy_cache
print_space
rusticlone_download
+ rusticlone_extract_background
rusticlone_extract_parallel
+ rusticlone_extract_background
+ rusticlone_extract_parallel
+ wait_background
print_space
check_source
print_space
@@ -469,7 +551,10 @@ main(){
destroy_source1
destroy_local2
print_space
+ rusticlone_restore_background
+ print_space
rusticlone_restore
+ wait_background
print_space
destroy_source2
destroy_local1
@@ -479,12 +564,14 @@ main(){
# further run
check_source
+ create_new_files
rusticlone_backup
rusticlone_restore
check_source
print_space
# results
+ remove_profiles_dir
create_coverage
create_badge
check_coverage