Skip to content

Commit 46f1770

Browse files
committed
Add Dropbox/iCloud sync status icons
Detect and display cloud sync status (synced, online-only, uploading, downloading) for files and folders in both Full and Brief view modes. Backend: - Add `sync_status.rs` with macOS `SF_DATALESS` flag and NSURL detection - Expose `get_sync_status` Tauri command with parallel fetching Frontend: - Add 4 SVG sync status icons - Display icons in FileList (Full mode) and BriefList (Brief mode) - Poll visible files every 2s for real-time status updates
1 parent c779a6d commit 46f1770

18 files changed

Lines changed: 676 additions & 69 deletions
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Dropbox sync status detection on macOS
2+
3+
Dropbox does not document how to programmatically detect file sync status. This document captures our
4+
reverse-engineering findings from December 2025.
5+
6+
## Overview
7+
8+
On macOS 12.5+, Dropbox uses Apple's **File Provider API**. Files live in `~/Library/CloudStorage/Dropbox/` (with
9+
`~/Dropbox` as a symlink). Sync status can be detected via:
10+
11+
1. **File system metadata** (`stat()`) - fast, no API calls
12+
2. **Spotlight metadata** (`mdls` / `NSMetadataItem`) - slower but more detailed
13+
3. **Extended attributes** (`com.dropbox.attrs`) - undocumented binary format
14+
15+
---
16+
17+
## Detection methods
18+
19+
### Method 1: stat() - fastest
20+
21+
The `stat()` system call provides two key indicators:
22+
23+
| Field | Access in Rust | Meaning |
24+
| ----------- | ----------------------------------------- | ----------------------------------- |
25+
| `st_blocks` | `metadata.blocks()` | Number of 512-byte blocks allocated |
26+
| `st_flags` | `metadata.st_flags()` (via `MetadataExt`) | File flags including `SF_DATALESS` |
27+
28+
**Key insight**: Online-only files have `blocks = 0` and `SF_DATALESS` flag set.
29+
30+
```rust
31+
use std::os::unix::fs::MetadataExt;
32+
33+
const SF_DATALESS: u32 = 0x40000000;
34+
35+
fn is_online_only(metadata: &std::fs::Metadata) -> bool {
36+
metadata.blocks() == 0 || (metadata.st_flags() & SF_DATALESS != 0)
37+
}
38+
```
39+
40+
### Method 2: Spotlight metadata - for sync progress
41+
42+
Spotlight indexes Dropbox files with these relevant keys:
43+
44+
| Key | Type | Meaning |
45+
| ---------------------- | ---------- | ---------------------------------------------- |
46+
| `kMDItemIsUploaded` | Bool (0/1) | File content fully uploaded to Dropbox servers |
47+
| `kMDItemIsUploading` | Bool (0/1) | File is currently uploading |
48+
| `kMDItemIsDownloading` | Bool (0/1) | File is currently downloading |
49+
| `kMDItemIsDownloaded` | Bool | File content fully downloaded (often null) |
50+
51+
**Note**: Spotlight indexing may lag behind actual state. New/temporary files may show `(null)` for all values.
52+
53+
Query via command line:
54+
55+
```bash
56+
mdls -name kMDItemIsUploaded -name kMDItemIsUploading /path/to/file
57+
```
58+
59+
### Method 3: Extended attributes - undocumented
60+
61+
Dropbox stores sync metadata in `com.dropbox.attrs` (26 bytes, binary format):
62+
63+
```bash
64+
xattr -px com.dropbox.attrs /path/to/file
65+
# Output: 0A 12 0A 10 FB 39 37 41 16 17 ED F2 00 00 00 00 00 8E E5 7E 10 ...
66+
```
67+
68+
The format appears to be a protobuf or custom binary encoding. First 16 bytes are consistent across files (possibly
69+
account/folder ID). Last 10 bytes vary by file/state. **Not recommended** for detection due to lack of documentation.
70+
71+
---
72+
73+
## Observed file states
74+
75+
### Synced (downloaded and up-to-date)
76+
77+
```
78+
stat: blocks=224, flags=0x40 (64)
79+
mdls: kMDItemIsUploaded=1, kMDItemIsUploading=0
80+
Finder: Green checkmark ✅
81+
```
82+
83+
### Online-only (cloud stub)
84+
85+
```
86+
stat: blocks=0, flags=0x40000060 (1073741920)
87+
Flags include: SF_DATALESS, UF_COMPRESSED, UF_TRACKED
88+
mdls: kMDItemIsUploaded=1, kMDItemIsUploading=0
89+
Finder: Cloud icon ☁️
90+
```
91+
92+
### Uploading (syncing to cloud)
93+
94+
```
95+
stat: blocks>0, flags=0x40 (64)
96+
mdls: kMDItemIsUploaded=0, kMDItemIsUploading=1
97+
Finder: Circular arrows 🔄
98+
```
99+
100+
### Downloading (syncing from cloud)
101+
102+
```
103+
stat: blocks=0, flags=0x40000060 (SF_DATALESS)
104+
Size shows expected final size, blocks still 0
105+
mdls: All values may be (null) during download
106+
Finder: Pie chart progress indicator 🥧
107+
```
108+
109+
### Download progress calculation
110+
111+
Finder shows a pie-chart progress indicator during downloads. We can potentially calculate progress using:
112+
113+
```
114+
progress = (blocks * 512) / size
115+
```
116+
117+
Where:
118+
119+
- `blocks` = `stat().st_blocks` (number of 512-byte blocks allocated)
120+
- `size` = `stat().st_size` (total file size)
121+
122+
**Caveat**: In our testing, `blocks` remained 0 during download (File Provider may buffer to temp location). This
123+
approach may not work with Dropbox's File Provider implementation. There is no public API to query per-file download
124+
progress from a third-party File Provider extension – the `NSProgress` is internal to Dropbox's extension.
125+
126+
**Recommendation**: Show syncing icon 🔄 without progress percentage. Displaying accurate progress would require
127+
reverse- engineering Dropbox's internal progress mechanism, which is not feasible.
128+
129+
---
130+
131+
## Recommended detection algorithm
132+
133+
```rust
134+
pub enum DropboxSyncStatus {
135+
Synced, // Fully downloaded and uploaded
136+
OnlineOnly, // Stub file, content in cloud
137+
Uploading, // Local changes being uploaded
138+
Downloading, // Cloud content being downloaded
139+
Unknown, // Not a Dropbox file or status unclear
140+
}
141+
142+
pub fn get_sync_status(path: &Path) -> DropboxSyncStatus {
143+
let metadata = match std::fs::metadata(path) {
144+
Ok(m) => m,
145+
Err(_) => return DropboxSyncStatus::Unknown,
146+
};
147+
148+
let blocks = metadata.blocks();
149+
let flags = metadata.st_flags();
150+
let is_dataless = (flags & 0x40000000) != 0 || blocks == 0;
151+
152+
// Fast path: check stat() first
153+
if is_dataless {
154+
// Could be online-only OR downloading
155+
// To distinguish, would need Spotlight query (slower)
156+
return DropboxSyncStatus::OnlineOnly;
157+
}
158+
159+
// File has local content - check if uploading via Spotlight
160+
// (This part requires NSMetadataItem query - omitted for brevity)
161+
// if is_uploading { return DropboxSyncStatus::Uploading; }
162+
163+
DropboxSyncStatus::Synced
164+
}
165+
```
166+
167+
---
168+
169+
## File flags reference
170+
171+
Relevant macOS file flags from `<sys/stat.h>`:
172+
173+
| Flag | Value | Meaning |
174+
| --------------- | ---------- | -------------------------------------- |
175+
| `UF_NODUMP` | 0x00000001 | Don't dump file |
176+
| `UF_COMPRESSED` | 0x00000020 | File is compressed |
177+
| `UF_TRACKED` | 0x00000040 | File changes tracked (File Provider) |
178+
| `UF_DATAVAULT` | 0x00000080 | Entitlement required for access |
179+
| `SF_DATALESS` | 0x40000000 | **File is a stub** - content not local |
180+
181+
Online-only Dropbox files typically have flags: `SF_DATALESS | UF_COMPRESSED | UF_TRACKED` = `0x40000060`
182+
183+
---
184+
185+
## Open questions
186+
187+
1. **Downloading vs online-only**: Both show `blocks=0` and `SF_DATALESS`. During active download, Spotlight metadata
188+
may be `(null)`. Need to find a reliable way to distinguish "static online-only" from "currently downloading".
189+
190+
2. **Sync errors**: How are sync errors (conflicts, permission issues) indicated? Need test files with error states.
191+
192+
3. **Ignored files**: Files with `com.dropbox.ignored` xattr are not synced. Detection is straightforward:
193+
194+
```bash
195+
xattr -p com.dropbox.ignored /path/to/file # Returns "1" if ignored
196+
```
197+
198+
4. **Shared folders**: Do shared folder files have different status indicators?
199+
200+
5. **Spotlight latency**: How quickly does Spotlight update after state changes? Is there a faster notification
201+
mechanism?
202+
203+
---
204+
205+
## Test files used
206+
207+
All in `~/Dropbox/sharing/`:
208+
209+
| File | State | Notes |
210+
| ------------------------ | ----------- | ----------------------------------- |
211+
| `edlevo-downloaded.png` | Synced | blocks=224, flags=0x40 |
212+
| `edlevo-online-only.png` | Online-only | blocks=0, flags=0x40000060 |
213+
| `edlevo-syncing.png` | Uploading | blocks>0, kMDItemIsUploading=1 |
214+
| `.../tmp.a` | Downloading | blocks=0, size=571MB, mdls all null |
215+
216+
---
217+
218+
## References
219+
220+
- Apple File Provider documentation: https://developer.apple.com/documentation/fileprovider
221+
- Dropbox help article on sync icons: https://help.dropbox.com/sync/icons
222+
- macOS `stat.h` flags: `/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/stat.h`

