Fontes Remotas (Komga e Kavita)

Além de pastas locais (SAF), o InkShelf conecta a servidores Komga e Kavita self-hosted do usuário. É uma capacidade opcional: quem nunca conecta um servidor tem exatamente o app 100% local descrito em 00 - Visão do Produto; quem conecta ganha bibliotecas remotas convivendo lado a lado com as pastas locais na mesma árvore de navegação, filtros, busca, favoritos e estatísticas.

Conectar uma fonte remota exige assinatura (Assinatura) — trava só a conexão de uma fonte NOVA (ou anexar mais bibliotecas a uma já existente); fontes já conectadas antes de existir essa trava continuam sincronizando e lendo normalmente.

Este documento cobre o que é compartilhado entre os dois provedores — modelo de dados, fluxo de conexão, credenciais, sincronização de progresso/favoritos, disponibilidade/reconexão, e a decisão de ações em massa não sincronizarem. Detalhes específicos de API/endpoint de cada um ficam em documentos próprios: Komga e Kavita.

Modelo de dados

Cada servidor conectado é uma linha em remote_sources (RemoteSourceEntity): id, type (KOMGA/KAVITA), baseUrl. Múltiplos servidores do mesmo tipo coexistem — dois servidores Komga diferentes são duas fontes distintas, cada uma com sua própria credencial e suas próprias bibliotecas selecionadas.

Cada biblioteca escolhida dentro de uma fonte é uma linha em remote_libraries (RemoteLibraryEntity, chave composta sourceId+remoteLibraryId), com suas próprias opções (organization, rootMode, hqCovers — ver abaixo). As opções são por biblioteca, não por servidor: duas bibliotecas do mesmo servidor podem ter organizações diferentes.

Arquivos que vêm de uma fonte remota usam os mesmos FileEntity/FolderEntity da biblioteca local, com campos extras preenchidos só para eles: remoteSourceId (qual fonte), remoteLibraryId (qual biblioteca), localRootUri (raiz local de onde veio, null para remotos), localProgressUpdatedAt (carimbo de última mudança local — ver "Progresso de leitura"). Cada provedor também tem seus próprios IDs de item — komgaBookId pro Komga; remoteItemId/remoteVolumeId/remoteSeriesId pro Kavita — ver Komga e Kavita pro porquê de cada um.

Adicionar uma fonte

Um único diálogo (AddLibrarySourceDialog, "Adicionar pasta") atende os três casos — pasta local, servidor Komga, servidor Kavita — e é reaproveitado em três lugares: Configurações → Bibliotecas, os estados vazios do Acervo/Início, e o passo final do Onboarding. Para servidor, o fluxo tem 3 passos: conectar (URL + credencial) → escolher bibliotecas → opções de organização.

Se a URL testada já bate com um servidor já conectado, o app não duplica a fonte — anexa as bibliotecas novas escolhidas à fonte existente (bibliotecas já adicionadas aparecem desabilitadas na lista). "Adicionar biblioteca" direto do card de uma fonte (Configurações) pula os passos de login e escolha de URL, reaproveitando a credencial já salva.

Autenticação e credenciais

Cada provedor tem seu próprio método (Komga aceita Basic Auth ou API key; Kavita usa uma Auth Key — ver Komga e Kavita), mas o armazenamento é o mesmo pros dois: a credencial nunca fica em texto plano. RemoteCredentialStore cifra com AES/GCM usando uma chave própria por sourceId, gerada e mantida no Android Keystore (nunca sai do hardware/TEE) — só o ciphertext e o IV ficam em SharedPreferences comum. Se a chave do Keystore ficar inválida (restore de backup, troca de conta em certas OEMs), a leitura falha de forma segura: trata como credencial perdida e limpa o resíduo cifrado, sem derrubar o app.

Opções por biblioteca

  • Organização (FOLDERS vs SERIES): como a hierarquia do servidor vira pastas no InkShelf — por pasta real do servidor, ou por série (uma pasta por série, arquivos soltos dentro).
  • Modo de raiz (MERGE vs LIBRARY_FOLDER): se o conteúdo aparece direto na raiz do acervo, ou dentro de uma pasta com o nome da biblioteca. Existe pros dois provedores.
  • Capas HQ (hqCovers): exclusivo do Komga — ver Komga.

Mudar qualquer opção dispara uma nova sincronização daquela fonte pra reaplicar. Detalhes de como cada provedor mapeia sua própria hierarquia pra essas opções: Komga e Kavita.

Sincronização

