learning-website-framework1-6 Exercise 3: Mirror PDFs and Solutions Safely ========================================================================= Visitors reach PDFs and solution files at the same relative path as in the content folder, so the build must copy every pdfs/ and solutions/ folder into the published output at an identical path. Write a copy step that is idempotent: it copies a file only when it is missing or newer, so a second run does almost nothing. Save as copy_assets.py: import os, shutil, sys def copy_if_newer(src, dest): s = os.stat(src) if os.path.exists(dest): d = os.stat(dest) if d.st_mtime >= s.st_mtime and d.st_size == s.st_size: return "skipped" os.makedirs(os.path.dirname(dest), exist_ok=True) shutil.copy2(src, dest) # copy2 keeps the modification time return "copied" def mirror_assets(content_root, public_root): counts = {"copied": 0, "skipped": 0} for dirpath, dirnames, filenames in os.walk(content_root): if os.path.basename(dirpath) in ("pdfs", "solutions"): for name in filenames: src = os.path.join(dirpath, name) rel = os.path.relpath(src, content_root) # identical relative path counts[copy_if_newer(src, os.path.join(public_root, rel))] += 1 return counts if __name__ == "__main__": content_root, public_root = sys.argv[1], sys.argv[2] print("first run: ", mirror_assets(content_root, public_root)) print("second run:", mirror_assets(content_root, public_root)) # touch one source file, then run again sample = next((os.path.join(d, f) for d, _, fs in os.walk(content_root) for f in fs if d.endswith("solutions")), None) if sample: os.utime(sample, None) print("after touching one file:", mirror_assets(content_root, public_root)) Run it on one course (the last argument is an output folder): python copy_assets.py "/claude-projects/website-content/content/linux/system-administration/debian-development-machine-setup" pub_test Output (checked by running it): first run: {'copied': 31, 'skipped': 0} second run: {'copied': 0, 'skipped': 31} after touching one file: {'copied': 1, 'skipped': 30} (The demo's last step updates the modified time of one source file, to show that a changed file is copied again. That is harmless, but use a copy of the folder if you prefer not to touch the original.) (31 files = 30 solution files plus the course PDF. This course has no per-chapter PDFs.) On PDFs and git --------------- PDFs are build outputs: each one is generated from the chapter HTML by a builder script, so they are ignored by git (*.pdf in .gitignore). The copy step reads them from the working folder, not from git. On a fresh machine, or a build server, the PDFs would be missing until you either regenerate them with the builder scripts or sync them from the folder that holds them. Decide which, and add a build check that every course which links to a PDF has the file. WHY THIS WORKS AS AN ANSWER --------------------------- Copy-if-newer with the same relative path is the simplest way to keep links stable and builds fast. Keeping modification times (copy2) is what makes the second run a no-op. Treating PDFs as artefacts and not as source also keeps the repository small, at the price of needing a documented way to rebuild them.