Files
sshyp/lib/sshync.py
T

169 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
from os import listdir, remove, walk
from os.path import expanduser, getmtime, join
from subprocess import CalledProcessError, PIPE, run
from sys import exit as s_exit
# REMOTE
def remote_list_gen(_client_device_name, _remote_dir): # prints all necessary remote data to stdout
# deletions
for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path, _sep, _device = _file.partition('\x1f')
_file_path = _file_path.replace('\x1e', '/')
if _device == _client_device_name:
print(_file_path)
try:
remove(f"{expanduser('~/.config/sshyp/deleted/')}{_file}")
except FileNotFoundError:
pass
print('\x1d')
# folders
for _root, _directories, _files in walk(expanduser('~/.local/share/sshyp')):
for _dir in _directories:
print(f"{_root.replace(expanduser('~'), '')}/{_dir}")
print('\x1d')
get_local_data(_remote_dir, 'server') # titles and mod times
# HYBRID
def delete(_file_path, _target_database): # deletes a file or folder and/or marks it for deletion upon syncing
from shutil import rmtree
try:
if _file_path.endswith('/'):
rmtree(f"{expanduser('~/.local/share/sshyp/')}{_file_path}")
else:
remove(f"{expanduser('~/.local/share/sshyp/')}{_file_path}.gpg")
except FileNotFoundError:
print(f"location does not exist {_target_database}")
if _target_database == 'remotely':
for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(expanduser('~/.config/sshyp/deleted/') + _file_path.replace('/', '\x1e') + '\x1f' + _device_name, 'w')
def get_local_data(_directory, _device): # retrieves and returns titles and mod times from the local device
_title_list, _mod_list = [], []
for _root, _directories, _files in walk(_directory):
for _filename in _files:
_title_list.append(join(_root.replace(_directory, '', 1), _filename))
_mod_list.append(int(getmtime(join(_root, _filename))))
if _device == 'server':
for _title in _title_list:
print(_title.rstrip())
for _time in _mod_list:
print(_time)
return _title_list, _mod_list
# LOCAL
def remote_list_fetch(_user_data): # captures and returns all necessary data from the remote server
try:
_remote_data = run(['ssh', '-i', _user_data[5], '-p', _user_data[2], f"{_user_data[0]}@{_user_data[1]}",
f'cd /lib/sshyp; python3 -c \'from sshync import remote_list_gen; remote_list_gen'
f'("{_user_data[6]}", "{_user_data[4]}")\''], stdout=PIPE, text=True, check=True
).stdout.split('\x1d')
except CalledProcessError:
print('\n\u001b[38;5;9merror: failed to connect to the remote server\u001b[0m\n')
_remote_data = ''
s_exit(6)
_deletion_database = _remote_data[0].strip().splitlines()
_folder_database = _remote_data[1].strip().splitlines()
_titles_mods = _remote_data[2].strip().splitlines()
return _deletion_database, _folder_database, _titles_mods[:len(_titles_mods)//2], \
_titles_mods[len(_titles_mods)//2:]
def deletion_sync(_deletion_database, _silent): # checks for and acts upon files and folders marked for deletion
for _file in _deletion_database:
if _file != '':
if not _silent:
print(f"\u001b[38;5;208m{_file}\u001b[0m has been sheared, removing...")
delete(_file, 'locally')
def folder_sync(_folder_database): # creates matches of remote folders on the local client
from pathlib import Path
for _folder in _folder_database:
if _folder != '' and not Path(f"{expanduser('~')}{_folder}").is_dir():
print(f"\u001b[38;5;2m{_folder.replace('/.local/share/sshyp/', '')}/\u001b[0m does not exist locally, "
f"creating...")
Path(f"{expanduser('~')}{_folder}").mkdir(mode=0o700, parents=True, exist_ok=True)
def sort_titles_mods(_list_1, _list_2): # creates and returns two lists (of titles and mod times) generated by sorting
# information from two provided 2D lists
_title_list_2_sorted, _mod_list_2_sorted = [], []
# title sorting
for _title in _list_1[0]:
if _title in _list_2[0]:
_title_list_2_sorted.append(_title)
for _title in _list_2[0]:
if _title not in _title_list_2_sorted:
_title_list_2_sorted.append(_title)
# mod time sorting
for _title in _title_list_2_sorted:
_mod_list_2_sorted.append(_list_2[1][_list_2[0].index(_title)])
return _title_list_2_sorted, _mod_list_2_sorted
def make_profile(_profile_dir, _local_dir, _remote_dir, _identity, _ip, _port, _user): # creates a sshync job profile
open(_profile_dir, 'w').write(f"{_user}\n{_ip}\n{_port}\n{_local_dir}\n{_remote_dir}\n{_identity}\n")
def get_profile(_profile_dir): # returns a list of data read from a sshync job profile
try:
_profile_data = open(_profile_dir).readlines()
except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the profile does not exist or is corrupted\u001b[0m\n')
_profile_data = None
s_exit(3)
_user = _profile_data[0].rstrip()
_ip = _profile_data[1].rstrip()
_port = _profile_data[2].rstrip()
_local_dir = _profile_data[3].rstrip()
_remote_dir = _profile_data[4].rstrip()
_identity = _profile_data[5].rstrip()
_client_device_id = listdir(expanduser('~/.config/sshyp/devices'))[0].rstrip()
return _user, _ip, _port, _local_dir, _remote_dir, _identity, _client_device_id
def run_profile(_profile_dir, _silent): # runs a sshync job profile
_user_data = get_profile(_profile_dir) # import profile data
# fetch remote lists
_deletion_database, _folder_database, _remote_titles, _remote_mods = remote_list_fetch(_user_data)
_remote_titles_mods = (_remote_titles, _remote_mods)
# sync deletions and folders
deletion_sync(_deletion_database, _silent)
folder_sync(_folder_database)
# sort titles and mods
_index_local = sort_titles_mods(_remote_titles_mods, get_local_data(_user_data[3], 'client'))
_index_remote = sort_titles_mods(_index_local, _remote_titles_mods)
# sync new and updated files
_i = -1
for _title in _index_local[0]:
_i += 1
if _title in _index_remote[0]:
# compare mod times and sync
if int(_index_local[1][_i]) > int(_index_remote[1][_i]):
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is newer locally, uploading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5], _user_data[3] + _title,
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
elif int(_index_local[1][_i]) < int(_index_remote[1][_i]):
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is newer remotely, downloading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
else:
print(f"\u001b[38;5;4m{_title[:-4]}\u001b[0m is not on remote server, uploading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5], _user_data[3] + _title,
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{'/'.join(_title.split('/')[:-1]) + '/'}"])
for _title in _index_remote[0]:
if _title not in _index_local[0]:
print(f"\u001b[38;5;2m{_title[:-4]}\u001b[0m is not in local directory, downloading...")
run(['scp', '-pqs', '-P', _user_data[2], '-i', _user_data[5],
f"{_user_data[0]}@{_user_data[1]}:{_user_data[4]}{_title}",
f"{_user_data[3]}{'/'.join(_title.split('/')[:-1]) + '/'}"])