diff --git a/.gitignore b/.gitignore index 3d7a281..6a34cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -221,3 +221,4 @@ build .ccls-cache socks.log .cache +.config diff --git a/tools/sync.py b/tools/sync.py new file mode 100755 index 0000000..c89f648 --- /dev/null +++ b/tools/sync.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +import json +import os +import sys +import subprocess + +help_str = ('A config file `sync.json` has been created in {}. ' + 'Please specify the remote host and destination directory in this ' + 'config file and then run the sync script again.').format(os.path.join(os.getcwd(), '.config')) +config_filename = os.path.join('.config', 'sync.json') +rsync_path = os.getenv('RSYNC_PATH', default = 'rsync') + + +def in_source_tree(): + return os.path.isfile('tools/sync.py') + + +def create_sample_config(): + config_file = open(config_filename, mode='w') + data = { + 'targets': { + 'example': { + 'host': 'Hostname', + 'user': 'User to connect to the remote host as (optional)', + 'path': 'Destination directory on remote host' + } + }, + + 'default_target': 'example' + } + + json.dump(data, config_file, indent=4) + + +def read_config(): + config_file = None + try: + config_file = open('sync.json') + except (OSError, FileNotFoundError): + create_sample_config() + print(help_str) + exit(0) + + return json.load(config_file) + + +def check_config(cfg): + if not 'targets' in cfg: + print('No targets specified in config file') + exit(-1) + + +def make_full_target(target): + path = target['path'].replace('\\', '/') + if path[-1] != '/': + path += '/' + + out = '{}:{}'.format(target['host'], path) + + if 'user' in target: + out = '{}@{}'.format(target['user'], out) + + return out + + +def print_available_targets(targets): + print('Available targets:') + + for name, target in targets.items(): + print(' * {} ({})'.format(name, make_full_target(target))) + + +if not in_source_tree(): + print('This script must be executed from the root of the Socks source tree') + exit(-1) + +config = read_config() +check_config(config); +targets = config['targets'] + +if len(sys.argv) < 2: + print('No mode specified. Available modes:') + print(' * pull: Update the local source tree to match the remote source tree') + print(' * push: Upload the remote source tree to match the local source tree') + exit(0) + +mode = sys.argv[1] +if mode != 'pull' and mode != 'push': + print('Invalid sync mode "{}"'.format(mode)) + exit(-1) + +target_name = '' + +if len(sys.argv) < 3: + if 'default_target' in config: + target_name = config['default_target'] + print('No target specified. Using default target "{}"'.format(target_name)) + else: + print('No target specified (and no default target configured)') + print_available_targets(config['targets']) + exit(0) + +if target_name not in targets: + print('Unknown target "{}"'.format(target_name)) + print_available_targets(config['targets']) + exit(-1) + +target = targets[target_name] + +print('Syncing source tree with {} ({})'.format(make_full_target(target), mode)) +rsync_args = [ + rsync_path, + '-azhP', + '--exclude', + 'build/' +] + +if mode == 'pull': + rsync_args += [ make_full_target(target), '.' ] +else: + rsync_args += [ '.', make_full_target(target) ] + + +rsync = subprocess.Popen(rsync_args) +rsync.communicate() + +if rsync.returncode == 0: + print('** Sync completed successfully') +else: + print('** Sync failed (error {})'.format(rsync.errorcode)) + +exit(rsync.returncode)