#!/usr/bin/env python3 """ ======================================================================= Sync Changes to Phone, Part 1: PC Run syncs whenever desired to propagate PC changes to the phone. The phone will be updated only for modifications made on the PC. See _README.html for license, attribution, version, and docs. ----------------------------------------------------------------------- Usage: Run this on your PC first (before running Part 2 on your phone), with: python3 sync-changes-part1-pc.py Run this in Terminal on macOS and Linux; and in Command Prompt, PowerShell, Cygwin, or WSL on Windows. Replace its "python3" with "python" where needed, and "py -3" in basic Windows shells. This file may also be run with no command line by click or IDE. Run this from any folder. It uses paths set in config_pc.py, and finds common.py in its own folder automatically. Step runtimes are shown in the console after step completion. Step outputs go to files in LOGS; watch with 'tail -f LOGS/file'. ----------------------------------------------------------------------- Notes: 0) "PC" here means a Windows, macOS, or Linux device, but "phone" can be anything: though designed to sync changes to Android 11 and later, these scripts can also sync to earlier Androids and PCs. 1) Heritage: this is a Python translation of a same-named Bash script in folder _etc/bash-version/; hence the too-many globals. Translated for Windows portability: Cygwin and WSL have seams. The Bash originals more explicitly show command lines run here. 2) Design: this creates deltas on the PC temporarily for both speed and data-loss prevention. Creating on the proxy seems nearly as good: using the PC adds a step to copy the deltas zip to the proxy; the deltas items must be copied to the proxy drive eventually in its content copy; and external SSDs are fast. Still, USB data transfers are generally much slower than PC buses, and metadata is more likely to make it to the phone in a zip made on PC. Don't use a system temp folder for this: it's hard to find if^H^H when things fail. 3) Coding: to shorten paths in zipfiles and hence on unzips, this can either os.chdir to content folders, or use ziptools' '-zip@.' arg to strip the path leading to the zipped item. The former shortens ziptools output, but its state is tricky. 4) Coding: apply the deltas to the proxy last, after the zip is created and moved to the proxy. This allows reruns to work if the script is aborted early. Else, if deltas are applied earlier, reruns will find no diffs to be propagated to phone. ======================================================================= """ import os, sys, shutil from os.path import join import config_pc, common # get PC settings and common code common.startup(config_pc, common) # copy and use PC config's settings from common import * # use both's top-level names as globals # subjects TOroot = driveRootPath(TO) thesrc = join(FROM, STUFF) # where content is on PC thedst = join(TO, STUFF) # where content is on proxy thedlt = join(FROM, 'DELTAS') # what deltas.py makes on PC thezip = join(FROM, 'DELTAS.zip') # what the zip makes on PC prxzip = join(TOroot, 'DELTAS.zip') # where zip is copied on proxy # 'from' and 'to' must both exist here if not os.path.isdir(thesrc): print('The FROM/STUFF folder does not exist: "%s"' % thesrc) print('Exiting; please check the config file and rerun.') shutdown(bad=True) if not os.path.isdir(thedst): print('The TO/STUFF folder does not exist: "%s"' % thedst) print('Exiting; please check the config file and rerun.') shutdown(bad=True) opener('Syncing:\nfrom %s\nto %s\nzip %s\nlogs %s' % (thesrc, thedst, prxzip, LOGS)) # deltas temp on PC? (deltas.py removes it too, but custom here [1.2]) if os.path.exists(thedlt): print('\nRemoving existing deltas folder from PC') timefunc(lambda: rmtree_FWP(thedlt, required=True)) #---------------------------------------------------------------- # Optional: offer to run nonportable-filenames fixer tool #---------------------------------------------------------------- offerFixnamesRun('sync-0', target=thesrc) #---------------------------------------------------------------- # Optional: show PC~proxy deltas before saving or propagating #---------------------------------------------------------------- previewChanges('sync-0') #---------------------------------------------------------------- # 1) Make PC~proxy deltas with [deltas.py], storing in PC folder #---------------------------------------------------------------- announce('Creating PC~proxy deltas') logto = join(LOGS, '%s--sync-1-pc-proxy-deltas-log.txt' % DATE) runpy(join(MALL, 'deltas.py'), thedlt, thesrc, thedst, '-skipcruft', '-quiet', logpath=logto, tailing=(9, 'Deltas summary')) #---------------------------------------------------------------- # 2) Zip deltas on PC with [zip-create.py -nocompress] #---------------------------------------------------------------- announce('Zipping deltas') logto = join(LOGS, '%s--sync-2-zip-deltas-pc-log.txt' % DATE) runpy(join(ZIP, 'zip-create.py'), thezip, thedlt, '-zip@.', '-skipcruft', '-nocompress', logpath=logto, tailing=(1, 'Zip create summary')) #---------------------------------------------------------------- # 3) Move deltas zip from PC to proxy #---------------------------------------------------------------- # copy and delete the deltas zip # zipping to proxy directly is likely slower than the PC print('Moving deltas zip to proxy') timefunc(lambda: moveZipToProxy(thezip, prxzip)) #---------------------------------------------------------------- # 4) Apply deltas on PC to proxy with [mergeall.py -restore] #---------------------------------------------------------------- # do this last, so reruns work if aborted earlier announce('Applying deltas to proxy') logto = join(LOGS, '%s--sync-3-apply-deltas-proxy-log.txt' % DATE) # backup proxy changes? (fast but consumes space) backup = ('-backup',) if BackupChanges else () runpy(join(MALL, 'mergeall.py'), thedlt, thedst, '-restore', '-auto', '-skipcruft', *backup, '-quiet', logpath=logto, tailing=(9, 'Mergeall summary')) print('Removing deltas folder from PC (keeping zip on proxy)') timefunc(lambda: rmtree_FWP(thedlt, required=False)) #---------------------------------------------------------------- # Optional: verify content copied from PC to phone #---------------------------------------------------------------- verifyPCtoProxy('sync-4', 'sync-5') # mergeall/diffall log names closer('See logs in "%s", and run part 2 on your phone.' % LOGS)