Files
Zen-Sync/cli.py
2025-05-24 22:48:25 +07:00

260 lines
11 KiB
Python

import argparse
import sys
import json
import logging
from config import ZenSyncConfig
from sync import ZenS3Sync
logger = logging.getLogger(__name__)
def create_parser():
parser = argparse.ArgumentParser(
description="Zen Browser Profile S3 Sync Tool",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
zensync upload --bucket my-backup-bucket
zensync download --bucket my-backup-bucket
zensync sync --bucket my-backup-bucket
zensync configure --bucket my-bucket --endpoint-url http://localhost:9000
zensync list-profiles
"""
)
parser.add_argument('--config', default='zen_sync_config.json', help='Configuration file path')
parser.add_argument('--roaming-path', help='Override Zen roaming data path')
parser.add_argument('--local-path', help='Override Zen local data path')
parser.add_argument('--verbose', '-v', action='store_true', help='Enable verbose logging')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Upload command
upload_parser = subparsers.add_parser('upload', help='Upload profiles to S3')
upload_parser.add_argument('--bucket', help='S3 bucket name')
upload_parser.add_argument('--prefix', default='zen-profiles/', help='S3 key prefix')
upload_parser.add_argument('--dry-run', action='store_true', help='Show what would be uploaded')
upload_parser.add_argument('--no-cache', action='store_true', help='Disable cache data upload')
upload_parser.add_argument('--force-full', action='store_true', help='Force full upload')
upload_parser.add_argument('--cleanup', action='store_true', help='Remove S3 files that no longer exist locally')
# Download command
download_parser = subparsers.add_parser('download', help='Download profiles from S3')
download_parser.add_argument('--bucket', help='S3 bucket name')
download_parser.add_argument('--prefix', default='zen-profiles/', help='S3 key prefix')
download_parser.add_argument('--dry-run', action='store_true', help='Show what would be downloaded')
download_parser.add_argument('--no-cache', action='store_true', help='Disable cache data download')
download_parser.add_argument('--force-full', action='store_true', help='Force full download')
download_parser.add_argument('--cleanup', action='store_true', help='Remove local files that no longer exist in S3')
# Sync command
sync_parser = subparsers.add_parser('sync', help='Bidirectional sync between local and S3')
sync_parser.add_argument('--bucket', help='S3 bucket name')
sync_parser.add_argument('--prefix', default='zen-profiles/', help='S3 key prefix')
sync_parser.add_argument('--dry-run', action='store_true', help='Show what would be synced')
sync_parser.add_argument('--no-cache', action='store_true', help='Disable cache data sync')
sync_parser.add_argument('--cleanup', action='store_true', help='Remove orphaned files')
# List profiles command
subparsers.add_parser('list-profiles', help='List available local profiles')
# Profile info command
subparsers.add_parser('profile-info', help='Show profile system information')
# Configure command
config_parser = subparsers.add_parser('configure', help='Configure sync settings')
config_parser.add_argument('--bucket', help='Set S3 bucket name')
config_parser.add_argument('--region', help='Set AWS region')
config_parser.add_argument('--endpoint-url', help='Set S3-compatible service endpoint')
config_parser.add_argument('--access-key', help='Set AWS access key ID')
config_parser.add_argument('--secret-key', help='Set AWS secret access key')
config_parser.add_argument('--profile', help='Set AWS profile name')
config_parser.add_argument('--roaming-path', help='Set Zen roaming data path')
config_parser.add_argument('--local-path', help='Set Zen local data path')
config_parser.add_argument('--auto-detect', action='store_true', help='Auto-detect Zen browser paths')
config_parser.add_argument('--enable-cache-sync', action='store_true', help='Enable cache data sync')
config_parser.add_argument('--disable-cache-sync', action='store_true', help='Disable cache data sync')
config_parser.add_argument('--disable-metadata', action='store_true', help='Disable S3 metadata')
config_parser.add_argument('--enable-metadata', action='store_true', help='Enable S3 metadata')
config_parser.add_argument('--signature-version', choices=['s3', 's3v4'], help='Set AWS signature version')
return parser
def handle_configure(args, config):
"""Handle configure command"""
if args.bucket:
config.config['aws']['bucket'] = args.bucket
if args.region:
config.config['aws']['region'] = args.region
if getattr(args, 'endpoint_url', None):
config.config['aws']['endpoint_url'] = args.endpoint_url
logger.info(f"Using custom S3 endpoint: {args.endpoint_url}")
if args.access_key:
config.config['aws']['access_key_id'] = args.access_key
logger.warning("Storing AWS access key in config file")
if args.secret_key:
config.config['aws']['secret_access_key'] = args.secret_key
logger.warning("Storing AWS secret key in config file")
if args.profile:
config.config['aws']['profile'] = args.profile
config.config['aws']['access_key_id'] = ""
config.config['aws']['secret_access_key'] = ""
logger.info(f"Configured to use AWS profile: {args.profile}")
if args.roaming_path:
config.config['sync']['zen_roaming_path'] = args.roaming_path
if args.local_path:
config.config['sync']['zen_local_path'] = args.local_path
if args.auto_detect:
auto_paths = config.auto_detect_zen_paths()
if auto_paths['roaming']:
config.config['sync']['zen_roaming_path'] = auto_paths['roaming']
print(f"Auto-detected roaming path: {auto_paths['roaming']}")
if auto_paths['local']:
config.config['sync']['zen_local_path'] = auto_paths['local']
print(f"Auto-detected local path: {auto_paths['local']}")
if args.enable_cache_sync:
config.config['sync']['sync_cache_data'] = True
if args.disable_cache_sync:
config.config['sync']['sync_cache_data'] = False
if getattr(args, 'disable_metadata', False):
config.config['aws']['disable_metadata'] = True
logger.info("S3 metadata disabled")
if getattr(args, 'enable_metadata', False):
config.config['aws']['disable_metadata'] = False
logger.info("S3 metadata enabled")
if getattr(args, 'signature_version', None):
config.config['aws']['signature_version'] = args.signature_version
logger.info(f"AWS signature version set to: {args.signature_version}")
config.save_config()
display_config = json.loads(json.dumps(config.config))
if display_config['aws'].get('secret_access_key'):
display_config['aws']['secret_access_key'] = "***HIDDEN***"
print("\nConfiguration updated:")
print(json.dumps(display_config, indent=2))
def handle_list_profiles(sync):
"""Handle list-profiles command"""
profiles = sync.list_profiles()
if profiles:
print(f"\nAvailable Zen Browser Profiles:")
print("=" * 70)
for profile_id, info in profiles.items():
status = " (Default)" if info['is_default'] else ""
print(f"{info['name']}{status}")
print(f" Profile ID: {profile_id}")
print(f" Path: {info['path']}")
print(f" Store ID: {info.get('store_id', 'N/A')}")
print(f" Full Path: {info['full_path']}")
print()
else:
print("No profiles found")
def handle_profile_info(sync):
"""Handle profile-info command"""
info = sync.get_profile_info()
print(f"\nZen Browser Profile System Information:")
print("=" * 70)
print(f"System Type: {info['system_type']}")
print("\nPaths:")
for path_name, path_value in info['paths'].items():
print(f" {path_name}: {path_value}")
print(f"\nProfiles Found: {len(info['profiles'])}")
if info['profiles']:
for profile_id, profile_info in info['profiles'].items():
status = " (Default)" if profile_info['is_default'] else ""
print(f"{profile_info['name']}{status}")
if 'profile_groups' in info:
print(f"\nProfile Groups:")
if info['profile_groups'].get('exists'):
print(f" Path: {info['profile_groups']['path']}")
print(f" Databases: {', '.join(info['profile_groups'].get('databases', []))}")
else:
print(" Not found")
def run_cli():
"""Main CLI entry point"""
parser = create_parser()
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
config = ZenSyncConfig(args.config)
if args.roaming_path:
config.config['sync']['zen_roaming_path'] = args.roaming_path
if args.local_path:
config.config['sync']['zen_local_path'] = args.local_path
if args.command == 'configure':
handle_configure(args, config)
return
if args.command in ['upload', 'download', 'sync']:
if args.bucket:
config.config['aws']['bucket'] = args.bucket
if args.prefix:
config.config['aws']['prefix'] = args.prefix
if hasattr(args, 'no_cache') and args.no_cache:
config.config['sync']['sync_cache_data'] = False
logger.info("Cache sync disabled for this operation")
if not args.command:
parser.print_help()
return
try:
require_s3 = args.command not in ['list-profiles', 'profile-info']
if args.command in ['upload', 'download', 'sync'] and hasattr(args, 'dry_run') and args.dry_run:
require_s3 = True
logger.info("Dry run mode: Will analyze existing S3 objects")
sync = ZenS3Sync(config, require_s3=require_s3)
if args.command == 'upload':
incremental = not getattr(args, 'force_full', False)
cleanup = getattr(args, 'cleanup', False)
success = sync.upload_to_s3(
dry_run=args.dry_run,
incremental=incremental,
cleanup=cleanup
)
sys.exit(0 if success else 1)
elif args.command == 'download':
incremental = not getattr(args, 'force_full', False)
cleanup = getattr(args, 'cleanup', False)
success = sync.download_from_s3(
dry_run=args.dry_run,
incremental=incremental,
cleanup=cleanup
)
sys.exit(0 if success else 1)
elif args.command == 'sync':
cleanup = getattr(args, 'cleanup', False)
success = sync.sync_bidirectional(
dry_run=args.dry_run,
cleanup=cleanup
)
sys.exit(0 if success else 1)
elif args.command == 'list-profiles':
handle_list_profiles(sync)
elif args.command == 'profile-info':
handle_profile_info(sync)
except Exception as e:
logger.error(f"Error: {e}")
if args.verbose:
import traceback
traceback.print_exc()
sys.exit(1)