Skip to content

release: proveedor 3.0.1 (esquema jellytrend completo y documentacion de la integracion) - #6

Merged
BORNIOS merged 16 commits into
mainfrom
release/3.0.1
Sep 19, 2026
Merged

BORNIOS merged 16 commits into
mainfrom
release/3.0.1

Conversation

@BORNIOS

@BORNIOS BORNIOS commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Qué publica este PR

Versión 3.0.1 del proveedor: corrección de la integración con JellyTrend.

  • Fix: el esquema jellytrend no creaba la tabla donde JellyTrend guarda el perfil de gustos aprendido (user_affinity), así que ese perfil no llegaba a almacenarse. El esquema se crea ahora completo (versión 4) y se comprueba al arrancar, de modo que una instalación 3.0.0 se corrige sola, sin recrear nada.
  • Docs: la sección «Integración con JellyTrend» documenta las dos piezas del contrato: almacén de datos en el esquema jellytrend (fuera de public, para que no entre en las exportaciones a SQLite) y acelerador de consultas con SQL nativo en las recomendaciones (4-10×).
  • Versión: Directory.Build.props y build.yaml pasan a 3.0.1.0. El targetAbi se mantiene en 12.1.0.0.

Verificación

Prueba Resultado
CI Verificación (Ubuntu 24.04, SDK 10.0.401) 92/92 correctas, 0 con error, 0 omitidas, con las 11 pruebas de PostgresIntegrationTests corriendo contra un servicio PostgreSQL 17 real. El resumen generado por la CI reporta versión 3.0.1.0 y Jellyfin 12.1.0
dotnet test local (Debug, sin connection string) 73 superadas, 0 con error, 19 omitidas (las de integración)
Servidor real Jellyfin 12.1.0 con PostgreSQL 17.11 esquema jellytrend con sus 9 tablas y versión 4; JellyTrend reporta Backend: PostgreSQL 17.11, esquema jellytrend
Catálogo la rama ya contiene las entradas 1.0.0.0, 2.0.0.0, 2.0.1.0 y 3.0.0.0, así que este merge no borra ninguna versión publicada

Release

El manifest.json no se edita a mano: lo genera el workflow release.yml al publicar (checksum MD5, sourceUrl y entrada replicada en main, que es el catálogo que lee Jellyfin).

Orden de publicación: este release va antes que JellyTrend 3.0.0, que consume este almacén.

Jorge Chávez Bornios and others added 16 commits September 17, 2026 21:07
Las cuatro consultas de PostgresRecommendationQueryProvider filtraban por BaseItems."Type" = 'Movie',
pero Jellyfin guarda ahi el nombre del tipo CLR (el discriminador de EF):
MediaBrowser.Controller.Entities.Movies.Movie. Medido en una base real: 0 filas con el nombre corto y
425 con el nombre CLR, asi que el proveedor no devolvia candidatos nunca.

- Un unico criterio (MovieTypeFilter) usado por las cuatro consultas, que acepta las dos formas
  ("Type" = 'Movie' OR "Type" LIKE '%.Movie'), para no repetir el mismo literal ni depender de la
  convencion del campo.
- Verificado contra la base local: 425 filas.
- Suite: 63 correctas, 0 con error, 11 omitidas (integracion sin POSTGRES_TEST_CONNECTION).
La copia local del contrato (JellyTrendContract/) y la proyeccion que devuelve son el contrato de
facto: JellyTrend la encuentra por nombre y lee las propiedades por nombre.

- JellyTrendContractTests: nombre completo del contrato, las seis firmas, las siete propiedades y que
  la implementacion sea construible por reflexion (publica y sin parametros o con un unico ILogger).
- Gemela en el repo de JellyTrend: las dos versiones deben avanzar alineadas.
- Suite: 67 correctas, 0 con error, 11 omitidas (integracion sin POSTGRES_TEST_CONNECTION).
EF Core llama a Initialise mas de una vez durante el arranque y cada llamada contaba tres lineas
(Initialise, conexion tuneada y pool), asi que el log de arranque salia duplicado.

- LogPipeline: la primera vez en INFO, las repeticiones en DEBUG.
- Suite: 67 correctas.
Dos cosas distintas que se veian juntas en el mismo log.

Escritura: cuando Jellyfin cree que un item es nuevo pero la fila ya existe en la base, el INSERT
duplicado hacia fallar todo el lote con 23505. Eso rompia la creacion de items de canal (por eso el
canal de recomendados mostraba solo dos) y la conversion de imagenes. BaseItems entra en la lista de
tablas cuyo conflicto se ignora, igual que las tablas de union; la fila que ya estaba se conserva y
las claves foraneas de las tablas hijas siguen apuntando a ella.

ItemValues NO se ignora a proposito: ItemValuesMap tiene clave foranea hacia esa tabla, asi que
saltarse la fila dejaria el mapa apuntando a un valor inexistente. Hay una prueba que lo documenta.

Arranque: EF Core llama a Initialise mas de una vez y cada llamada contaba tres lineas, con la cadena
de conexion repetida (y otra vez en Modo activo). Ahora se informa una sola vez: una linea de
pipeline, una del pool, una de compatibilidad y la conexion solo en Modo activo.

- 2 pruebas nuevas del interceptor. Suite: 69 correctas.
…y DbUpdateConcurrencyException)

La cache de consultas no invalidaba nada y además guardaba las respuestas de
consultas ejecutadas dentro de una transaccion. Jellyfin lee una fila dentro de su
propia transaccion para decidir entre INSERT y UPDATE, y ademas serializaba los
arreglos de parametros con ToString(), con lo que TODAS las listas de ids
compartian una sola entrada de cache.

