Sistema de Capas

Como o InkShelf gera, armazena e prioriza o carregamento das miniaturas de capa exibidas na Biblioteca, sem que isso trave a rolagem em acervos grandes.

Extração

A extração abaixo vale pra arquivos locais. Itens Komga/Kavita não são extraídos — a capa vem pronta da API do servidor: ensureKomgaCover busca a miniatura do livro (ou a página em resolução cheia, se "capas em alta qualidade" — hqCovers, desligado por padrão — estiver ligado naquela biblioteca) e ensureKavitaCover usa GET /api/Image/chapter-cover. Precisa de rede na primeira busca de cada item; depois de baixada, a capa cai no mesmo cache em disco descrito abaixo — sem diferença de tratamento a partir daí. Ver Fontes Remotas.

A capa de um arquivo local é sempre a primeira página/imagem dele, obtida sem precisar abrir o leitor completo:

  • CBZ/ZIP: primeira imagem do arquivo, em ordem natural de nome (Página 2 vem antes de Página 10).
  • CBR (RAR4) e RAR5: mesmo princípio, com o motor de extração correspondente a cada variante do formato.
  • PDF: primeira página é renderizada via PdfRenderer nativo do Android.
  • EPUB: a imagem de capa declarada no OPF do livro é usada quando existe; se não houver, cai para a primeira imagem encontrada no HTML do primeiro capítulo — incluindo capas geradas por ferramentas como o Calibre, que embutem a imagem dentro de um elemento SVG em vez de uma tag <img> comum.

O bitmap extraído é reduzido (amostragem por potência de 2 na decodificação, depois redimensionado) e salvo como JPEG no cache do app, em cache/covers/{id-do-arquivo}.jpg. O arquivo original nunca é modificado.

Capa de pasta

Uma pasta não tem página própria para extrair capa, então sua miniatura vem de um arquivo dentro dela:

  • Por padrão, é o primeiro arquivo encontrado ao percorrer a árvore da pasta (primeiro na própria pasta; se vazia de arquivos diretos, desce nas subpastas).
  • O usuário pode fixar manualmente qual arquivo representa a pasta, pela ação "definir como capa" no menu de contexto (ver Biblioteca) — essa escolha manual tem prioridade sobre a automática.
  • Se uma pasta ainda não tem capa própria, ela herda a capa do primeiro arquivo/subpasta que a ganhar, e essa capa se propaga para cima: a pasta-mãe (e a mãe dela, e assim por diante) também adota a mesma miniatura, até encontrar uma pasta que já tem capa definida. Isso evita pastas com ícone genérico só porque seu conteúdo está um nível mais abaixo.

Carregamento e priorização

A extração de capa é um trabalho pesado (abrir um arquivo compactado, decodificar uma imagem) e a Biblioteca pode listar milhares de itens. Para não travar a rolagem nem duplicar trabalho:

  • Existe uma única fila de pedidos de capa compartilhada pelo app inteiro, consumida por um número fixo de workers em segundo plano (4 workers de I/O — rede e disco) — não importa quantas telas estejam pedindo capas ao mesmo tempo, a concorrência total é sempre limitada. A parte de CPU (decodificar, redimensionar, comprimir) roda num pool próprio de 2 threads em prioridade de background, separado do Dispatchers.IO, pra não disputar CPU de igual pra igual com a interface.
  • As gravações de coverPath em files/folders são acumuladas e aplicadas numa única transação a cada ~400 ms. Cada UPDATE isolado invalida a tabela inteira no Room e faz toda tela que observa a biblioteca reconsultar — em lote, uma rajada de capas vira uma invalidação. A tela Início ainda se protege do outro lado: seleciona as fileiras sobre projeções sem coverPath (estáveis enquanto só capas mudam) e hidrata só os itens exibidos — ver Tela Inicial.
  • A fila é LIFO, não FIFO: o pedido mais recente é atendido primeiro. Como cada card pede sua capa só quando entra na composição da tela, isso prioriza naturalmente os itens que estão sob o dedo do usuário durante um scroll longo, em vez de processar em ordem de uma fila que pode ter milhares de pedidos acumulados.
  • Pedidos duplicados (duas telas pedindo a mesma capa ao mesmo tempo) são deduplicados por chave.
  • Ao trocar de pasta, os pedidos pendentes daquela tela são descartados imediatamente — só o que já estava em execução termina.
  • Antes de qualquer extração, o worker verifica se a capa já existe em cache (linha no banco + arquivo em disco); só faz o trabalho pesado se realmente faltar.

Atualização e limpeza

  • "Atualizar capa" no menu de contexto força uma nova extração, ignorando o cache existente.
  • Limpar o cache de capas (disponível nas configurações) apaga todas as miniaturas de uma vez.
  • Miniaturas de arquivos que saem da biblioteca (apagados, movidos ou pasta raiz removida) são limpas automaticamente pelo próprio processo de sincronização — ver Scanner.