File: PC-Phone USB Sync/restore-symlinks-from-backup.py
#!/usr/bin/env python3
"""
======================================================================================
restore-symlinks-from-backup.py - a PC-Phone USB Sync app utility script.
This script has the same terms of use as that of the app. It's available
online at quixotely.com/PC-Phone USB Sync/restore-symlinks-from-backup.py.
Run immediately after a SYNC, with a command line like this:
python3 restore-symlinks-from-backup.py <TO-path>
Where the required <TO-Path> argument is the relative or absolute pathname
of the content folder which was TO in the preceding SYNC. This can be run
in Terminal on Linux, and Command Prompt, Power Shell, and WSL on Windows.
You must have a Python 3.x installed on the device where you run this.
This script restores all symlinks (only) in TO that were removed or changed
by the preceding SYNC, and were saved by the SYNC in TO's __bkp__ backups
folder. Use this to restore symlinks on Linux or Windows which were deleted
because they did not exist on the removable drive which was FROM in the SYNC.
macOS doesn't require this because it forges symlinks on removable drives,
so they will generally survive roundtrips to other platforms. Android
doesn't require this because it doesn't support symlinks in usable folders.
This script suffices in this specific usage context, but in general, you're
better off avoiding symlinks altogether in content folders propagated to
removable drives and devices that don't support them. See example ahead.
Caution: this works only after a SYNC that removes symlinks. It will restore
symlinks in TO whether they were deleted by omission in FROM, or changed by
differences in FROM. Use this only if you're sure that the SYNC removed
symlinks in TO because they were absent on a FROM removable drive (or other).
More details:
https://quicotely.com/PC-Phone USB Sync/Tech-Notes.html#n1
See also:
https://learning-python.com/android-deltas-sync/_etc/find-all-symlinks.py
======================================================================================
"""
import sys, os, glob, shutil
LISTONLY = False # True = list backed-up symlinks without restoring them
def exit(msg, label=True):
print('Error: ' + msg if label else msg)
sys.exit(1)
if len(sys.argv) != 2:
exit('Usage: python3 <TO-path>', label=False)
TO = sys.argv[1]
if not os.path.isdir(TO):
exit('TO path is not a folder')
TObkp = TO + os.sep + '__bkp__'
if not os.path.isdir(TObkp):
exit('TO path has no __bkp__ folder')
bkppatt = 'date??????-time??????' # ignore '*--UNDONE', '__undoable__'
backups = glob.glob(os.path.join(TObkp, bkppatt)) # mergeall prunes use 'date*-time*'
if not backups:
exit('TO path has no usable backups')
latestbackup = sorted(backups)[-1] # latest by date/time folder name
numlinks = 0
for (dirhere, subshere, fileshere) in os.walk(latestbackup):
for filename in fileshere + subshere: # links in both lists
bkppath = os.path.join(dirhere, filename)
if os.path.islink(bkppath):
numlinks += 1
if LISTONLY:
print('Found:', bkppath)
continue
# map TO/__bkp__/date-time/path to TO/path
bkpprefix = len(latestbackup) + len(os.sep)
TOpath = os.path.join(TO, bkppath[bkpprefix:])
# partly yoinked from Mergeall's cpall.copylink() [tbd: FWP()?]
# windows dir-link arg
if sys.platform.startswith('win') and os.path.isdir(bkppath):
dirarg = dict(target_is_directory=True)
else:
dirarg ={}
# remove current link if present # lexists: link, not its target
if os.path.lexists(TOpath): # else os.symlink() will fail
os.remove(TOpath)
# copy linkpath over
linkPath = os.readlink(bkppath) # the from link's pathname str
os.symlink(linkPath, TOpath, **dirarg) # store pathname as new link
shutil.copystat(bkppath, TOpath, follow_symlinks=False)
# report
print('Restored: [%s] => [%s]' % (bkppath, TOpath))
print('Done: %s backed-up symlinks found %s.' %
(numlinks, ('(only)' if LISTONLY else 'and restored')))
"""
======================================================================================
Example output:
% python3 restore-symlinks-from-backup.py test-restore
Restored: [/Users/me/Desktop/test-restore/__bkp__/date010203-time010203/link-file.txt] => [/Users/me/Desktop/test-restore/link-file.txt]
Restored: [/Users/me/Desktop/test-restore/__bkp__/date010203-time010203/link-folder-file.txt] => [/Users/me/Desktop/test-restore/link-folder-file.txt]
Restored: [/Users/me/Desktop/test-restore/__bkp__/date010203-time010203/link-folder.txt] => [/Users/me/Desktop/test-restore/link-folder.txt]
Restored: [/Users/me/Desktop/test-restore/__bkp__/date010203-time010203/folder/link-parent-file.txt] => [/Users/me/Desktop/test-restore/folder/link-parent-file.txt]
Restored: [/Users/me/Desktop/test-restore/__bkp__/date010203-time010203/folder/link-file.txt] => [/Users/me/Desktop/test-restore/folder/link-file.txt]
Restored: [/Users/me/Desktop/test-restore/__bkp__/date010203-time010203/folder/link-parent.txt] => [/Users/me/Desktop/test-restore/folder/link-parent.txt]
Done: 6 backed-up symlinks found and restored.
======================================================================================
"""