File: android-deltas-sync/sync-changes-part1-pc.py

#!/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)



[Home page] Books Code Blog Python Author Train Find ©M.Lutz