Skip to content

Commit

Permalink
Merge pull request #469 from tadeubas/fix-file-load
Browse files Browse the repository at this point in the history
Avoid exiting when user misclick and cancel the load of a file
  • Loading branch information
odudex authored Oct 15, 2024
2 parents fdee14e + 03678f9 commit 6420c61
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 25 deletions.
12 changes: 12 additions & 0 deletions src/krux/pages/file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ..sd_card import SDHandler
from ..krux_settings import t
from ..format import generate_thousands_separator, render_decimal_separator
from ..display import BOTTOM_PROMPT_LINE

LIST_FILE_DIGITS = 9 # len on large devices per menu item
LIST_FILE_DIGITS_SMALL = 5 # len on small devices per menu item
Expand Down Expand Up @@ -149,6 +150,17 @@ def show_file_details(self, file):
self.ctx.input.wait_for_button()
return MENU_CONTINUE

def load_file(self, file):
"""Handler to ask if will load selected file in the file explorer"""
if SDHandler.dir_exists(file):
return MENU_EXIT

self.display_file(file)

if self.prompt(t("Load?"), BOTTOM_PROMPT_LINE):
return MENU_EXIT
return MENU_CONTINUE

def display_file(self, file):
"""Display the file details on the device's screen"""
import uos
Expand Down
2 changes: 2 additions & 0 deletions src/krux/pages/home_pages/sign_message_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ def _sign_at_address_from_sd(self, data):
"""Message signed at a derived Bitcoin address - SD card"""
data = data.decode() if isinstance(data, bytes) else data
lines = [line.strip() for line in data.splitlines() if line.strip()]
if len(lines) == 0:
return None
script_type = lines[-1].lower()
if len(lines) < 2 or script_type not in SINGLESIG_SCRIPT_PURPOSE:
return None
Expand Down
16 changes: 8 additions & 8 deletions src/krux/pages/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
# THE SOFTWARE.

from . import Page
from ..display import BOTTOM_PROMPT_LINE
from ..krux_settings import t
from ..sd_card import SDHandler
from embit.wordlists.bip39 import WORDLIST
Expand Down Expand Up @@ -61,15 +60,16 @@ def load_file(self, file_ext="", prompt=True, only_get_filename=False):
from .file_manager import FileManager

file_manager = FileManager(self.ctx)
filename = file_manager.select_file(file_extension=file_ext)
filename = file_manager.select_file(
select_file_handler=file_manager.load_file,
file_extension=file_ext,
)

if filename:
filename = file_manager.display_file(filename)

if self.prompt(t("Load?"), BOTTOM_PROMPT_LINE):
if only_get_filename:
return filename, None
return filename, sd.read_binary(filename)
filename = filename[4:] # remove "/sd/" prefix
if only_get_filename:
return filename, None
return filename, sd.read_binary(filename)
return "", None

