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
198 lines
8.7 KiB
Markdown
198 lines
8.7 KiB
Markdown
# 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.
|