From 9729809f6f0b20046bec16f6bf801ca08d92543c Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Sat, 16 May 2026 23:04:30 -0700 Subject: [PATCH] Add the first level of file loading. --- borgmatic/actions/browse/controller.py | 29 ++++++++----- borgmatic/actions/browse/view.py | 59 ++++++++++++++++++-------- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/borgmatic/actions/browse/controller.py b/borgmatic/actions/browse/controller.py index 57299aa1..17a2addb 100644 --- a/borgmatic/actions/browse/controller.py +++ b/borgmatic/actions/browse/controller.py @@ -1,3 +1,4 @@ +import os import argparse import json import logging @@ -41,22 +42,28 @@ def get_repository_archives(config, repository): ) -def get_archive_files(config, repository, archive): +def get_archive_files(config, repository, archive_name): with borgmatic.logger.Log_prefix(repository.get('label', repository['path'])): - logger.answer(f"Listing archive {archive['archive']}") + logger.answer(f"Listing archive {archive_name}") global_arguments = argparse.Namespace() local_path = config.get('local_path', 'borg') remote_path = config.get('remote_path') local_borg_version = borgmatic.borg.version.local_borg_version(config, local_path) - return borgmatic.borg.list.capture_archive_listing( - repository['path'], - archive['archive'], - config, - local_borg_version, - global_arguments, - list_paths=(r're:^([^/]+/){1,2}[^/]+$',), # TODO - local_path=local_path, - remote_path=remote_path, + return sorted( + dict.fromkeys( + base_path + for archive in borgmatic.borg.list.capture_archive_listing( + repository['path'], + archive_name, + config, + local_borg_version, + global_arguments, + list_paths=(r're:^([^/]+/){1,2}[^/]+$',), # TODO + local_path=local_path, + remote_path=remote_path, + ) + for base_path in (archive['path'].split(os.path.sep)[0],) + ), ) diff --git a/borgmatic/actions/browse/view.py b/borgmatic/actions/browse/view.py index 7d5c7309..c3a15efa 100644 --- a/borgmatic/actions/browse/view.py +++ b/borgmatic/actions/browse/view.py @@ -58,33 +58,28 @@ async def add_repository_archives( # Reverse the archives, so the common case of accessing the latest archive is easy because it's # at the top. - browse_app.call_from_thread( - archives_list.add_options, - ( - textual.widgets.option_list.Option(archive['archive'], id=archive['archive']) - for archive in reversed(archives_data['archives']) - ), - ) + for archive in reversed(archives_data['archives']): + browse_app.call_from_thread( + archives_list.add_option, + textual.widgets.option_list.Option(archive['archive'], id=archive['archive']), + ) browse_app.call_from_thread(timer.stop) browse_app.call_from_thread(archives_list.remove_option, 'loading-indicator') -# FIXME: Revamp for option list instead of tree. @textual.work(thread=True) -def add_archive_files(browse_tree, archive_node, config, repository, archive, timer, loading_node): - files_data = borgmatic.actions.browse.controller.get_archive_files(config, repository, archive) - config = archive_node.data['config'] - repository = archive_node.data['repository'] - archive = archive_node.data['archive'] +def add_archive_files(browse_app, files_list, config, repository, archive_name, timer): + paths = borgmatic.actions.browse.controller.get_archive_files(config, repository, archive_name) - for file_data in files_data: - add_path( - browse_tree, archive_node, config, repository, archive, timer, loading_node, file_data + for path in paths: + browse_app.call_from_thread( + files_list.add_option, + textual.widgets.option_list.Option(path, id=path), ) - browse_tree.app.call_from_thread(loading_node.remove) - browse_tree.app.call_from_thread(timer.stop) + browse_app.call_from_thread(timer.stop) + browse_app.call_from_thread(files_list.remove_option, 'loading-indicator') class Configuration_files_list(textual.widgets.OptionList): @@ -124,6 +119,12 @@ class Archives_list(textual.widgets.OptionList): self.border_title = 'archives' +class Files_list(textual.widgets.OptionList): + def __init__(self): + super().__init__(id='files-list', classes='panel') + self.border_title = 'files' + + class Logs(textual.widgets.RichLog): def __init__(self): super().__init__(markup=True, classes='panel') @@ -159,6 +160,10 @@ class Browse_app(textual.app.App): display: none; } + #files-list { + display: none; + } + #logs-container { height: 50%; display: none; @@ -174,12 +179,14 @@ class Browse_app(textual.app.App): self.configuration_files_list = Configuration_files_list(self.configs) self.repositories_list = Repositories_list() self.archives_list = Archives_list() + self.files_list = Files_list() yield textual.widgets.Header() with textual.containers.Horizontal(): yield self.configuration_files_list yield self.repositories_list yield self.archives_list + yield self.files_list with textual.containers.Horizontal(id='logs-container'): logs_widget = Logs() @@ -219,6 +226,17 @@ class Browse_app(textual.app.App): repository=self.repositories_list.repositories[event.option_id], timer=timer, ) + elif event.option_list == self.archives_list: + timer = add_inline_loading_indicator(self.files_list) + + add_archive_files( + self, + files_list=self.files_list, + config=self.repositories_list.config, + repository=self.repositories_list.repositories[self.repositories_list.highlighted_option.id], + archive_name=event.option_id, + timer=timer, + ) def on_option_list_option_selected(self, event): if event.option_list == self.configuration_files_list: @@ -226,6 +244,11 @@ class Browse_app(textual.app.App): self.archives_list.styles.display = 'block' self.repositories_list.focus() self.repositories_list.highlighted = 0 + elif event.option_list == self.repositories_list: + self.repositories_list.styles.display = 'none' + self.files_list.styles.display = 'block' + self.archives_list.focus() + self.archives_list.highlighted = 0 def action_toggle_logs(self): logs_container = self.query_one('#logs-container')