KomgaScanner/KavitaScanner cumprem o mesmo papel do LibraryScanner local (ver Scanner) — descobrem o que existe no servidor — e o merge com o que já está no Room segue a mesma filosofia do SyncEngine: campos vindos do servidor são atualizados, estado do usuário (favorito, oculto, capa customizada, progresso) é preservado, nunca zerado por um resync simples.

Bibliotecas remotas grandes rodam num serviço em 1º plano (KomgaSyncService/KavitaSyncService) com notificação de progresso. Detalhes de cliente/endpoint por provedor: Komga e Kavita (esta última também explica a barra de progresso baseada em contagem de séries).

Capas

Capas de itens remotos não vêm de extração local (ver Sistema de Capas) — o CoverLoader busca da própria API do servidor, um endpoint diferente por provedor (ver Komga e Kavita). Precisam de rede na primeira busca; depois de baixadas, ficam no mesmo cache em disco das capas locais.

Progresso de leitura bidirecional

Ler um item remoto no InkShelf empurra o progresso pro servidor (KomgaProgressSync/KavitaProgressSync, debounce de 1200ms por arquivo — passando por RemoteNetworkGate, serializado/espaçado/com retry, ver "Ações em massa" abaixo); ler ou marcar como lido/não lido direto no Komga/Kavita reflete de volta no InkShelf no próximo resync. Pra evitar que um resync reverta progresso feito no app segundos antes de ser confirmado no servidor, o merge só deixa o servidor "vencer" um valor mais baixo depois de uma janela de alguns minutos sem mudança local (FileEntity.localProgressUpdatedAt) — dentro da janela, o maior valor vence, como proteção. Endpoint exato e a diferença de como cada provedor deriva/desmarca "lido": Komga e Kavita.

Favoritos bidirecionais

Nem Komga nem Kavita têm favoritos nativos por item (só "Want to Read" por série inteira, granularidade grossa demais). Os dois usam o mesmo substituto: uma Reading List no servidor chamada "Favoritos" (ou variantes reconhecidas do nome — "favorite", "favourites" etc. — pra adotar uma lista que o usuário já tinha manualmente). Favoritar/desfavoritar um item local agenda um push que reconcilia essa lista com o estado local; puxar da lista no pull nunca desfavorita por conta própria (só soma). A API de reading list difere bastante entre os dois provedores (incremental de verdade no Kavita, substituição completa no Komga) — ver Komga e Kavita.

Disponibilidade e reconexão

Cada card de fonte remota (Configurações → Bibliotecas) mostra um "led" — verde se o servidor respondeu numa verificação leve (ping) nos últimos 30s, cinza caso contrário. É puramente informativo, não afeta sync nem download.

O botão "Reconectar" no card reusa a credencial já salva (nunca pede nada de novo ao usuário) e testa a conexão na hora, sem esperar o próximo poll — útil quando o servidor estava fora do ar e acabou de voltar. Se a conexão passar, dispara uma sincronização.

Ações em massa não sincronizam com o servidor

Marcar uma pasta inteira ou uma seleção múltipla de itens como lida/não lida fica só local — não é enviado ao Komga/Kavita. Só o toggle de um item por vez (fora de seleção múltipla) sincroniza. Essa é uma decisão deliberada: operações em massa contra bibliotecas grandes causavam queda de conexão com o servidor de forma consistente e reprodutível (mesmo sintoma no Komga e no Kavita, mesmo sem VPN/proxy no meio), e a causa raiz nunca foi diagnosticável sem acesso aos logs do próprio servidor. Favoritar em massa não tem essa restrição — não gera 1 requisição por item.

Mesmo o toggle individual, que continua sincronizando, passa por RemoteNetworkGate: as chamadas de push (progresso e favoritos, os dois provedores) são serializadas, espaçadas no tempo, com retry em várias rodadas e um circuit breaker que pausa a fila inteira se detectar falhas persistentes — uma camada de defesa a mais contra o mesmo tipo de instabilidade, sem depender de nunca gerar mais de 1 requisição por vez.

Downloads

Itens remotos podem ser baixados pra leitura offline; os arquivos baixados aparecem num hub próprio ("Downloads", em Configurações — visível só quando existe alguma fonte remota conectada) com a mesma lógica de favoritos/leitura das demais telas.

Remover uma biblioteca ou fonte

Remover a última biblioteca selecionada de uma fonte remove a fonte inteira (credencial incluída). Remover uma biblioteca também apaga os arquivos/pastas locais correspondentes e os caches associados a eles, mas não afeta nada no servidor em si.