Skip to content

Commit 6863ea3

Browse files
pmaxhoganclaude
andauthored
feat: per-source toggle to back up OneDrive cloud-only placeholder files (#133)
## What & why Issue #4: give each backup source a policy for OneDrive / cloud-only placeholder files. Windows OneDrive Files-On-Demand marks a dehydrated placeholder with `FILE_ATTRIBUTE_RECALL_ON_OPEN`; opening it forces a network hydration, so the scanner has always skipped these by default (DESIGN s5.2.1) to avoid silently pulling down terabytes of cloud-only data. Some users, though, *want* those files backed up. This adds a per-source opt-in to do exactly that. ## Policy semantics New `PlaceholderPolicy` (serde snake_case) carried end-to-end: - **`skip`** (default): unchanged behaviour - a cloud-only placeholder is skipped before stat/hash and its existing `file_state` row is preserved (not treated as a deletion). - **`force_download`**: the scanner does NOT skip on the recall attribute; the file flows through the normal open/read path and Windows hydrates it on read (the byte read happens downstream at hash/upload time, so scan-time `stat` never triggers hydration). Stored as a new **nullable** `placeholder_policy` TEXT column (migration `0008`). `NULL` (every pre-migration row) and any unrecognised value decode to `skip`, so the change is fully backward compatible with no data migration. ## Shape - `crates/driven-core`: `PlaceholderPolicy` enum + `SourceRow.placeholder_policy`, migration `0008`, 5 sqlx query sites (2 SELECT + 3 upsert) + regenerated committed `.sqlx/`. Scanner gates the skip via a pure `should_skip_placeholder(attrs, policy)` fn (mirrors the repo's `classify_*` / `fallback_decision` pure-decision pattern) so both branches are unit-testable cross-platform (the real attribute cannot be synthesised on a temp file portably). - IPC: `AddSourceRequest` (`#[serde(default)]`), `SourcePatch` (`Option<PlaceholderPolicy>`), and `SourceDto` gain the field; wired through `add_source` / `update_source`. TS mirrors updated in `ipc/types.ts`. - UI: a "Back up OneDrive cloud-only files" toggle with an explanatory caption in BOTH the add-source wizard (exclusions step) and the SourceTable inline edit panel; strings localized in `en-US.json`. ## Tests - Rust: `should_skip_placeholder` both-policy unit test; `from_db` NULL/unknown -> skip + encode round-trip; sqlite `placeholder_policy_round_trip` (default + force_download persist/reload/flip). `cargo fmt --check`, `clippy --workspace --all-targets -D warnings`, `cargo test -p driven-core / -p driven-app / -p driven-cli / -p driven-chaos` all green. Migration `0008` verified applying in order. - UI: extended `settings-components.test.ts` with mount tests exercising the new toggle in the wizard and both SourceTable states (reflect current policy + patch on save). `pnpm lint`, `vue-tsc --noEmit`, `pnpm test:unit` all green. Note: the scanner import of `PlaceholderPolicy` is `#[cfg(any(windows, test))]` gated to match its only (cfg-gated) consumer, so the non-Windows CI clippy legs stay warning-free. Refs #34 (backlog item: OneDrive cloud-only placeholders). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 4e9fde6 commit 6863ea3

43 files changed

Lines changed: 573 additions & 46 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.sqlx/query-3195ca7d060e43ce8be8651e66bd65239aaede46a6d237390abeda1a119a2d75.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-eee4b2498d829e426305b803d63a62371271105bd7ef3f626cd970a5920bb548.json renamed to .sqlx/query-519c2f6c9e040c67a14f44d92553d4aea61375d50d79cf95105980d318db5ff9.json

Lines changed: 19 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-af709bab951a1b495c05e5f4179a9fbd6f7d1e66bdbe62fa84429740ef5cf9ba.json renamed to .sqlx/query-95c1eb7488ee60abedbc08d67ca4c78c3be90e8863d8eaa89733fd6f20cc5ce8.json

Lines changed: 19 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-a450f209392b6f773adb766d1ffdbc50647a6b4e1cea4a4b2f92460200031be9.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

crates/driven-chaos/src/scenarios/concurrency.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ fn source_in(account: AccountId, root: &std::path::Path, folder_id: &str) -> Sou
141141
respect_gitignore: false,
142142
include_patterns: vec![],
143143
exclude_patterns: vec![],
144+
placeholder_policy: Default::default(),
144145
schedule_json_v2_reserved: None,
145146
deep_verify_interval_secs: 604_800,
146147
last_full_scan_at: None,

crates/driven-chaos/src/scenarios/drive_side.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ fn source_in(account: AccountId, root: &std::path::Path, folder_id: &str) -> Sou
9797
respect_gitignore: false,
9898
include_patterns: vec![],
9999
exclude_patterns: vec![],
100+
placeholder_policy: Default::default(),
100101
schedule_json_v2_reserved: None,
101102
deep_verify_interval_secs: 604_800,
102103
last_full_scan_at: None,

crates/driven-chaos/src/scenarios/file_size.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ fn source_in(account: AccountId, root: &Path, folder_id: &str) -> SourceRow {
145145
respect_gitignore: false,
146146
include_patterns: vec![],
147147
exclude_patterns: vec![],
148+
placeholder_policy: Default::default(),
148149
schedule_json_v2_reserved: None,
149150
deep_verify_interval_secs: 604_800,
150151
last_full_scan_at: None,

crates/driven-chaos/src/scenarios/filenames.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ fn source_row(account: driven_core::types::AccountId, root: &Path, folder_id: &s
240240
respect_gitignore: false,
241241
include_patterns: vec![],
242242
exclude_patterns: vec![],
243+
placeholder_policy: Default::default(),
243244
schedule_json_v2_reserved: None,
244245
deep_verify_interval_secs: 604_800,
245246
last_full_scan_at: None,

crates/driven-chaos/src/scenarios/mutation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ fn source_in(account: driven_core::types::AccountId, root: &Path, folder_id: &st
188188
respect_gitignore: false,
189189
include_patterns: vec![],
190190
exclude_patterns: vec![],
191+
placeholder_policy: Default::default(),
191192
schedule_json_v2_reserved: None,
192193
deep_verify_interval_secs: 604_800,
193194
last_full_scan_at: None,

crates/driven-chaos/src/scenarios/mutator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ fn flat_source(account: AccountId, root: &Path, folder_id: &str) -> SourceRow {
147147
respect_gitignore: false,
148148
include_patterns: vec![],
149149
exclude_patterns: vec![],
150+
placeholder_policy: Default::default(),
150151
schedule_json_v2_reserved: None,
151152
deep_verify_interval_secs: 604_800,
152153
last_full_scan_at: None,

0 commit comments

Comments
 (0)