🔄 Atualizador#
O sistema de atualização do Codis (src/updater/) é modular e usa apenas a stdlib: faz backup antes de qualquer mudança, aceita cancelamento a qualquer momento e restaura o backup se algo falhar. O diálogo de atualização (src/updater/dialog.py, aberto por open_update_dialog) mostra progresso, changelog e a escolha do modo.
Modos de Atualização#
Todos os modos passam por UpdateManager.update(mode) e devolvem um UpdateResult.
| Modo | O que faz |
|---|---|
CHECK_ONLY |
Consulta a API do GitHub e compara a versão instalada com a mais recente. Nenhum arquivo é modificado (usado na verificação silenciosa da inicialização) |
SMART |
Usa a Compare API do GitHub para baixar só os arquivos que mudaram entre as duas versões; cai para ZIP se a Compare API falhar |
ZIP |
Baixa o ZIP completo da release e sobrescreve a instalação atual |
REINSTALL |
Remove os arquivos não protegidos e reinstala do zero via ZIP (corrige instalações corrompidas) |
FORCE |
Igual a REINSTALL, mas ignora a checagem de versão: reinstala mesmo já estando na última versão |
BINARY |
Substitui o executável PyInstaller. Em Linux/macOS usa rename atômico; no Windows agenda a troca por um script .bat após o reinício. Só funciona em builds empacotados |
Os estados possíveis de um resultado (UpdateStatus) são UP_TO_DATE, UPDATE_AVAILABLE, SUCCESS, PENDING_RESTART, ERROR e CANCELLED.
Arquitetura#
| Componente | Arquivo | Responsabilidade |
|---|---|---|
UpdateManager |
manager.py |
Orquestra os modos. Expõe check(), update(mode, *, target_tag="", dry_run=False), cancel(), run_in_thread(...) e restart_app(). Thread-safe via threading.Lock |
UpdateChannel |
channel.py |
Toda a comunicação com a API do GitHub, só com urllib. Aceita token pessoal (PAT) para elevar o rate limit |
BackupManager |
backup.py |
Snapshot do diretório do app antes de modificar, restauração em falha e limpeza após sucesso. Funciona como context manager |
UpdateResult / ReleaseInfo / UpdateMode / UpdateStatus |
result.py |
Dataclasses e enums. UpdateResult.ok e .update_available são propriedades calculadas |
UpdateDialog |
dialog.py |
Interface Tkinter com visual consistente com o K-UI |
Proteção de Dados#
Os caminhos em _PROTECTED (manager.py) são verificados antes de qualquer escrita ou remoção; nenhum modo os modifica.
| Caminho protegido | Conteúdo |
|---|---|
config.json |
Configurações do usuário |
.codis |
Diretório de dados do editor |
session.json |
Estado da sessão (arquivos abertos) |
keybindings.json |
Atalhos customizados |
plugins |
Plugins instalados |
themes |
Temas customizados |
snippets |
Snippets personalizados |
logs |
Arquivos de log |
API#
Verificação silenciosa na inicialização (não bloqueia a UI; roda em thread daemon):
from src.updater import UpdateManager, UpdateMode
from src.constants import APP_VERSION
mgr = UpdateManager(APP_VERSION)
def _on_result(result):
if result.update_available:
self.after(0, lambda: self._notify_update(result))
mgr.run_in_thread(mode=UpdateMode.CHECK_ONLY, callback=_on_result)
Atualização com progresso:
def _on_progress(message: str, percent: float) -> None:
progressbar.set(percent)
status_label.config(text=message)
mgr = UpdateManager(
current_version="1.0.0",
github_token=config.get("github_token", ""),
on_progress=_on_progress,
)
result = mgr.update(UpdateMode.SMART)
if result.ok and result.restart_required:
if messagebox.askyesno("Reiniciar?", "Atualização aplicada. Reiniciar agora?"):
UpdateManager.restart_app()
Simulação (dry_run), sem modificar nada:
result = mgr.update(UpdateMode.SMART, dry_run=True)
print(f"Arquivos que seriam atualizados: {result.files_updated}")
BackupManager como context manager:
from src.updater.backup import BackupManager
from pathlib import Path
with BackupManager(Path("/caminho/para/codis")) as bm:
bm.create(label="pre-update")
# Se uma exceção ocorrer aqui, restore() é chamado automaticamente
perform_risky_operation()
# Sem exceção -> cleanup() remove o backup temporário
Segurança: Backup → Aplicar → Confirmar → Limpar#
- Backup automático:
BackupManager.create()copia o diretório do app para um diretório temporário (sem__pycache__nem.pyc). - Aplicação com checagem de cancelamento: o evento
_cancelledé verificado a cada arquivo; se cancelado, a operação para e o backup é restaurado. - Confirmação: só após todas as operações terminarem,
backup.cleanup()remove o snapshot. - Rollback em falha: qualquer exceção dispara
restore()+cleanup()e o erro viraUpdateStatus.ERROR.
Configuração#
Sem token, a API do GitHub permite 60 requisições por hora por IP; com um PAT, o limite sobe para 5.000 por hora.
Chave em config.json |
Tipo | Padrão | Descrição |
|---|---|---|---|
github_token |
string | "" |
PAT do GitHub. Vazio = sem autenticação |
auto_check_updates |
bool | true |
Verifica silenciosamente ao iniciar |
Testes#
tests/test_updater.py (87 casos) roda 100% offline: o canal é mockado com unittest.mock e os ZIPs são gerados em memória.
pytest tests/test_updater.py -v
| Classe de teste | Casos |
|---|---|
TestReleaseInfo |
6 |
TestUpdateResult |
10 |
TestVersionTuple |
7 |
TestFmtBytes |
4 |
TestIsProtected |
5 |
TestCheck |
6 |
TestUpdateSmart |
7 |
TestUpdateZip |
6 |
TestUpdateReinstall |
4 |
TestUpdateForce |
2 |
TestTargetTag |
2 |
TestCancel |
2 |
TestRunInThread |
3 |
TestBackupManager |
8 |
TestUpdateChannelHeaders |
11 |
TestOverwriteFromDir |
2 |
TestClearPycache |
2 |
Veja também Builds & Empacotamento e Arquitetura.