Binary versus text file detection for borgmatic browse.

This commit is contained in:
Dan Helfman
2026-05-23 22:50:51 -07:00
parent 94a2d68198
commit 43e995a4f3
9 changed files with 44 additions and 15 deletions
+1
View File
@@ -1,6 +1,7 @@
.
apprise
attrs
binaryornot
certifi
charset-normalizer
click
+1
View File
@@ -2,6 +2,7 @@
# uv pip compile --annotation-style line binary_requirements.in -o binary_requirements.txt
apprise==1.10.0 # via -r binary_requirements.in
attrs==26.1.0 # via jsonschema, referencing, -r binary_requirements.in
binaryornot==0.6.0 # via -r binary_requirements.in
. # via -r binary_requirements.in
certifi==2026.5.20 # via apprise, requests, -r binary_requirements.in
charset-normalizer==3.4.7 # via requests, -r binary_requirements.in
+19 -6
View File
@@ -7,6 +7,9 @@ import borgmatic.borg.extract
import borgmatic.borg.repo_list
import borgmatic.borg.version
import binaryornot.helpers
logger = logging.getLogger(__name__)
@@ -82,6 +85,10 @@ READLINES_HINT_BYTES = 100000
def get_archive_file_content(config, repository, archive_name, file_path):
'''
Given a configuration dict, a repository dict, an archive name in that repository, and a file
path in that archive, return the file's contents or None if the file can't be loaded.
'''
with borgmatic.logger.Log_prefix(repository.get('label', repository['path'])):
logger.info(f'Getting archive content of file {file_path}')
local_path = config.get('local_path', 'borg')
@@ -102,10 +109,16 @@ def get_archive_file_content(config, repository, archive_name, file_path):
extract_to_stdout=True,
).stdout.readlines(READLINES_HINT_BYTES)
content = ''.join(line.decode() for line in lines)
content = b''.join(lines)
return (
content
if len(content) < READLINES_HINT_BYTES
else f'{content}[... truncated for display ...]'
)
if binaryornot.helpers.is_binary_string(content):
return None
try:
return (
content.decode()
if len(content) < READLINES_HINT_BYTES
else f'{content.decode()}\n[... truncated for display ...]'
)
except UnicodeDecodeError:
return None
+11 -5
View File
@@ -15,6 +15,12 @@ OPTION_LIST_BINDINGS = (
textual.binding.Binding(
key='down,j', action='cursor_down', description='scroll down', show=True, priority=True
),
textual.binding.Binding(
key='pageup', action='page_up', description='page up', show=True, priority=True
),
textual.binding.Binding(
key='pagedown', action='page_down', description='page down', show=True, priority=True
),
textual.binding.Binding(
key='enter', action='select', description='select', show=True, priority=True
),
@@ -55,7 +61,7 @@ class Repositories_list(textual.widgets.OptionList):
),
classes='panel',
)
self.border_title = 'repositories'
self.border_title = '📦 repositories'
class Archives_list(textual.widgets.OptionList):
@@ -66,7 +72,7 @@ class Archives_list(textual.widgets.OptionList):
self.repository = repository
super().__init__(classes='panel')
self.border_title = 'archives'
self.border_title = '📚 archives'
self.highlighted_option_changed = False
timer = borgmatic.actions.browse.loading.add_inline_loading_indicator(self)
@@ -128,10 +134,10 @@ class File_preview(textual.widgets.RichLog):
BINDINGS = [
*textual.widgets.RichLog.BINDINGS,
textual.binding.Binding(
key='up', action='scroll_up', description='scroll up', show=True, priority=True
key='up,k', action='scroll_up', description='scroll up', show=True, priority=True
),
textual.binding.Binding(
key='down', action='scroll_down', description='scroll down', show=True, priority=True
key='down,j', action='scroll_down', description='scroll down', show=True, priority=True
),
textual.binding.Binding(
key='pageup', action='page_up', description='page up', show=True, priority=True
@@ -175,4 +181,4 @@ class File_preview(textual.widgets.RichLog):
class Logs(textual.widgets.RichLog):
def __init__(self):
super().__init__(markup=True, id='logs', classes='panel')
self.border_title = 'logs'
self.border_title = '🪵 logs'
+2
View File
@@ -4,11 +4,13 @@ import enum
class Path_type(enum.Enum):
DIRECTORY = 'd'
LINK = 'l'
PIPE = 'p'
FILE = '-'
PATH_TYPE_ICONS = {
Path_type.DIRECTORY.value: '📁',
Path_type.LINK.value: '🔗',
Path_type.PIPE.value: '🚰',
Path_type.FILE.value: '📄',
}
+7 -3
View File
@@ -59,7 +59,7 @@ def add_archive_files(
)
for path_type, file_path, link_target in file_type_paths:
pieces = (borgmatic.actions.browse.paths.PATH_TYPE_ICONS.get(path_type, '?'), file_path) + (
pieces = (borgmatic.actions.browse.paths.PATH_TYPE_ICONS.get(path_type, ''), file_path) + (
('', link_target) if link_target else ()
)
highlighted_option = directory_list.highlighted_option
@@ -88,6 +88,10 @@ def load_file_preview(browse_app, file_preview, config, repository, archive_name
)
browse_app.call_from_thread(timer.stop)
syntax_lexer = rich.syntax.Syntax.guess_lexer(file_path, file_content)
browse_app.call_from_thread(file_preview.clear)
browse_app.call_from_thread(file_preview.write, rich.syntax.Syntax(file_content, syntax_lexer))
if file_content is None:
browse_app.call_from_thread(file_preview.write, 'Cannot load a preview for this file')
else:
syntax_lexer = rich.syntax.Syntax.guess_lexer(file_path, file_content)
browse_app.call_from_thread(file_preview.write, rich.syntax.Syntax(file_content, syntax_lexer))
+1 -1
View File
@@ -31,7 +31,7 @@ validate-borgmatic-config = "borgmatic.commands.validate_config:main"
[project.optional-dependencies]
Apprise = ["apprise"]
browse = ["textual"]
browse = ["textual", "binaryornot"]
browse-dev = ["textual-dev"]
[project.urls]
+1
View File
@@ -1,5 +1,6 @@
apprise
attrs
binaryornot
certifi
charset-normalizer
click>=8.1.8
+1
View File
@@ -2,6 +2,7 @@
# uv pip compile --annotation-style line test_requirements.in -o test_requirements.txt
apprise==1.10.0 # via -r test_requirements.in
attrs==26.1.0 # via jsonschema, referencing, -r test_requirements.in
binaryornot==0.6.0 # via -r test_requirements.in
certifi==2026.5.20 # via apprise, requests, -r test_requirements.in
charset-normalizer==3.4.7 # via requests, -r test_requirements.in
click>=8.1.8