feat: initial Dropbox tag sync tool
CI / Tests (Linux, Python 3.12) (push) Failing after 4m51s
CI / Tests (Linux, Python 3.11) (push) Failing after 5m19s
CI / Tests (Linux, Python 3.9) (push) Failing after 5m3s
CI / Tests (macOS) (push) Has been cancelled
CI / Tests (Windows) (push) Has been cancelled

Cross-platform CLI that mirrors Dropbox web tags to OS-native tags:
- macOS: Finder xattrs (content-preserving, no re-upload)
- Windows: XMP:Subject via ExifTool (re-upload expected once per file)

Includes an interactive wizard that auto-detects the OS and Dropbox
folder, walks the user through read-only token creation, previews
tagged files, applies tags, and installs a background scheduler
(launchd on macOS, Task Scheduler on Windows).

Ships with:
- 47-test suite (pytest) covering config, state DB, run_sync, writers
- Gitea Actions CI for Linux / macOS / Windows with coverage reporting
This commit is contained in:
2026-04-20 14:16:50 -04:00
commit e363020080
21 changed files with 2626 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
# Dropbox Tag Sync
Bring Dropbox web tags down to your computer so you can search for tagged
files in Finder (macOS) or File Explorer (Windows) as if they were
first-class local tags.
## The problem this solves
Dropbox lets you add **tags** to files from its web interface. Those tags
live in Dropbox's cloud metadata — they are *not* stored inside the files
themselves. The Dropbox desktop app syncs file **contents**, but it does
not copy tags onto your local files. As a result, tags you rely on for
search and organization show up on dropbox.com but are invisible to
Finder, Spotlight, File Explorer, and Windows Search.
This tool closes that gap. It connects to Dropbox, reads each file's
tags, and writes them to your local files in a form your operating
system treats as a first-class tag.
## Quick start
```
pip install -r requirements.txt
python tagsync.py wizard
```
That's it. The wizard detects your OS, finds your Dropbox folder, walks
you through creating a read-only Dropbox access token, previews what
will be tagged, applies the tags, and offers to install a background
scheduler so new tags keep flowing down automatically.
Everything after that is optional — if you want to re-run a sync,
check status, or manage the schedule, use the other subcommands below.
## Requirements
- **Python 3.9+**
- The packages in `requirements.txt` (`pip install -r requirements.txt`)
- **Windows only:** [ExifTool](https://exiftool.org/) on your `PATH`
## What's in this repo
| File / folder | Purpose |
| ------------------------ | ------------------------------------------ |
| `tagsync.py` | Single CLI entry point |
| `tagsync/` | Package with all the logic |
| `requirements.txt` | Python dependencies |
| `README.md` | This document |
Inside `tagsync/`:
| File | Purpose |
| ------------------------- | --------------------------------------------- |
| `wizard.py` | Interactive setup flow |
| `core.py` | Dropbox listing, tag fetching, sync loop |
| `config.py` | Settings file + secure token storage |
| `detect.py` | OS / Dropbox folder / dependency detection |
| `platform_macos.py` | Finder-xattr writer + launchd scheduler |
| `platform_windows.py` | ExifTool-based XMP writer + Task Scheduler |
## Commands
All commands are run as `python tagsync.py <command>`.
| Command | What it does |
| ------------------------------ | ---------------------------------------------------- |
| `wizard` | Interactive setup (run this first) |
| `run` | Perform a one-shot sync using saved settings |
| `run --dry-run` | Show what would change without touching any files |
| `status` | Show config, token presence, and schedule state |
| `schedule install` | Install the background scheduled task |
| `schedule install --interval 30` | Install, running every 30 minutes |
| `schedule uninstall` | Remove the background scheduled task |
| `schedule status` | Show whether the scheduler is installed and loaded |
| `reset --all` | Clear stored token, settings, and local state DB |
| `reset --token` | Clear only the token |
## How it works
1. You create a Dropbox access token with **read-only** metadata scope.
The wizard walks you through this. The token lives in your OS
keyring (macOS Keychain or Windows Credential Manager).
2. The tool lists every file in your Dropbox and asks Dropbox which
tags each file has.
3. For files whose tags have changed since the last run, it writes
those tags to the local copy.
4. A small SQLite cache remembers what was written so subsequent runs
only touch files whose tags actually changed.
Nothing is ever deleted from Dropbox. The tool only **reads** from
Dropbox and **writes** to local files on your machine.
## The important platform difference
macOS and Windows store "file tags" in fundamentally different ways,
and that shapes what happens under the hood.
### macOS — clean and invisible
macOS has a universal, built-in tag system. Tags are stored in an
**extended attribute** (`xattr`) alongside the file, not inside it. The
tool writes the xattr directly. Consequences:
- Every file type can be tagged (text, zip, source code, anything).
- The file's contents are untouched, so Dropbox does **not** see the
file as modified and does **not** re-upload it.
- The file's modification time does not change.
### Windows — embeds tags into the file
Windows has no universal tag store. The "Tags" field in Explorer is
actually the **XMP:Subject** metadata field embedded inside the file.
This is a limitation of the operating system, not the tool.
Consequences:
- Only file formats that support XMP (images, PDFs, Office docs, most
audio and video) can be tagged. The tool skips unsupported formats
by default and can optionally write a `.tags.json` sidecar next to
them instead.
- Because the tag lives inside the file, writing it **does** modify
the file. Dropbox will notice and re-upload the file once. After
that first sync, tags are stored in both the cloud and the local
copy, and future runs only touch files whose Dropbox tags change
again.
- If Dropbox Smart Sync leaves some files online-only, tagging one
would trigger a download. The tool can skip online-only files (on
by default in the wizard).
> **Plan for the first Windows run.** If there are many tagged files,
> the first run will queue a large number of re-uploads as each file
> gets its tags embedded. Schedule it overnight, on a good connection,
> and expect a one-time spike in Dropbox activity. Subsequent runs are
> quiet.
## One-time Dropbox app setup
The wizard will open this page and walk you through it, but for
reference:
1. Go to <https://www.dropbox.com/developers/apps> and click **Create app**.
2. Choose:
- **Scoped access**
- **Full Dropbox**
- A name of your choosing (e.g. `tag-sync`)
3. Open the **Permissions** tab and enable:
- `files.metadata.read`
4. Click **Submit**.
5. On the **Settings** tab, scroll to **OAuth 2 → Generated access token**
and click **Generate**. Paste that token into the wizard.
The generated token only has permission to read file metadata. It
cannot modify anything in Dropbox even if it leaked.
## Where things are stored
| Item | Location |
| ------------------ | ---------------------------------------------------- |
| Token (preferred) | macOS Keychain / Windows Credential Manager |
| Token (fallback) | `~/.dropbox_tag_sync/token` (mode 0600) |
| Settings | `~/.dropbox_tag_sync/config.json` |
| Local state cache | `~/.dropbox_tag_sync/state.db` |
| Log (macOS) | `~/Library/Logs/dropbox-tag-sync.log` |
| launchd plist | `~/Library/LaunchAgents/com.dropboxtagsync.agent.plist` |
| Windows task | Task Scheduler, name: `DropboxTagSync` |
## Troubleshooting
**"not found locally"** — the file exists in Dropbox but hasn't synced
down yet. Let Dropbox finish syncing, or (on Windows) mark the file
as "always keep on this device" if it's online-only.
**Windows tags don't show up in Explorer after a run** — Explorer's
Tags column may be hidden; right-click a column header and add
**Tags**. Windows Search may need a few minutes to re-index.
**"exiftool not found"** — install it from <https://exiftool.org/>
and confirm `exiftool -ver` works in a terminal.
**The state cache got into a weird state** — run
`python tagsync.py reset --state`. The next run will re-examine every
file; no data is lost.
**The token is wrong or revoked** — run
`python tagsync.py reset --token`, then `python tagsync.py wizard`
to enter a new one.
## Safety summary
- **Read-only** with Dropbox (token scope: `files.metadata.read`).
- **Non-destructive on macOS**: only extended attributes are written.
- **Content-modifying on Windows**: rewrites the XMP metadata section
of supported files (one-time re-upload per tagged file is expected).
- **Idempotent**: safe to run as often as you like.
- **Resumable**: if interrupted, the next run picks up where it left off.
- **One-way**: tags flow cloud → local only. Tagging on the web is
still the source of truth.