@staticmethod
Expand Down
50 changes: 36 additions & 14 deletions tests/pages/home_pages/test_sign_message_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def test_sign_message(mocker, m5stickv, tdata):
and case[3][0] == BUTTON_ENTER
and case[3][1] == BUTTON_ENTER
)
if case[0] and signed_qr_message and case[6] is None:
if case[0] is not None and signed_qr_message and case[6] is None:
home.display_qr_codes.assert_has_calls(
[
mocker.call(case[4], case[1], "Signed Message"),
Expand Down Expand Up @@ -353,6 +353,26 @@ def test_sign_message_at_address(mocker, m5stickv, tdata):
"3. bc1qgl..cn3",
"IN/4LmcGRaI5sgvBP2mrTXQFvD6FecXd8La03SixPabsb/255ElRGTcXhicT3KFsNJbfQ9te909ZXeKMaqUcaPM=",
),
( # 7 - Sign empty - Load from and save to SD card
[
BUTTON_PAGE, # Load from SD card
BUTTON_ENTER, # Confirm load from SD card
BUTTON_ENTER, # Choose file "signmessage.txt"
BUTTON_ENTER, # Confirm to sign message
BUTTON_ENTER, # Confirm to sign message
BUTTON_ENTER, # Check signature
BUTTON_PAGE, # Sign to SD card
BUTTON_ENTER, # Confirm sign to SD card
BUTTON_PAGE_PREV, # Move to Go
BUTTON_ENTER, # Go
],
None,
"",
True, # Sign to SD
"A test message.",
"3. bc1qgl..cn3",
"IN/4LmcGRaI5sgvBP2mrTXQFvD6FecXd8La03SixPabsb/255ElRGTcXhicT3KFsNJbfQ9te909ZXeKMaqUcaPM=",
),
]
case_count = 0
for case in cases:
Expand All @@ -375,26 +395,28 @@ def test_sign_message_at_address(mocker, m5stickv, tdata):
)
qr_capturer = mocker.spy(QRCodeCapture, "qr_capture_loop")
mocker.patch.object(message_signer, "has_sd_card", new=lambda: True)
if case[2]: # Load from SD card
if case[2] is not None: # Load from SD card
mocker.patch.object(
message_signer, "load_file", return_value=("signmessage.txt", case[2])
)

message_signer.sign_message()

qr_capturer.assert_called_once()
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call("Message:", 10, theme.highlight_color)]
)
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call(case[4], mocker.ANY, max_lines=10)]
)
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call("Address:", mocker.ANY, theme.highlight_color)]
)
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call(case[5], mocker.ANY)],
)

if case[2] != "":
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call("Message:", 10, theme.highlight_color)]
)
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call(case[4], mocker.ANY, max_lines=10)]
)
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call("Address:", mocker.ANY, theme.highlight_color)]
)
ctx.display.draw_hcentered_text.assert_has_calls(
[mocker.call(case[5], mocker.ANY)],
)
if not case[3]:
message_signer.display_qr_codes.assert_called_once_with(
case[6],
Expand Down
126 changes: 126 additions & 0 deletions tests/pages/test_file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,132 @@ def mock_localtime(timestamp):
)


def test_file_load(m5stickv, mocker, mock_file_operations):
from krux.pages.file_manager import FileManager
from krux.input import BUTTON_ENTER, BUTTON_PAGE
from krux.sd_card import DESCRIPTOR_FILE_EXTENSION
import time

BTN_SEQUENCE = (
[BUTTON_PAGE] # move to second entry, last directory
+ [BUTTON_PAGE] # move to third entry, first file
+ [BUTTON_PAGE] # move to fourth entry, last file
+ [BUTTON_ENTER] # load file
+ [BUTTON_ENTER] # confirm file details
)

def mock_localtime(timestamp):
return time.gmtime(timestamp)

# specific types
mocker.patch(
"os.listdir",
new=mocker.MagicMock(
return_value=[
"file1.txt", # third entry
"file1.err", # not entry
"file2_has_a_long_name.txt", # fourth entry
"subdir2", # second entry
"subdir1_has_a_long_name", # first entry
]
),
)

# selected file has this timestamp
mocker.patch("time.localtime", side_effect=mock_localtime)

# to view this directory, selected file isn't a directory
mocker.patch(
"krux.sd_card.SDHandler.dir_exists",
mocker.MagicMock(side_effect=[True, False, False]),
)
# first 2 entries are files, next 2 are directories
mocker.patch(
"krux.sd_card.SDHandler.file_exists",
mocker.MagicMock(side_effect=[True, True, True, False, False]),
)
ctx = create_ctx(mocker, BTN_SEQUENCE)
file_manager = FileManager(ctx)
file_manager.select_file(
select_file_handler=file_manager.load_file,
file_extension=[".abc", DESCRIPTOR_FILE_EXTENSION],
)

assert ctx.input.wait_for_button.call_count == len(BTN_SEQUENCE)

ctx.display.draw_hcentered_text.assert_has_calls(
[
mocker.call(
"file2_has_a_long_name.txt\n\nSize: 1.1 KB\n\nCreated: 1970-01-01 00:00\n\nModified: 1970-01-01 00:00"
)
]
)


def test_file_load_cancel(m5stickv, mocker, mock_file_operations):
from krux.pages.file_manager import FileManager
from krux.input import BUTTON_ENTER, BUTTON_PAGE
from krux.sd_card import DESCRIPTOR_FILE_EXTENSION
import time

BTN_SEQUENCE = (
[BUTTON_PAGE] # move to second entry, last directory
+ [BUTTON_PAGE] # move to third entry, first file
+ [BUTTON_PAGE] # move to fourth entry, last file
+ [BUTTON_ENTER] # load file
+ [BUTTON_PAGE] # cancel load
+ [BUTTON_ENTER] # load file
+ [BUTTON_ENTER] # confirm
)

def mock_localtime(timestamp):
return time.gmtime(timestamp)

# specific types
mocker.patch(
"os.listdir",
new=mocker.MagicMock(
return_value=[
"file1.txt", # third entry
"file1.err", # not entry
"file2_has_a_long_name.txt", # fourth entry
"subdir2", # second entry
"subdir1_has_a_long_name", # first entry
]
),
)

# selected file has this timestamp
mocker.patch("time.localtime", side_effect=mock_localtime)

# to view this directory, selected file isn't a directory
mocker.patch(
"krux.sd_card.SDHandler.dir_exists",
mocker.MagicMock(side_effect=[True, False, False, False]),
)
# first 2 entries are files, next 2 are directories
mocker.patch(
"krux.sd_card.SDHandler.file_exists",
mocker.MagicMock(side_effect=[True, True, True, False, False]),
)
ctx = create_ctx(mocker, BTN_SEQUENCE)
file_manager = FileManager(ctx)
file_manager.select_file(
select_file_handler=file_manager.load_file,
file_extension=[".abc", DESCRIPTOR_FILE_EXTENSION],
)

assert ctx.input.wait_for_button.call_count == len(BTN_SEQUENCE)

ctx.display.draw_hcentered_text.assert_has_calls(
[
mocker.call(
"file2_has_a_long_name.txt\n\nSize: 1.1 KB\n\nCreated: 1970-01-01 00:00\n\nModified: 1970-01-01 00:00"
)
]
)


def test_files_and_folders_with_long_filenames(m5stickv, mocker, mock_file_operations):
from krux.pages.file_manager import FileManager
from krux.input import BUTTON_ENTER, BUTTON_PAGE_PREV
Expand Down
5 changes: 2 additions & 3 deletions tests/pages/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

def test_load_file(m5stickv, mocker, mock_file_operations):
from krux.pages.utils import Utils
from krux.input import BUTTON_ENTER, BUTTON_PAGE
from krux.input import BUTTON_ENTER

BTN_SEQUENCE = [
BUTTON_ENTER, # Pick first file
Expand All @@ -14,7 +14,7 @@ def test_load_file(m5stickv, mocker, mock_file_operations):
for only_get_filename in ONLY_GET_FILENAME_OPTIONS:
mocker.patch(
"krux.sd_card.SDHandler.dir_exists",
mocker.MagicMock(side_effect=[True, False]),
mocker.MagicMock(side_effect=[True, False, False]),
)
ctx = create_ctx(mocker, BTN_SEQUENCE)
utils = Utils(ctx)
Expand All @@ -32,7 +32,6 @@ def test_load_file(m5stickv, mocker, mock_file_operations):

def test_load_file_with_no_sd(m5stickv, mocker):
from krux.pages.utils import Utils
from krux.input import BUTTON_PAGE

mocker.patch(
"krux.sd_card.SDHandler.dir_exists",
Expand Down

0 comments on commit 6420c61

Please sign in to comment.