Consecuencias medidas en un escaneo completo: 100 fallos 23505 en
IX_ItemValues_Type_Value (el lote entero se descartaba, items de canal incluidos)
y 9 DbUpdateConcurrencyException en BaseItems (UPDATE sobre filas que no existian
porque la transaccion que las inserto se revirtio y la cache las seguia sirviendo).

- HomeQueryCacheInterceptor: estado compartido entre instancias, sin cache dentro
  de transacciones, invalidacion por generacion en cada escritura, arreglos
  serializados elemento a elemento y marcador __jellyfin_no_cache.
- ItemValueReuseInterceptor: reutiliza la fila existente de ItemValues cuando
  Jellyfin va a insertar un valor que ya esta guardado, y repunta ItemValuesMap a
  esa fila (los mapas se repuntan antes de desasociar el valor, porque EF propaga
  el detach en cascada).
- UpsertConflictInterceptor: ItemValues se ignora solo ante el conflicto real
  (Type, Value) y el INSERT del mapa pasa a ser condicional para no dejar la clave
  foranea apuntando a una fila inexistente.
- Se retira el registro DI hacia JellyTrend: identidad de tipos distinta por el
  espejo del contrato, el plugin que necesita el dato lo busca.
- schema.sql: el indice unico de ItemValues coincide ya con el modelo real.

Verificado con 92 pruebas (incluidas las de integracion contra PostgreSQL real).
… workflow

El ABI estaba escrito a mano dentro del jq del manifest y tambien en el env del paso que replica la entrada en main, asi que publicar otra linea (10.11) obligaba a editar el workflow. Ahora el paso de version lee targetAbi de build.yaml (fuente de la rama), falla si no lo encuentra y lo pasa a los dos jq: la misma receta publica 10.11 o 12.1 cambiando solo build.yaml.
JellyTrend guardaba su cache de caracteristicas, la lista de tendencias, las recomendaciones por usuario, los elementos suprimidos y el historial de corridas en archivos JSON sueltos. Este plugin implementa ahora el contrato que JellyTrend declara (mirror en JellyTrendContract/) contra un esquema propio.

- Esquema jellytrend, fuera de public: el export a SQLite solo enumera public, asi que estas tablas nunca viajan a un .db donde Jellyfin las abriria sin sus datos.

- Nada se crea hasta que JellyTrend llama EnsureSchema: un servidor sin ese plugin no recibe tablas que no va a usar.

- Facetas y entradas de tendencias como jsonb; el documento de tendencias y el volcado de facetas los arma PostgreSQL (jsonb_build_object / jsonb_object_agg) en vez de serializar a mano.

- Ningun metodo lanza: ante un fallo degrada a vacio y lo registra, para que JellyTrend siga con JSON sin romper la tarea.
La exportacion ya las dejaba fuera porque GetUserTablesAsync solo enumera el esquema public y estas tablas viven en jellytrend. Se anade JellyTrendTables como segunda red: si alguna vez acabaran en public, tampoco viajarian a un SQLite que Jellyfin no espera.
user_item_consumption guarda como consume cada usuario cada titulo (un documento jsonb por par usuario+item, con su marca de tiempo) y el esquema sube a la version 2. La creacion sigue siendo a demanda: un servidor sin JellyTrend no recibe la tabla.
Tabla user_affinity_pair para las afinidades combinadas y ReplaceAffinities/GetAffinities en el contrato: los valores sueltos van a user_affinity y los pares a user_affinity_pair, en una sola transaccion. GetAffinities devuelve el perfil como array JSON, que es justo lo que JellyTrend ya sabe deserializar. Esquema version 3.
user_affinity estaba en el diseno pero no en el DDL: la escritura del perfil fallaba en silencio (0 filas) mientras el consumo si se guardaba. Se anade la tabla y el esquema sube a la version 4; EnsureSchema la crea en el proximo arranque sin tocar lo existente.
El README describia solo la mitad de la integracion: decia que el motor de
recomendaciones usa SQL nativo optimizado (4-10x), que sigue siendo cierto, pero
callaba que desde JellyTrend 3.x este plugin tambien le hace de almacen. Un
administrador que abra su base se encuentra un esquema jellytrend que ninguna parte de
la documentacion mencionaba.

Ahora la seccion explica los dos roles en una tabla (almacen de datos y acelerador de
consultas) y anade lo que hay que saber del esquema: que vive fuera de public a
proposito y por que (la exportacion a SQLite enumera public, asi que esas tablas nunca
acaban en el archivo; y el Health Check y las estadisticas siguen analizando solo las
tablas de Jellyfin), que se crea la primera vez que JellyTrend lo pide y que un
servidor sin JellyTrend no lo ve, que la version del esquema es la v4, y que los JSON
de {DataDir}/data/JellyTrend quedan como copia de cortesia. La linea de caracteristicas
resume las dos cosas.

Las notas de la 3.0.0, en espaniol e ingles, decian que la integracion sigue disponible
"solo" por IRecommendationQueryProvider.cs; ahora nombran las dos piezas.

Verificado antes de tocar nada: los dos README del proveedor no tienen referencias
locales rotas (documentos, las cinco capturas de las pestanias, el logo y la licencia);
el acelerador de consultas sigue vivo en JellyTrend 3.x (CandidateSource y ProviderState
lo consumen y DatabaseBackendStartupService lo prueba con GetRandomUnwatchedMovies), asi
que la cifra 4-10x se mantiene; las diez plantillas de la consola SQL son diez; y el
esquema jellytrend tiene nueve tablas con CurrentSchemaVersion = 4.
@BORNIOS
BORNIOS merged commit b389f63 into main Sep 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant