Build a Dropbox-style file sync engine
Keep two folders, standing in for two devices, perfectly in step. Build a two-way sync engine in the Python standard library that fingerprints every file by its contents, works out the smallest set of changes, copies only what is needed, propagates deletes without resurrecting them, and handles the genuinely hard part: when both sides edited the same file, it keeps both versions instead of silently losing one. You will understand sync indexes, content hashing, three-way compare and why naive mtime sync corrupts data.
- Level 0 skills: The terminal survival kit for
cd, running a command and opening a file in an editor. - Python 3.10 or newer (tested on 3.13). Check with
python --version. The engine uses only the standard library, so there is nothing to install to run it. uv, which the test step uses to fetch pytest without touching your system Python, is installed in Install anything from GitHub, safely. - A build lesson first is ideal, for the uv and pytest workflow this one reuses: Ship your own command-line app or Build your own API.
- A code editor. VS Code ↗ is free; Notepad or TextEdit work too.
Why sync is harder than copy
Copying is one-way and forgetful. Sync has to remember what the two sides last agreed on, or it cannot tell an edit from a delete, or a delete from a file it has never seen. Seeing the finished engine run makes the shape of the problem clear.
Copying one folder over another is easy: overwrite everything, done. Sync is harder because changes happen on both sides, and the engine has to work out *what changed* and *who changed it* before it touches a single file. Three questions trip up every naive attempt:
- A file is in A but not in B. Did someone add it in A (copy it to B), or delete it in B (so remove it from A too)? The two need opposite actions, and the file alone cannot tell you which.
- A file differs between A and B. Which side is newer? Using the clock to decide is where data gets lost, as you will see in step 3.
- Both sides edited the same file since they last agreed. There is no right answer that keeps only one. Pick a winner and someone's work is gone.
The answer to all three is a sync index: a small record of the state both sides last agreed on, kept between runs. With it, the engine does a three-way compare for every file (the index as the common base, the file in A, the file in B) and the right action falls out. Without it, sync is guessing.
Get the kit. Every file is in /learn/build-a-file-sync-engine: syncengine.py (the engine), demo.py (the scenarios), sync.py (a command-line tool), test_syncengine.py and requirements.txt. Put them together in one folder, for example
file-syncin your Documents, and open a terminal there.The whole engine is standard-library Python, so you can run the scenarios straight away.
demo.pybuilds two folders in a throwawaysandbox, puts files in them, and runs four scenarios, printing the real fingerprints and the decisions the engine makes:In the file-sync folderpython demo.pyThe first scenario is the easy one: B is empty, A has three files, so every file is an add to B. Note the twelve-character fingerprints; those are the start of each file's content hash, which the next steps explain:
Output====================================================================== 1. Initial sync: make B match A ====================================================================== A before: notes.txt ef1821c82589 11 bytes photos/cat.txt 93eca2959c10 23 bytes todo.md c08a755ff142 11 bytes B before: (empty) Sync report: ADD B notes.txt ef1821c82589 (11 bytes) ADD B photos/cat.txt 93eca2959c10 (23 bytes) ADD B todo.md c08a755ff142 (11 bytes) (0 file(s) already in sync) B after: notes.txt ef1821c82589 11 bytes photos/cat.txt 93eca2959c10 23 bytes todo.md c08a755ff142 11 bytesThe run keeps going through three more scenarios (an edit, an add-and-delete, and a conflict), which the rest of the lesson takes apart one at a time. For now you have seen the destination: a report of add / update / delete / conflict decisions, each with the fingerprint that justifies it.
TipRunpython demo.py --keepand thesandboxfolder stays behind, with folders A and B and theindex.jsonit built, so you can open them and look. Without--keepthe sandbox is removed at the end, so every run starts clean.
python demo.py prints four numbered scenarios and ends with Sandbox removed. Scenario 1 shows three ADD B lines.If it breaks'python' is not recognised, or it opens the Microsoft Store + 2 more
'python' is not recognised, or it opens the Microsoft Store
py demo.py instead of python demo.py.'ModuleNotFoundError: No module named syncengine'
demo.py imports syncengine.py, so the two must sit in the same folder, and you must run the command from that folder. Check with dir (Windows) or ls (Mac and Linux) that both files are there.'can't open file demo.py'
cd into the folder where you saved the kit files first.