#!/usr/bin/env python3

"""
rsnappush: Perform file-based incremental rsync push backup with hard-links

Author: Frank Tobin
Email: ftobin@neverending.org
License: Eclipse Public License 2.0 (https://opensource.org/licenses/EPL-2.0)
"""

import os
import re
import sys
import time
import argparse
import subprocess

parser = argparse.ArgumentParser(description="Perform file-based incremental rsync push backup with hard-links")

parser.add_argument("source", metavar="SOURCE_PATH", help="directory to backup")
parser.add_argument("dest", metavar="[ACCOUNT:]DEST_PATH", help="Where to store the backups.  ACCOUNT can be anything ssh will accept.  Example: user@host:backups/")

parser.add_argument("-r", "--rsync-opt", action="append", default=[],
                    help="pass-thru options to rsync.  Use '=' syntax, and include prefixing dashes. Example: --rsync-opt=--partial-dir=/home/user/rsync-partial")
parser.add_argument("--quiet", "-q", action="store_true", default=False,
                    help="emit less output")
parser.add_argument("--backup-prefix", default="backup-",
                    help="Prefix used in directory snapshots; default: 'backup-'")
parser.add_argument("--compare-n-backups", type=int, default=20,
                    help="How many previous backups to consider hard-linking against; default: 20 (rsync max)")

args = parser.parse_args()

match = re.search("(.+)?:(.*)", args.dest)

if match:
    dest_account = match.group(1)
    dest_path = match.group(2)
else:
    dest_account = None
    dest_path = args.dest

mkdir_cmd = ["mkdir", "-p", dest_path]
if dest_account:
    mkdir_cmd = ['ssh', dest_account] + mkdir_cmd
subprocess.run(mkdir_cmd, check=True)

# check what backups already exist
check_backups_cmd = ["ls", f"{dest_path}/"]
if dest_account:
    check_backups_cmd = ['ssh', dest_account] + check_backups_cmd
proc = subprocess.run(check_backups_cmd,
                      stdout=subprocess.PIPE, encoding="utf-8", check=True)

link_dests = proc.stdout.splitlines()
link_dests = list(filter(lambda x: re.search(args.backup_prefix, x), link_dests))
link_dests = link_dests[-args.compare_n_backups:]
link_dest_args = list(map(lambda x: f"--link-dest=../{x}", link_dests))

my_dest = time.strftime(f'{args.backup_prefix}%Y%m%d-%H:%M')

cmd = ["rsync", "--human-readable", "-a", "--hard-links",
       "--delete", "--compress-level", "9",
       "-e", "ssh"
]

if not args.quiet:
    cmd.extend(["-v", "--progress"])

cmd.extend(args.rsync_opt)
cmd.extend(link_dest_args)

cmd.extend([os.path.normpath(f"{args.source}") + "/", # just look inside the directory
            os.path.join(args.dest, my_dest)])

if not args.quiet:
    print(*cmd)
os.execvp(cmd[0], cmd)
