78 lines
2.6 KiB
Python
Executable File
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:])
|