Files
Nonemm/publish-update.py
T

78 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Archives the files of a publish folder that differ from the previous archive.
usage: publish-update.py <previous archive> <folder> <output archive>
The previous archive is the full one the last publish left behind (.zip or
.tar.gz). A file goes into the output when it is new or its content changed;
files the new publish no longer has are listed but cannot be removed by
unpacking over the old install. The archive keeps the folder name as its top
directory, like the full one. Prints nothing and writes nothing when no file
changed.
"""
import hashlib
import os
import sys
import tarfile
import zipfile
def digest(data):
return hashlib.sha256(data).hexdigest()
def read_previous(path):
"""Maps each file's path inside the archive to a hash of its content."""
hashes = {}
if path.endswith(".zip"):
with zipfile.ZipFile(path) as archive:
for info in archive.infolist():
if not info.is_dir():
hashes[info.filename] = digest(archive.read(info))
else:
with tarfile.open(path) as archive:
for member in archive.getmembers():
if member.isfile():
hashes[member.name] = digest(archive.extractfile(member).read())
return hashes
def main(previous, folder, output):
root = os.path.basename(os.path.normpath(folder))
old = read_previous(previous)
changed = []
current = set()
for directory, _, names in os.walk(folder):
for name in sorted(names):
full = os.path.join(directory, name)
inside = root + "/" + os.path.relpath(full, folder).replace(os.sep, "/")
current.add(inside)
with open(full, "rb") as file:
if old.get(inside) != digest(file.read()):
changed.append((full, inside))
removed = sorted(set(old) - current)
if os.path.exists(output):
os.remove(output)
if not changed:
print("no file changed since the previous archive")
return
changed.sort(key=lambda pair: pair[1])
if output.endswith(".zip"):
with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as archive:
for full, inside in changed:
archive.write(full, inside)
else:
with tarfile.open(output, "w:gz") as archive:
for full, inside in changed:
archive.add(full, inside, recursive=False)
print(f"{output}: {len(changed)} changed of {len(current)} files")
for gone in removed:
print(f" no longer in the publish: {gone}")
if __name__ == "__main__":
if len(sys.argv) != 4:
sys.exit(__doc__)
main(*sys.argv[1:])