docs/todo.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@
1616
- [x] Add file watching to auto-update changes. It should be as close to immediate as possible
1717
- [x] Implement proper Full view (with fixed columns)
1818
- [x] Add Brief view with view switching option
19+
- [x] Make sure it lists Dropbox files correctly, incl. files that are loaded on the fly
1920
- [ ] Add different sorting options
20-
- [ ] Make sure it lists Dropbox files correctly, incl. files that are loaded on the fly
2121
- [ ] When sorting alphabetically, sort numbers ascending, not alphabetically
2222
- [ ] Add "change drive" feature
2323
- [ ] Tweak chunked loading to load in chunks on the backend based on drive speed. So far, we've only tested with fast
2424
drives, and chunk sizes are constant.
25+
- [ ] Load iCloud sync statuses, too
26+
- [ ] Load Google Drive sync statuses, too
27+
- [ ] Load OneDrive sync statuses, too?
28+
-
29+
## Cleanup
30+
31+
- Rename FileList to FullList
32+
- In Full mode, size display coloring is ugly, fix it
33+
- When the app starts, it's temporarily all white bg for like 1 second. Go around this by delaying opening the window,
34+
or show a nice loading screen via normal HTML (no svelte) if that solves it. What can we do?
35+
- Big dir reading is wrong: takes 7 seconds for the 50k dir, and it looks weird: it shows 45k files Loading but then it
36+
loads immediately
2537

2638
## Settings
2739

0 commit comments

Comments
 (0)