Added selection confirmation sections
This commit is contained in:
parent
0f0c47bbe4
commit
428d255538
1 changed files with 203 additions and 17 deletions
|
|
@ -76,6 +76,47 @@ class State():
|
||||||
self.init_tmux()
|
self.init_tmux()
|
||||||
exe.start_thread(self.fix_tmux_layout_loop)
|
exe.start_thread(self.fix_tmux_layout_loop)
|
||||||
|
|
||||||
|
def confirm_selections(self, mode, prompt):
|
||||||
|
"""Show selection details and prompt for confirmation."""
|
||||||
|
report = []
|
||||||
|
|
||||||
|
# Source
|
||||||
|
report.append(std.color_string('Source', 'GREEN'))
|
||||||
|
report.extend(build_object_report(self.source))
|
||||||
|
report.append(' ')
|
||||||
|
|
||||||
|
# Destination
|
||||||
|
report.append(std.color_string('Destination', 'GREEN'))
|
||||||
|
if mode == 'Clone':
|
||||||
|
report[-1] += std.color_string(' (ALL DATA WILL BE DELETED)', 'RED')
|
||||||
|
report.extend(build_object_report(self.destination))
|
||||||
|
report.append(' ')
|
||||||
|
|
||||||
|
# Block pairs
|
||||||
|
if self.block_pairs:
|
||||||
|
# Show mapping
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# Show deletion warning if required
|
||||||
|
if mode == 'Clone':
|
||||||
|
report.append(std.color_string('WARNING', 'YELLOW'))
|
||||||
|
report.append(
|
||||||
|
'All data will be deleted from the destination listed above.',
|
||||||
|
)
|
||||||
|
report.append(
|
||||||
|
std.color_string(
|
||||||
|
['This is irreversible and will lead to', 'DATA LOSS.'],
|
||||||
|
['YELLOW', 'RED'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
report.append(' ')
|
||||||
|
|
||||||
|
# Prompt user
|
||||||
|
std.clear_screen()
|
||||||
|
std.print_report(report)
|
||||||
|
if not std.ask(prompt):
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
def fix_tmux_layout(self, forced=True):
|
def fix_tmux_layout(self, forced=True):
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
"""Fix tmux layout based on cfg.ddrescue.TMUX_LAYOUT."""
|
"""Fix tmux layout based on cfg.ddrescue.TMUX_LAYOUT."""
|
||||||
|
|
@ -139,7 +180,6 @@ class State():
|
||||||
self.source = select_disk('Source')
|
self.source = select_disk('Source')
|
||||||
source_parts = select_disk_parts(mode, self.source)
|
source_parts = select_disk_parts(mode, self.source)
|
||||||
self.update_top_panes()
|
self.update_top_panes()
|
||||||
std.pause()
|
|
||||||
|
|
||||||
# Select destination
|
# Select destination
|
||||||
self.destination = get_object(docopt_args['<destination>'])
|
self.destination = get_object(docopt_args['<destination>'])
|
||||||
|
|
@ -157,6 +197,28 @@ class State():
|
||||||
)
|
)
|
||||||
self.update_progress_pane()
|
self.update_progress_pane()
|
||||||
|
|
||||||
|
# Confirmation #1
|
||||||
|
self.confirm_selections(mode, 'Are these selections correct?')
|
||||||
|
|
||||||
|
# Set working dir
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# Add block pairs
|
||||||
|
# NOTE: Destination is not updated
|
||||||
|
# Load settings/maps
|
||||||
|
# Ask about boot partition
|
||||||
|
# Create pairs using paths
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# Confirmation #2
|
||||||
|
self.confirm_selections(mode, 'Start recovery?')
|
||||||
|
|
||||||
|
# Prep destination
|
||||||
|
# if cloning and not resuming format destination
|
||||||
|
|
||||||
|
# Done
|
||||||
|
# Ready for main menu
|
||||||
|
|
||||||
def init_tmux(self):
|
def init_tmux(self):
|
||||||
"""Initialize tmux layout."""
|
"""Initialize tmux layout."""
|
||||||
tmux.kill_all_panes()
|
tmux.kill_all_panes()
|
||||||
|
|
@ -276,6 +338,110 @@ class State():
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
def build_directory_report(path):
|
||||||
|
"""Build directory report, returns list."""
|
||||||
|
path = str(path)
|
||||||
|
report = []
|
||||||
|
|
||||||
|
# Get details
|
||||||
|
if PLATFORM == 'Linux':
|
||||||
|
cmd = [
|
||||||
|
'findmnt',
|
||||||
|
'--output', 'SIZE,AVAIL,USED,FSTYPE,OPTIONS',
|
||||||
|
'--target', path,
|
||||||
|
]
|
||||||
|
proc = exe.run_program(cmd)
|
||||||
|
width = len(path) + 1
|
||||||
|
for line in proc.stdout.splitlines():
|
||||||
|
line = line.replace('\n', '')
|
||||||
|
if 'FSTYPE' in line:
|
||||||
|
line = std.color_string(f'{"PATH":<{width}}{line}', 'BLUE')
|
||||||
|
else:
|
||||||
|
line = f'{path:<{width}}{line}'
|
||||||
|
report.append(line)
|
||||||
|
else:
|
||||||
|
# TODO Get dir details under macOS
|
||||||
|
report.append(std.color_string('PATH', 'BLUE'))
|
||||||
|
report.append(str(path))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def build_disk_report(dev):
|
||||||
|
"""Build device report, returns list."""
|
||||||
|
children = dev.details.get('children', [])
|
||||||
|
report = []
|
||||||
|
|
||||||
|
# Get widths
|
||||||
|
widths = {
|
||||||
|
'fstype': max(6, len(str(dev.details.get('fstype', '')))),
|
||||||
|
'label': max(5, len(str(dev.details.get('label', '')))),
|
||||||
|
'name': max(4, len(dev.path.name)),
|
||||||
|
}
|
||||||
|
for child in children:
|
||||||
|
widths['fstype'] = max(widths['fstype'], len(str(child['fstype'])))
|
||||||
|
widths['label'] = max(widths['label'], len(str(child['label'])))
|
||||||
|
widths['name'] = max(
|
||||||
|
widths['name'],
|
||||||
|
len(child['name'].replace('/dev/', '')),
|
||||||
|
)
|
||||||
|
widths = {k: v+1 for k, v in widths.items()}
|
||||||
|
|
||||||
|
# Disk details
|
||||||
|
report.append(f'{dev.path.name} {dev.description}')
|
||||||
|
report.append(' ')
|
||||||
|
dev_fstype = dev.details.get('fstype', '')
|
||||||
|
dev_label = dev.details.get('label', '')
|
||||||
|
dev_name = dev.path.name
|
||||||
|
dev_size = std.bytes_to_string(dev.details["size"], use_binary=False)
|
||||||
|
|
||||||
|
# Partition details
|
||||||
|
report.append(
|
||||||
|
std.color_string(
|
||||||
|
(
|
||||||
|
f'{"NAME":<{widths["name"]}}'
|
||||||
|
f'{" " if children else ""}'
|
||||||
|
f'{"SIZE":<7}'
|
||||||
|
f'{"FSTYPE":<{widths["fstype"]}}'
|
||||||
|
f'{"LABEL":<{widths["label"]}}'
|
||||||
|
),
|
||||||
|
'BLUE',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
report.append(
|
||||||
|
f'{dev_name if dev_name else "":<{widths["name"]}}'
|
||||||
|
f'{" " if children else ""}'
|
||||||
|
f'{dev_size:>6} '
|
||||||
|
f'{dev_fstype if dev_fstype else "":<{widths["fstype"]}}'
|
||||||
|
f'{dev_label if dev_label else "":<{widths["label"]}}'
|
||||||
|
)
|
||||||
|
for child in children:
|
||||||
|
fstype = child['fstype']
|
||||||
|
label = child['label']
|
||||||
|
name = child['name'].replace('/dev/', '')
|
||||||
|
size = std.bytes_to_string(child["size"], use_binary=False)
|
||||||
|
report.append(
|
||||||
|
f'{name if name else "":<{widths["name"]}}'
|
||||||
|
f'{size:>6} '
|
||||||
|
f'{fstype if fstype else "":<{widths["fstype"]}}'
|
||||||
|
f'{label if label else "":<{widths["label"]}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Indent children
|
||||||
|
if len(children) > 1:
|
||||||
|
report = [
|
||||||
|
*report[:4],
|
||||||
|
*[f'├─{line}' for line in report[4:-1]],
|
||||||
|
f'└─{report[-1]}',
|
||||||
|
]
|
||||||
|
elif len(children) == 1:
|
||||||
|
report[-1] = f'└─{report[-1]}'
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
def build_main_menu():
|
def build_main_menu():
|
||||||
"""Build main menu, returns wk.std.Menu."""
|
"""Build main menu, returns wk.std.Menu."""
|
||||||
menu = std.Menu(title=std.color_string('ddrescue TUI: Main Menu', 'GREEN'))
|
menu = std.Menu(title=std.color_string('ddrescue TUI: Main Menu', 'GREEN'))
|
||||||
|
|
@ -291,6 +457,22 @@ def build_main_menu():
|
||||||
return menu
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
def build_object_report(obj):
|
||||||
|
"""Build object report, returns list."""
|
||||||
|
report = []
|
||||||
|
|
||||||
|
# Get details based on object given
|
||||||
|
if hasattr(obj, 'is_dir') and obj.is_dir():
|
||||||
|
# Directory report
|
||||||
|
report = build_directory_report(obj)
|
||||||
|
else:
|
||||||
|
# Device report
|
||||||
|
report = build_disk_report(obj)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
def build_settings_menu(silent=True):
|
def build_settings_menu(silent=True):
|
||||||
"""Build settings menu, returns wk.std.Menu."""
|
"""Build settings menu, returns wk.std.Menu."""
|
||||||
title_text = [
|
title_text = [
|
||||||
|
|
@ -555,6 +737,25 @@ def select_disk_parts(prompt, disk):
|
||||||
menu.add_action('Quit')
|
menu.add_action('Quit')
|
||||||
object_list = []
|
object_list = []
|
||||||
|
|
||||||
|
def _select_parts(menu):
|
||||||
|
"""Loop over selection menu until at least one partition selected."""
|
||||||
|
while True:
|
||||||
|
selection = menu.advanced_select(
|
||||||
|
f'Please select the parts to {prompt.lower()}: ',
|
||||||
|
)
|
||||||
|
if 'All' in selection:
|
||||||
|
for option in menu.options.values():
|
||||||
|
option['Selected'] = True
|
||||||
|
elif 'None' in selection:
|
||||||
|
for option in menu.options.values():
|
||||||
|
option['Selected'] = False
|
||||||
|
elif 'Proceed' in selection:
|
||||||
|
if any([option['Selected'] for option in menu.options.values()]):
|
||||||
|
# At least one partition/device selected/device selected
|
||||||
|
break
|
||||||
|
elif 'Quit' in selection:
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
# Bail early if child device selected
|
# Bail early if child device selected
|
||||||
if disk.details.get('parent', False):
|
if disk.details.get('parent', False):
|
||||||
return [disk]
|
return [disk]
|
||||||
|
|
@ -576,22 +777,7 @@ def select_disk_parts(prompt, disk):
|
||||||
menu.title += std.color_string(' No partitions detected.', 'YELLOW')
|
menu.title += std.color_string(' No partitions detected.', 'YELLOW')
|
||||||
|
|
||||||
# Get selection
|
# Get selection
|
||||||
while True:
|
_select_parts(menu)
|
||||||
selection = menu.advanced_select(
|
|
||||||
f'Please select the parts to {prompt.lower()}: ',
|
|
||||||
)
|
|
||||||
if 'All' in selection:
|
|
||||||
for option in menu.options.values():
|
|
||||||
option['Selected'] = True
|
|
||||||
elif 'None' in selection:
|
|
||||||
for option in menu.options.values():
|
|
||||||
option['Selected'] = False
|
|
||||||
elif 'Proceed' in selection:
|
|
||||||
if any([option['Selected'] for option in menu.options.values()]):
|
|
||||||
# At least one partition/device selected/device selected
|
|
||||||
break
|
|
||||||
elif 'Quit' in selection:
|
|
||||||
raise std.GenericAbort()
|
|
||||||
|
|
||||||
# Build list of Disk() object_list
|
# Build list of Disk() object_list
|
||||||
for option in menu.options.values():
|
for option in menu.options.values():
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue