Repository navigation
Almacenamiento y blobs
English · Español
Todo lo que la app guarda en local se reparte en dos sitios: app_data_dir (la biblioteca SQLite library.db, el almacén de imágenes direccionado por contenido blobs/, los recursos descargados, la partida en curso y la identidad para jugar en red) y app_cache_dir (ficheros derivados que se pueden borrar en cualquier momento). Los mazos dependen de app_data_dir; nada depende de app_cache_dir.
Código: src-tauri/src/store/ (db.rs, decks.rs, blobs.rs, etiquetas.rs, colecciones.rs, presets.rs, settings.rs, listas.rs, partidas.rs, migrations/*.sql), src-tauri/src/mesa/guardada.rs (la partida en curso), src-tauri/src/state.rs (Paths, AppState::open), src-tauri/src/render.rs (caché de renderizado) y src/stores/deck-store.ts (el lado de la pantalla).
Decisión: las imágenes de los mazos viven en el almacén de blobs bajo app_data_dir/blobs/, no en la caché. Motivo: los mazos referencian las imágenes por SHA-256, así que las imágenes son datos del usuario; el sistema operativo o el usuario pueden vaciar un directorio de caché cuando quieran, y eso nunca debe romper un mazo. La caché (app_cache_dir/renders/) solo guarda renderizados derivados (miniaturas, vistas previas de diseños) que se recalculan a partir de los blobs cuando hacen falta.
AppState::open(paths) (state.rs) crea los dos directorios, abre library.db, ejecuta store::decks::rellenar_refs una sola vez, construye BlobStore::new(data/blobs) y Recursos::abrir(data), y carga (o crea) la clave de identidad para jugar en red con red::identidad::cargar_o_crear(data). Paths { data, cache } se rellena en src-tauri/src/lib.rs con app_data_dir() / app_cache_dir() de Tauri, pasando por red::perfil::carpeta (con un perfil de debug, perfiles/<nombre>/ dentro de cada una).
Db (store/db.rs) envuelve una única rusqlite::Connection en un Mutex (rusqlite no es Sync; los trabajos largos sueltan el cerrojo entre pasos). Al abrir fija journal_mode=WAL, foreign_keys=ON, synchronous=NORMAL y después ejecuta las migraciones. Db::in_memory() se usa en los tests.
Las migraciones son ficheros SQL con include_str! listados en orden en MIGRACIONES (store/db.rs) y registrados en schema_migrations (version, applied_at) (que se crea con CREATE TABLE IF NOT EXISTS antes del bucle). Cada una se ejecuta en su propia transacción, junto con su fila de schema_migrations. Regla: las migraciones solo se añaden; una publicada nunca se edita.
| # | Fichero | Añade |
|---|---|---|
| 1 | 0001_init.sql |
decks, deck_cards (+ índices deck_cards_orden, deck_cards_front, deck_cards_back), blobs, presets (+ presets_kind), app_settings, scryfall_cards (+ scryfall_nombre, scryfall_impresion). |
| 2 | 0002_blob_refs.sql |
blob_refs (owner_kind, owner_id, blob) + blob_refs_blob: referencias a imágenes que no son ni el anverso ni el reverso de una carta (arte del diseñador, emblemas, imágenes «antes»). |
| 3 | 0003_biblioteca.sql |
Organización de la biblioteca: columnas decks.pinned, decks.notes, decks.opened_at, decks.cover_card_id; tablas tags (+ único tags_name sobre name COLLATE NOCASE) y deck_tags (+ deck_tags_tag). |
| 4 | 0004_idioma_deck.sql |
Columna decks.lang_override (idioma del mazo elegido a mano; NULL significa «calculado a partir de las cartas»). |
| 5 | 0005_listas_juego.sql |
Tabla deck_listas (deck_id, id, nombre, comandantes, cartas, updated_at), clave primaria (deck_id, id), deck_id referencia a decks(id) con ON DELETE CASCADE: las listas para jugar de la mesa de juego (Mesa de juego). |
| 6 | 0006_partidas.sql |
Tablas partidas (una fila por partida terminada: hora de inicio y de fin, formato y modo como valores JSON, jugadores y ganadores como JSON, cómo acabó, una nota, rondas, duración, versión del catálogo y registro, el registro comprimido o NULL) y partida_decks (partida_id, jugador, deck_id, puesto), clave primaria (partida_id, jugador), con partida_id referenciando partidas(id) ON DELETE CASCADE; índices partidas_terminada y partida_decks_deck: el historial de partidas (Mesa de juego). |
| 7 | 0007_icono_etiqueta.sql |
Columna tags.icon (TEXT, NULL = sin icono): el id de Iconify del icono de una etiqueta de la biblioteca (game-icons:crown); véanse Biblioteca de mazos e Iconos. |
| 8 | 0008_partida_imagenes.sql |
Tabla partida_imagenes (partida_id, sha), clave primaria (partida_id, sha), partida_id con referencia a partidas(id) ON DELETE CASCADE, índice partida_imagenes_sha: las imágenes propias (por SHA-256) que pinta la repetición de una partida del historial, cuyas copias viven en app_data_dir/partidas/imagenes/ (véase Mesa de juego). |
| 9 | 0009_linaje.sql | Tabla deck_linaje (deck_id, linaje, actualizado), con deck_id como clave primaria que referencia decks(id) ON DELETE CASCADE, índice deck_linaje_linaje; la migración da además a cada deck que ya existe su propio id como linaje y su updated_at como hora: qué decks de equipos distintos son «el mismo deck» (Pasar decks). |
| 10 | 0010_amigos.sql | Tablas amigos_mensajes (la conversación descifrada con cada amigo: amigo = su identidad en hex, mio, tipo texto/invitacion, texto, enviado = reloj de quien lo escribió, orden = reloj de este equipo, estado, id_cliente, id_servidor; índices únicos en id_cliente y en id_servidor de los recibidos, para que reenviar o perder un ack nunca duplique) y amigos_claves (de cada amigo: la clave de cifrado vista la última vez, verificado, cambio, leido_hasta). El servidor solo tuvo estos mensajes cifrados (Amigos y mensajes). |
| 11 | 0011_amigos_alias.sql | Tabla amigos_alias (amigo PRIMARY KEY, alias, nota): el nombre que le pongo a un amigo y mi nota privada sobre él, solo en este equipo (Amigos y mensajes). |
Test: abrir_dos_veces_aplica_la_migracion_una_sola_vez (db.rs) también ejecuta PRAGMA integrity_check.
erDiagram
decks ||--o{ deck_cards : "ON DELETE CASCADE"
decks ||--o{ deck_tags : "ON DELETE CASCADE"
tags ||--o{ deck_tags : "ON DELETE CASCADE"
decks ||--o{ deck_listas : "ON DELETE CASCADE"
decks ||--o| deck_linaje : "ON DELETE CASCADE"
decks }o--o{ blobs : "default_back_blob"
deck_cards }o--o{ blobs : "front_blob / back_blob"
blob_refs }o--|| blobs : "blob"
decks ||--o{ blob_refs : "owner_kind=deck"
presets ||--o{ blob_refs : "owner_kind=preset"
decks ||--o{ blob_refs : "owner_kind=mesa (tapete de la mesa)"
partidas ||--o{ partida_decks : "ON DELETE CASCADE"
partidas ||--o{ partida_imagenes : "ON DELETE CASCADE"
decks {
text id PK
text name
text decklist_text
text settings_json
text default_back_json
text default_back_blob
int schema_version
int created_at
int updated_at
int pinned
text notes
int opened_at
text cover_card_id
text lang_override
}
deck_cards {
text id
text deck_id FK
int position
text front_blob
text back_blob
text card_json
}
blobs {
text hash PK
text ext
text mime
int width
int height
int bytes
text origin
int created_at
}
blob_refs {
text owner_kind
text owner_id
text blob
}
presets {
text id PK
text kind
text name
text json
int updated_at
}
tags {
text id PK
text name
text color
text icon
int created_at
}
deck_tags {
text deck_id
text tag_id
}
deck_listas {
text deck_id
text id
text nombre
text comandantes
text cartas
int updated_at
}
app_settings {
text key PK
text value
}
scryfall_cards {
text id PK
text oracle_id
text name
text lang
text set_code
text collector_number
text json
int fetched_at
}
partidas {
text id PK
int empezada
int terminada
text formato
text modo
text jugadores
text ganadores
text como
text nota
int rondas
int duracion_ms
int version_catalogo
blob registro
}
partida_decks {
text partida_id FK
int jugador
text deck_id
int puesto
}
deck_linaje {
text deck_id PK
text linaje
int actualizado
}
partida_imagenes {
text partida_id FK
text sha
}
Notas sobre las tablas:
- La clave primaria de
deck_cardses(deck_id, id);front_blob/back_blobson copias desnormalizadas de los hashes de imagen de la carta, para que el recolector de basura y la biblioteca encuentren las imágenes en uso sin interpretarcard_json. ElDeckCardcompleto escard_json. Un id de carta duplicado dentro de un mismo mazo se convierte enInvalidInputconmotivo = duplicate_card_id. -
decks.default_back_jsones elFaceImageserializado;default_back_blobes su hash (por la misma razón). -
presets.kindes uno defrontOffsets,backOffsets,print(store/presets.rs, se guardan por nombre: mismo tipo y nombre sustituye) ocollection(store/colecciones.rs, se guarda por id para que renombrar no cree una segunda colección; una colección que ya no se deserializa, por ejemplo de una versión futura, la omitelist). -
scryfall_cardses una caché de metadatos (TTL de una semana,scryfall/cache.rs; los precios se refrescan a las 24 h). Ten en cuenta que, aunque es una caché, vive dentro delibrary.dbenapp_data_dir, porque las estadísticas y la biblioteca funcionan sin conexión a partir de ella (commands/stats.rs,biblioteca.rs). -
deck_listas(store/listas.rs) guarda las listas para jugar de la mesa de juego:comandantesycartasson arrays JSON de ids de copias del deck (DeckCard.id). Viven aparte del deck, como las etiquetas, porquedecks::savereescribe el deck en cada autoguardado y las pisaría; los ids que ya no están en el deck solo se avisan al comprobar la lista.guardarinserta o sustituye por(deck_id, id)(un id nuevo si viene vacío) con el nombre limpio y cortado a 60 caracteres. -
deck_linaje(store/linaje.rs) dice qué decks son el mismo deck en equipos distintos.linajees un id compartido por todas las copias: un deck nuevo toma su propio id (decks::saveinserta la fila), un duplicado recibe otro, y uno importado o recibido conserva el que traía.actualizadoes la hora (ms) del último guardado de esa versión, en el equipo donde se hizo: la renuevandecks::saveydecks::rename, y viaja en el.pbdeck(Biblioteca.linaje/actualizado). Un deck sin fila (de antes de la migración, o metido a mano en un test) cuenta con su id como linaje (linaje::de,linaje::decks_de). Vive aparte del deck como las etiquetas y las listas para jugar, y escribirla no cambiaupdated_at. -
partidasypartida_decks(store/partidas.rs) son el historial de la mesa de juego.registroes laPartidaGuardadade la partida (el mismo JSON comprimido que la partida guardada,mesa/guardada.rs) oNULLcuando quien la guardó nunca tuvo la partida entera (un invitado en red).partida_decksno tiene clave foránea adecksa propósito: borrar un deck deja sus partidas, con el nombre del deck escrito dentro del JSON de los jugadores. Guardar una partida con un id que ya existe la reemplaza (y a suspartida_decks); jugar nunca tocadecks.updated_at. Cada jugador del JSON lleva también la lista para jugar usada (listaId,lista,cartasLista), escrita desdedeck_listasal guardar la partida; las filas guardadas antes se leen sin ellos. -
partida_imagenes(store/partidas.rs) apunta, por cada partida con repetición, las huellas de las imágenes propias que pinta (NuevaPartida.imagenes). No es un dueño deblob_refs: esas imágenes no son blobs sino imágenes de la mesa (JPEG saneados, también las de los demás jugadores), copiadas fuera de la caché aapp_data_dir/partidas/imagenes/.partidas::borrarborra la partida en una transacción y devuelve las huellas que ya no usa ninguna otra fila, para que el comando borre esas copias; una huella que comparten dos partidas se queda hasta que se va la última. -
tags.colores uno deCOLORES(gray red orange amber green teal sky blue violet pink); un color desconocido se queda en gris; los nombres de etiqueta se recortan, se limitan a 40 caracteres, son únicos sin distinguir mayúsculas ycreatedevuelve la etiqueta existente si el nombre ya existe.tags.iconse pone aparte conetiquetas::icono(tag_icon_set), que solo acepta un id de Iconify válido (iconos::id_valido) oNULL;updatelo conserva. - Las fechas son milisegundos desde 1970 (
store::ahora_ms).
decks::save y decks::rename fijan updated_at. Fijar como favorito, las notas (máx. 4000 caracteres), la carta de portada, el idioma a mano, las etiquetas, las listas para jugar y «abierto en» deliberadamente no lo tocan (etiquetas.rs, decks::marcar_abierto), para que organizar la biblioteca no la reordene por fecha de modificación. Test: favorito_notas_y_portada_no_cambian_la_fecha.
| Función | Comportamiento |
|---|---|
create(db, name, default_back) |
Uuid nuevo, PrintSettings por defecto, DECK_SCHEMA_VERSION, y después save. La capa de comandos (commands/library.rs crear_deck) le pasa el reverso genérico. |
open(db, id) |
Lee la fila del mazo y todos los card_json ordenados por position; NotFound si no existe, Db si el JSON ya no se deserializa. deck_open también llama a marcar_abierto; deck_peek no. |
save(db, deck) |
Una transacción. Primero valida que toda imagen referenciada existe en blobs (anversos, reversos, reverso del mazo e imágenes del diseñador de refs_del_deck); si falta alguna, InvalidInput, motivo = blob_missing. Después hace upsert de la fila de decks (conserva created_at y las columnas de la biblioteca), borra y vuelve a insertar todos los deck_cards en orden, y reescribe los blob_refs del mazo. Devuelve la hora de guardado. |
list(db) |
Un DeckSummary por mazo para la biblioteca: número de cartas, portada (la carta elegida si sigue presente y con imagen; si no, la primera carta con imagen), etiquetas. Ordenado por updated_at DESC. |
duplicate(db, id, name) |
Lo abre, asigna uuids nuevos al mazo y a cada carta, lo guarda y después copia notas, portada (reasignada al id nuevo de la carta), idioma a mano y etiquetas (no el favorito). Las imágenes se comparten, no se copian. |
delete(db, id) |
Borra el mazo (la cascada elimina cartas, etiquetas y listas para jugar), sus blob_refs (owner_kind = 'deck'), la referencia de su tapete de la mesa de juego (owner_kind = 'mesa') y el ajuste de ese tapete (mesa.tapete.<id>). Los ficheros de imagen no se eliminan; lo hace después la recolección de basura. |
rellenar_refs(db) |
Relleno único de blob_refs a partir de los mazos existentes, protegido por la clave blob_refs_v1 de app_settings (una tabla vacía no es un marcador válido porque un mazo sin diseños la deja vacía). |
Tests: guardar_y_reabrir_135_cartas_da_lo_mismo, no_se_guarda_una_carta_con_una_imagen_que_no_existe, dos_cartas_con_el_mismo_id_es_un_error_claro, duplicar_renombrar_y_borrar, la_limpieza_respeta_el_arte_de_los_disenos, el_relleno_de_referencias_se_hace_una_vez.
BlobStore (store/blobs.rs) es un almacén de imágenes direccionado por contenido.
- Estructura:
app_data_dir/blobs/<primeros dos caracteres hex>/<sha256>.<ext>(BlobStore::path_of).extesjpg,pngowebp(proxy_core::imaging::probe::Kind). - La identidad es el SHA-256 de los bytes del fichero, en hexadecimal minúscula, 64 caracteres (
es_huellalo valida y se reutiliza para validar rutas que vienen de paquetes y del protocolo de imágenes). Dos mazos con la misma carta comparten un único fichero. - Fila de metadatos en
blobs:ext,mime,width,height,bytes,origin,created_at.
| Función | Comportamiento |
|---|---|
put_bytes(db, bytes, origin) -> FaceImage |
Detecta el formato (ErrorCode::Unsupported si no es una imagen admitida; si está corrupta, ErrorCode::Image con motivo = corrupt), rechaza cualquier lado superior a LADO_MAXIMO = 12000 px (Image, motivo = too_large), calcula el hash, escribe de forma atómica si no existe y hace INSERT OR IGNORE de la fila. Si el blob ya existía, el origin devuelto es con el que entró la primera vez. |
import_files(db, paths) |
Lee ficheros del usuario y les aplica put_bytes como Upload. Copia, nunca enlaza, así que borrar el original no puede romper un mazo. |
generic_back(db) |
Inserta el reverso genérico incluido (assets/reverso-generico.png, 745x1040, origen Generated) y guarda su hash en app_settings bajo generic_back_blob. |
locate(db, hash) |
Ruta y Kind de un blob conocido (busca ext en blobs); None para hashes mal formados. |
gc(db, ocupado) |
Véase más abajo. |
escribir_atomico(dest, bytes) |
Escribe en un temporal .part en la misma carpeta (su nombre es único en cada escritura: id del proceso y un contador, porque la preparación de la mesa escribe en paralelo y dos copias de la misma carta, o dos decks que la comparten, escriben el mismo destino a la vez; con un nombre fijo uno renombraba el temporal del otro y fallaba con «no existe el fichero», que se veía como «no se puede escribir en disco»; test escribir_a_la_vez_el_mismo_destino_no_falla), hace sync_all y renombra; un fallo nunca deja un fichero a medias con el nombre final. Se reutiliza en casi todas las descargas de la app. |
BlobStore::gc borra los blobs que nada usa y que tienen más de 24 horas (GRACIA_MS). Un blob se conserva si se cumple cualquiera de estas condiciones:
-
deck_cards.front_bloboback_blobes igual a su hash. -
decks.default_back_blobes igual a él. - Una fila de
blob_refsapunta a él (arte del diseñador, emblema del diseñador, imágenes «antes», emblemas de colecciones, tapetes de la mesa de juego, la imagen de los dados, el avatar para jugar en red). - Es el reverso genérico (
app_settings.generic_back_blob). - Se creó hace menos de 24 horas.
Por qué el periodo de gracia: una imagen puede pertenecer a una descarga cuyo mazo aún no se ha guardado. Por qué ocupado: gc devuelve ErrorCode::Busy mientras hay un trabajo de resolución o exportación en marcha (AppState::ocupado()), porque el trabajo puede tener hashes que ninguna fila de mazo referencia todavía. ocupado() también es verdadero mientras hay una partida abierta en la mesa (Mesa de juego). El único que la llama es blob_gc (commands/library.rs), lanzado desde el diálogo «Liberar espacio» (src/features/library/liberar-dialog.tsx); no hay recolección automática. Primero se elimina el fichero (si falta no pasa nada) y después la fila de blobs.
Los propietarios de blob_refs: owner_kind = 'deck' (se reescribe en cada decks::save a partir de CardDesign::blobs()), 'preset' (el emblema de una colección, escrito en colecciones::save y eliminado en colecciones::delete) y 'mesa' (el tapete de la mesa de juego: owner_id es el id del deck o general; lo escribe y lo quita mesa/tapete.rs::guardar, y se borra con su deck en decks::delete; con owner_id = 'dados', la imagen de los dados del jugador, que escribe mesa/dados.rs::guardar) y 'red' (owner_id = 'avatar', el avatar para jugar en red, que escribe y quita red/identidad.rs::guardar). El tapete no puede usar 'deck' porque decks::save reescribe las referencias 'deck' de un deck en cada guardado y lo borraría. Test: la_limpieza_solo_borra_lo_huerfano_y_viejo.
Trampa conocida: cualquier lugar nuevo que guarde un hash de blob debe quedar reflejado en una de las cuatro protecciones anteriores, o la recolección acabará borrando la imagen y el mazo fallará al guardarse o imprimirse (save se niega a guardar una referencia a un blob inexistente, así que normalmente lo notarás en el siguiente guardado).
«Liberar espacio» también puede convertir las imágenes de los decks elegidos, con las reglas de Formato del paquete de mazo, y borrar los originales de este equipo. No se puede deshacer, así que lo que protege los datos es el orden:
- Primero se hacen y se guardan todos los JPEG (
optimizar). Ningún deck ha cambiado aún: cancelar o un fallo aquí deja la biblioteca como estaba, y lo que sobre lo recogegc. - Cada deck se reescribe en una transacción (
decks::save_imagenes), que conservaupdated_aty la hora del linaje: el contenido es el mismo y, si no, otro equipo vería una versión más nueva. -
BlobStore::borrar_sin_usarborra un original solo si no lo usa nadie (usos: otro deck, una colección, la mesa, el reverso genérico), sin las 24 h de gracia degcporque se sabe que acaban de dejar de hacer falta.
estimar cuenta solo lo que se libera de verdad: un original que sigue usando otro deck se queda, y su JPEG ocupa además. Una imagen en la que no compensa (transparencia, colores planos) se apunta en optimizar.descartadas y no se vuelve a ofrecer, porque saberlo obliga a leerla. Se rechaza con Busy durante un trabajo o una partida. La pantalla no lo deja desde la vista Diseñar (una carta en edición tiene sus propias huellas), y un deck abierto pasa por prestarDeck con conservar: false, porque deshacer apuntaría a imágenes borradas. Las partidas guardadas no se ven afectadas: la mesa usa sus propias imágenes (mesa/imagenes.rs).
AppState::carpeta_renders() (render.rs) es el único directorio de caché que gestiona la interfaz de la app. Contenido, todo derivado y regenerable bajo demanda:
Ruta bajo renders/
|
Contenido | Lo escribe |
|---|---|---|
<key>.jpg |
Miniaturas de vista previa procesadas (clave de RenderSpec::key()) |
AppState::miniatura (render.rs) |
t/<hash>.jpg |
Miniaturas de resultados de búsqueda obtenidas de Scryfall u otras fuentes | protocol.rs |
d/<hash>-<width>.png |
Vistas previas de cartas del diseñador |
diseno/vista.rs imagen
|
c/<hash>-<width>.png, c/<hash>.json
|
«Capa sin ilustración» de la carta del diseñador y la información del hueco de la ilustración |
diseno/vista.rs capa
|
b/<blob>-<side>.jpg o .png
|
Ilustración reducida para el arrastre en vivo |
diseno/vista.rs arte_reducido
|
m/<sha256 o s-<id>-<cara>>-<ancho>.jpg |
Imágenes de la mesa de juego reducidas al ancho que pide la pantalla |
mesa/imagenes.rs servir
|
El renderizado del PDF no tiene caché en disco («pesa demasiado», render_para_pdf). Los comandos cache_stats / cache_clear (commands/library.rs) miden y hacen remove_dir_all exactamente de esta carpeta; se exponen en el panel de la lista (src/features/decklist/decklist-panel.tsx) con un texto de confirmación. Test: vaciar_la_cache_no_toca_las_imagenes_de_los_decks.
Se accede mediante store/settings.rs (get, set, upsert) y, desde la pantalla, los comandos genéricos app_setting_get / app_setting_set (src/lib/ipc.ts). Claves vistas en el código:
| Clave | La escribe | Significado |
|---|---|---|
last_deck_id |
src/stores/deck-store.ts |
Mazo que se abre al arrancar. |
generic_back_blob |
BlobStore::generic_back |
Hash del reverso genérico; lo protege de la recolección. |
blob_refs_v1 |
decks::rellenar_refs |
Marca que blob_refs ya se rellenó. |
carpeta_importar, carpeta_exportar
|
commands/carpetas.rs |
Última carpeta usada por los diálogos de fichero de importar / exportar (se guardan por separado). |
mesa.tapete.<id del deck>, mesa.tapete
|
mesa/tapete.rs |
El tapete de la mesa de juego de un deck, o el general: JSON { blob, velo, desenfoque }. La clave del deck se borra en decks::delete. |
mesa.dados |
mesa/dados.rs |
El aspecto de los dados y la moneda del jugador (AspectoDados, JSON). |
red.apodo, red.avatar
|
red/identidad.rs |
El nombre del equipo al pasar decks (vacío: el del sistema, pasar::nombre_equipo) y la huella del blob del avatar. |
amigos.activo, amigos.cursor, amigos.hola, amigos.codigo
|
amigos/ |
Amigos: si la persona aceptó usar el servidor ("1"), el cursor de sync, la huella del último hello y el código de amigo de este equipo. Los tres últimos se vacían al salir o al importar otra identidad (Amigos y mensajes). |
Otras preferencias de la interfaz (tema, skin, idioma, tamaños de paneles, vista de la biblioteca) no están aquí; son claves de localStorage con el prefijo proxybuilder. (por ejemplo proxybuilder.lang, proxybuilder.theme, proxybuilder.preferencias, proxybuilder.biblioteca), véanse Ajustes y shell y Temas y skins.
El mazo abierto vive en el store de zustand useDeck (deck, guardado, errorGuardado, version, pasado, futuro). Véase también Stores de estado.
-
Un único punto de entrada. Todo cambio del usuario pasa por
aplicar(cambio): empuja una instantánea (cards,settings,defaultBack; no el nombre ni el texto de la lista) apasado(con topeMAX_HISTORIAL = 100), vacíafuturo, poneguardado = "dirty"y subeversion.setCartasysetAjustesson envoltorios. Ninguna pantalla llama adeck_savepor su cuenta. -
Cambios que no se pueden deshacer.
setSinHistorial(nombre,decklistText) marca como sucio sin historial.refrescarCartas(antes, nuevas)sustituye cartas (por ejemplo miniaturas de diseños regeneradas) sin historial y solo para las cartas que siguen siendo el mismo objeto que enantes, de modo que una edición concurrente del usuario gana. -
Deshacer/rehacer.
deshacer/rehacerintercambian instantáneas entrepasadoyfuturoy marcan como sucio (así que deshacer también se guarda).cargar(deck)reinicia el historial. -
Autoguardado.
iniciarAutoguardado(retardoMs = 800)(se inicia ensrc/features/shell/use-deck-inicial.ts) se suscribe a los cambios deversionmientras está sucio y retarda un temporizador de800 ms. Solo hay un guardado en curso a la vez: si llega otro cambio durante el guardado, encadena otro guardado después con el último mazo. Tras guardar, el estado pasa a"saved"solo siversionno se movió entretanto; si no, sigue en"dirty". Si falla, el estado es"error"(conerrorGuardadofijado); el siguiente cambio vuelve a armar el guardado. -
Volcado inmediato.
guardarAhora()guarda al instante si no está ya guardado; se llama antes de abrir otro mazo, de duplicar o exportar desde la biblioteca y antes de que se instale una actualización de la app. -
Arranque.
abrirDeckInicial(nombre)leelast_deck_id, pruebadeck_openy, si falla, recurre adeck_create; después escribelast_deck_id. -
Lado de Rust.
deck_save->decks::savereescribe el mazo entero en una transacción (véase más arriba), de modo que un guardado es atómico: o queda el mazo nuevo entero o el anterior.
Tests: src/stores/__tests__/deck-store.test.ts (cinco cambios rápidos guardan una sola vez, deshacer x3 restaura y rehacer repite, error y reintento, cambios sin historial), deck-store-abrir.test.ts.
Todo lo que sigue es relativo al app_data_dir o app_cache_dir de la plataforma (los resuelve Tauri; las rutas no están fijadas en el código). «Se puede borrar» significa que no se pierden datos de mazos; la entrada se recrea o se vuelve a descargar cuando hace falta.
| Ruta | Contenido | Lo crea | ¿Se puede borrar? |
|---|---|---|---|
library.db (+ -wal, -shm) |
Biblioteca SQLite: mazos, cartas, etiquetas, presets (incluidas las colecciones), ajustes, scryfall_cards
|
store/db.rs |
No. Borrarla borra todos los mazos. Cierra antes la app; copia los tres ficheros juntos. |
blobs/<xx>/<sha256>.<ext> |
Imágenes de los mazos (direccionadas por contenido) | store/blobs.rs |
No. Los mazos quedarían referenciando imágenes inexistentes. Usa en su lugar «Liberar espacio» de la app. |
modelos/ |
Dos modelos de OCR (text-detection.rten, text-recognition.rten, ~12 MB), descargados la primera vez que se usa el OCR |
ocr.rs |
Sí (se vuelven a descargar cuando se usa el OCR otra vez). |
temas/<id>.json, temas/fuentes/<hash>.woff2
|
Temas de tweakcn instalados (convertidos a CSS) y sus Google Fonts | temas.rs |
Sí, pero esos temas desaparecen hasta que se reinstalen. |
simbolos/ (indice.json + SVG) |
Símbolos de maná y de sets de Scryfall, renovados cuando el índice tiene más de 30 días | diseno/recursos.rs |
Sí (se vuelven a descargar). |
fuentes/<sha256>.<ttf|otf>, fuentes/licencias/
|
Letras importadas por el usuario o del catálogo de letras libres, con sus licencias |
diseno/recursos.rs, diseno/letras_libres.rs
|
Solo si aceptas que los diseños que las usan recurran a otra letra. Las letras importadas por el usuario no se pueden recuperar. |
marcos/<id>/ |
Packs de marcos: original (marcos de Card Conjurer, descargado), packs descargables como Balatro y packs importados por el usuario |
diseno/original.rs, diseno/descargables.rs, diseno/recursos.rs
|
Los descargados: sí (se vuelven a descargar y se comprueba su SHA-256). Los packs importados por el usuario: no hay copia en ningún otro sitio. |
packs-extras/<id>/ |
Contenido extra de los packs descargables (reversos, fichas, cartas hechas), guardado aparte del pack | diseno/descargables.rs |
Sí (se vuelve a descargar). |
partidas/actual.json.gz |
La partida en curso de la mesa (PartidaGuardada): versión del catálogo, configuración, decks preparados, partida inicial, eventos, hora del guardado y el marcador del «al mejor de», como JSON en gzip escrito de forma atómica. Se escribe al abrir la partida, cada 50 eventos (CADA), a los 20 s con eventos sin guardar (CADA_MS) y cuando se fuerza (mesa_guardar, que la pantalla llama al salir de la pestaña o cerrar la ventana; mesa_salir con guardar; mesa_siguiente_partida). No lo escribe un invitado en red (la partida la guarda el anfitrión) ni durante una repetición. Lo borra mesa_salir sin guardar (no al salir de una repetición). Las partidas terminadas van a la tabla partidas
|
mesa/guardada.rs, commands/mesa.rs
|
Sí, pero se pierde la partida en curso. Un fichero de un catálogo más nuevo se rechaza con Unsupported (motivo: version). |
partidas/imagenes/<xx>/<sha256>.jpg |
Copias de las imágenes propias de la mesa que pintan las repeticiones del historial de partidas (tabla partida_imagenes), escritas tras guardar una partida terminada; una imagen que falte en la caché (app_cache_dir/mesa/) se repone desde aquí |
mesa/imagenes.rs (guardar_para_historial, servir, olvidar_de_historial), store/partidas.rs
|
Solo junto con las partidas del historial: sin ellas esas repeticiones salen con cartas vacías. Cada copia se borra con la última partida que la usa. |
identidad/clave |
La clave de iroh de la instalación (32 bytes, ed25519, permisos 0600, escrita de forma atómica): la identidad del jugador en red y en el servidor de amigos (firma sus peticiones y da la clave de cifrado de los mensajes, ver Amigos y mensajes). Al importar una identidad, la clave anterior se queda en identidad/clave.anterior. En Android va sellada: identidad/clave.sellada guarda la clave cifrada con una clave AES-GCM del Android Keystore (Cofre, android::cofre), así que una copia del fichero no sirve fuera del móvil. Una clave que se encuentra sin sellar se sella al arrancar, y el fichero en claro solo se borra tras comprobar que lo sellado se abre y da la misma clave; si el Keystore falla se queda como estaba, para no perder la identidad (los amigos dependen de ella). En el escritorio sigue en un fichero con permisos solo del usuario. |
red/identidad.rs |
Solo si aceptas ser un jugador nuevo (los amigos tendrían que volver a añadirte). Expórtala antes (Ajustes > Mesa de juego) para conservarla. |
amigos/textos-avisos.json, amigos/avisados.json
|
Solo Android: las plantillas de los avisos que mandó la pantalla la última vez (amigos_textos) y los ids de los mensajes pendientes ya avisados, para la comprobación con la app cerrada |
amigos/fondo.rs |
Sí: las plantillas vuelven al abrir la app; como mucho, un mensaje pendiente avisa una vez más. |
amigos/alias.json |
Solo Android: los nombres puestos a los amigos, para el aviso con la app cerrada | amigos/fondo.rs |
Sí: se vuelve a escribir con el siguiente cambio de un nombre. |
perfiles/<nombre>/ |
Otra carpeta de datos entera para una instancia de debug (PB_PERFIL; nunca en release) |
red/perfil.rs |
Sí. |
letras-cjk/<código>.ttf, letras-cjk/Noto-CJK-OFL.txt
|
Noto Sans JP/KR/SC/TC para las cartas diseñadas en japonés, coreano y chino (ja, ko, zhs, zht; unos 10–18 MB cada una) y su licencia OFL, bajadas la primera vez que un deck usa uno de esos idiomas de diseño |
diseno/letras_cjk.rs, diseno/recursos.rs (guardar_cjk, leer_cjk) |
Sí (se vuelven a bajar y se comprueba su SHA-256 al elegir otra vez el idioma; mientras, esas cartas enseñan el aviso de que faltan letras). |
iconos/elegidos.json |
Los iconos de Iconify que la persona eligió para algo guardado (el icono de una etiqueta), con sus datos SVG, para verlos sin red | iconos.rs |
Sí, pero los iconos elegidos vuelven a necesitar la red para verse. |
| Ruta | Contenido | Lo crea | ¿Se puede borrar? |
|---|---|---|---|
renders/ (+ t/, d/, c/, b/, m/) |
Miniaturas derivadas, vistas previas del diseñador e imágenes reducidas de la mesa de juego |
render.rs, protocol.rs, diseno/vista.rs, mesa/imagenes.rs
|
Sí (botón en el panel de la lista). |
mesa/ (<xx>/<sha256>.jpg, s/<id>-<cara>.jpg, i/<clave>.json) |
Caras de las cartas de la mesa de juego preparadas a 745 px (ANCHO_MESA, JPEG 85), impresiones de Scryfall, tapetes reducidos a 1920 px de ancho (ANCHO_TAPETE, clave del índice t-<blob>; el tapete en sí sigue en blobs/) y el índice de clave de preparación a imagen |
mesa/imagenes.rs, mesa/preparar.rs, mesa/tapete.rs
|
Sí (se vuelven a preparar al empezar o seguir una partida; las de las repeticiones del historial vuelven de partidas/imagenes/). |
mesa/s/<id de Scryfall>-<cara>.jpg |
Las caras de Scryfall que enseña «Originales en mi idioma» (y las que una partida en red baja de Scryfall), tal como se bajaron |
mesa/imagenes.rs, mesa/originales.rs
|
Sí (se vuelven a bajar al enseñarlas). |
mesa/r/<xx>/<sha256>.jpg |
En una partida en red, los bytes de las imágenes propias de los demás tal como llegaron (los que dan la SHA-256, para pasárselos a otro); la copia saneada que se ve va a mesa/<xx>/
|
red/imagenes.rs |
Sí (se vuelven a pedir al enseñarlas). |
scryfall-card-names.json, scryfall-set-codes.json
|
Catálogo de nombres de cartas y códigos de sets de Scryfall para el reconocimiento local del OCR, renovado cada semana | scryfall/nombres.rs |
Sí (se vuelve a descargar cuando hace falta; requiere red). |
cockatrice-tokens.xml |
Base de datos de fichas de Cockatrice (solo se usan las fichas que Scryfall no tiene), renovada cada semana | fuentes/cockatrice.rs |
Sí. |
pasar/<uuid>/deck.pbdeck |
El paquete que se envía o se recibe mientras dura un intercambio (Pasar decks); la carpeta se borra al acabar o cancelar, y lo que quede se limpia al abrir «Recibir» o al empezar un envío |
pasar/mod.rs (Temporal) |
Sí. |
fichero/ |
En Android, copias de los ficheros content:// que elige la persona (una importación se lee de una copia porque un ZIP necesita Seek) y los temporales que se escriben antes de copiar o compartir; una copia se borra al soltarla y lo que tenga más de 24 h se barre |
fichero.rs |
Sí. |
actualizacion/ |
En Android, el APK que bajó el actualizador antes de entregarlo al instalador del sistema; se borra cuando la app está al día | actualizar.rs |
Sí. |
iconos/ (colecciones.json, <prefijo>.json) |
El catálogo de Iconify del selector de iconos (la lista de colecciones y los nombres de cada una), renovado a los 7 días | iconos.rs |
Sí (se vuelve a bajar al abrir el selector). |
mtgjson/nombres.sqlite |
Nombres de cartas sin conexión en otros idiomas (opcional), construidos a partir de MTGJSON | fuentes/mtgjson.rs |
Sí (una acción de Ajustes también lo elimina). |
perfiles/<nombre>/ (+ webview/) |
La caché de una instancia de debug (PB_PERFIL), con su propia carpeta de datos de la vista web para que las instancias no compartan localStorage
|
red/perfil.rs, lib.rs (crear_ventana) |
Sí. |
- Fichero de registro
proxy-builder.logen elapp_log_dirde Tauri (registro.rs): ahí se registra cada petición de red, para poder comprobar que abrir un mazo guardado no descarga nada. - La geometría de la ventana la guarda
tauri-plugin-window-state(registrado enlib.rs, con el indicador de visibilidad excluido). Deducido: escribe en el directorio de configuración de la app; no se lee en este repo. - La gestión del paquete descargado por el actualizador y de su firma se describe en Versiones y actualizaciones automáticas.
De un intercambio no se guarda nada aparte de los decks que crea y su linaje (deck_linaje). El código, su secreto y el anuncio en la red local viven en memoria 15 minutos; el paquete en tránsito está en app_cache_dir/pasar/. La clave de iroh que usa es la de la instalación (identidad/clave), la misma que en el juego en línea.
-
app_settings:red.apodo(apodo) yred.avatar(la huella de un blob, protegida con una fila deblob_refsde tipored/avatar). El aspecto de los dados esmesa.dados(su imagen,mesa/dados). - El servidor de amigos guarda claves públicas, amistades y mensajes cifrados hasta que se confirman; las conversaciones descifradas solo viven en esta base de datos (
amigos_mensajes,amigos_claves, migración 0010). Ver Amigos y mensajes.
| Situación | Resultado |
|---|---|
Guardar un mazo cuya carta referencia una imagen que no está en blobs
|
InvalidInput (motivo = blob_missing, blob = <hash>); el estado del autoguardado pasa a "error". |
| Dos cartas con el mismo id en un mazo |
InvalidInput (motivo = duplicate_card_id). |
| Bytes de imagen no admitidos o corruptos |
Unsupported / Image (motivo = corrupt). |
| Imagen de más de 12000 px por lado |
Image (motivo = too_large). |
blob_gc mientras corre un trabajo o hay una partida abierta |
Busy. |
optimizar_lanzar mientras corre un trabajo o hay una partida abierta |
Busy; no cambia nada. |
| JSON guardado que ya no se deserializa |
Db (con detalle). |
| Mutex de la base de datos envenenado |
Internal. |
Los códigos de error se definen en src-tauri/src/error.rs; véase IPC y manejo de errores.
- Columna persistida nueva: añade
000N_*.sql, añádelo aMIGRACIONESy no edites nunca los ficheros antiguos. Recuerda que el valor por defecto deALTER TABLE ... ADD COLUMNdebe valer para las filas existentes. - Datos nuevos que referencian una imagen: guarda su hash en un lugar protegido (véase la lista de la recolección) y añádelo a
refs_del_deck/CardDesign::blobs(). - Recurso descargado nuevo: elige
app_data_dirsi las funciones dependen de que siga ahí,app_cache_dirsi se puede recrear por completo; escribe conescribir_atomico.
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
- Rename-to-Tapete · Cambio de nombre a Tapete
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- The-Card-Stamp · El sello de las cartas
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
- Game-Engine · Motor de la mesa
- Game-Table · Mesa de juego
- Online-Play · Juego en línea
- Friends-and-Messages · Amigos y mensajes
Mobile · Móvil
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Icons · Iconos
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Card-Search · Búsqueda de cartas
- Set-Browser · Explorador de sets
Reference · Referencia