Rework library sync around a JSON document; add card filters
Sync
The previous design put the merge inside SQL: timestamp-stamping triggers,
a tombstone table, per-device SQLite snapshots, a device id in the database,
and row merges written as correlated-subquery UPDATEs. All of that schema
was inherited by desktop, and the merge was very hard to verify.
Replace it with a single JSON document plus a three-way merge in plain Rust.
Storing the last-read remote document as the merge base makes "who changed
what" and "what was deleted" derivable, which removes the tombstone table,
the updated_at columns and all eight triggers. It also makes a single shared
file safe: if a publish is lost the remote is unchanged, so the next merge
re-detects the local edits and republishes rather than reading their absence
as a deletion. That removes the need for per-device files and for device
identity entirely. Schema impact drops to one column, decks.uuid.
Conflicts resolve to "theirs", which is required for convergence rather than
chosen for taste — preferring ours has each device re-asserting its own value
forever. Every such choice is reported to the user.
SQLite becomes invisible: the working copy lives in the app data dir and the
user only ever names a .json file. There is always a document, defaulting to
private storage, so edits are never stranded with nowhere to be saved.
Pointing at a file that doesn't exist yet moves the current library there;
pointing at one that does adopts it. A converter brings a pre-document
library.db across.
Android reaches the folder through the Storage Access Framework rather than
MANAGE_EXTERNAL_STORAGE, so no storage permission is declared at all.
Filters
Track card origin in reference.db: Universes Beyond, digital-only and
Un-set cards, plus numeric power/toughness. Each origin flag is derived as
"every printing", not "any printing" — ordinary cards get reprinted into
those sets, and Lightning Bolt appearing in Marvel Commander must not hide
it. Non-numeric power ("*", "1+*") stays NULL so it drops out of range
filters instead of being compared as zero.
Also wire up filters over data already present: price range, format legality
(restricted counts as legal) and "can be a commander". All of these degrade
silently against an older reference.db rather than failing every query.
The shared criteria live in one component used by both filter panels, as a
compact row of icon toggles.
Card Search gains the Library tab's bulk selection: checkboxes in both views,
select-all, and a bulk add-to-deck with a single undo for the whole batch.
This commit is contained in:
@@ -287,6 +287,40 @@ pub struct LibraryFilter {
|
||||
pub location: Option<String>,
|
||||
/// Case-insensitive substring match against the card's oracle rules text.
|
||||
pub oracle_text: Option<String>,
|
||||
/// Hide cards that exist only as Universes Beyond (Lord of the Rings,
|
||||
/// Warhammer 40,000, Marvel, ...). Ordinary cards reprinted into a UB set
|
||||
/// are kept — see `oracle_cards.universes_beyond`.
|
||||
///
|
||||
/// Silently inert against a `reference.db` built before that column
|
||||
/// existed, the same way the other reference-schema additions degrade.
|
||||
#[serde(default)]
|
||||
pub exclude_universes_beyond: bool,
|
||||
/// Hide cards that exist only on Arena/MTGO (Alchemy rebalances and the
|
||||
/// like) and so can never be in a physical collection.
|
||||
#[serde(default)]
|
||||
pub exclude_digital_only: bool,
|
||||
/// Hide cards that exist only in "funny" sets (Unglued, Unhinged, Unstable,
|
||||
/// Unfinity).
|
||||
#[serde(default)]
|
||||
pub exclude_funny: bool,
|
||||
/// Cheapest known printing, in USD. Cards with no known price count as 0,
|
||||
/// matching how they're displayed.
|
||||
pub price_min: Option<f64>,
|
||||
pub price_max: Option<f64>,
|
||||
/// Only cards legal in this format (a [`LEGALITY_FORMATS`] slug, e.g.
|
||||
/// "commander"). Excludes banned and not-legal; restricted still counts as
|
||||
/// legal, since a restricted card is playable as a single copy.
|
||||
pub legal_in: Option<String>,
|
||||
/// Only cards that can head a Commander deck.
|
||||
#[serde(default)]
|
||||
pub can_be_commander: bool,
|
||||
/// Printed power/toughness range. Cards whose printed value isn't a plain
|
||||
/// number ("*", "1+*") are excluded from these, since they can't be
|
||||
/// compared — see `oracle_cards.power`.
|
||||
pub power_min: Option<f64>,
|
||||
pub power_max: Option<f64>,
|
||||
pub toughness_min: Option<f64>,
|
||||
pub toughness_max: Option<f64>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user