Reordered classes and removed old menu functions
This commit is contained in:
parent
7ac91fd312
commit
03bdb4b4b7
1 changed files with 112 additions and 189 deletions
|
|
@ -37,6 +37,29 @@ USAGE = """ {script_name} clone [source [destination]]
|
||||||
|
|
||||||
|
|
||||||
# Clases
|
# Clases
|
||||||
|
class BaseObj():
|
||||||
|
"""Base object used by DevObj, DirObj, and ImageObj."""
|
||||||
|
def __init__(self, path):
|
||||||
|
self.type = 'base'
|
||||||
|
self.path = os.path.realpath(path)
|
||||||
|
self.set_details()
|
||||||
|
|
||||||
|
def is_dev(self):
|
||||||
|
return self.type == 'dev'
|
||||||
|
|
||||||
|
def is_dir(self):
|
||||||
|
return self.type == 'dir'
|
||||||
|
|
||||||
|
def is_image(self):
|
||||||
|
return self.type == 'image'
|
||||||
|
|
||||||
|
def self_check(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_details(self):
|
||||||
|
self.details = {}
|
||||||
|
|
||||||
|
|
||||||
class BlockPair():
|
class BlockPair():
|
||||||
"""Object to track data and methods together for source and dest."""
|
"""Object to track data and methods together for source and dest."""
|
||||||
def __init__(self, source, dest, mode):
|
def __init__(self, source, dest, mode):
|
||||||
|
|
@ -132,6 +155,95 @@ class BlockPair():
|
||||||
data=(self.rescued/self.size)*100)
|
data=(self.rescued/self.size)*100)
|
||||||
|
|
||||||
|
|
||||||
|
class DevObj(BaseObj):
|
||||||
|
"""Block device object."""
|
||||||
|
def self_check(self):
|
||||||
|
"""Verify that self.path points to a block device."""
|
||||||
|
if not pathlib.Path(self.path).is_block_device():
|
||||||
|
raise GenericError('Path "{}" is not a block device.'.format(
|
||||||
|
self.path))
|
||||||
|
if self.parent:
|
||||||
|
print_warning('"{}" is a child device.'.format(self.path))
|
||||||
|
if ask('Use parent device "{}" instead?'.format(self.parent):
|
||||||
|
self.path = os.path.realpath(path)
|
||||||
|
self.set_details()
|
||||||
|
|
||||||
|
def set_details(self):
|
||||||
|
"""Set details via lsblk."""
|
||||||
|
self.type = 'dev'
|
||||||
|
self.details = get_device_details(self.path)
|
||||||
|
self.name = '{name} {size} {model} {serial}'.format(
|
||||||
|
name=self.details.get('name', 'UNKNOWN'),
|
||||||
|
size=self.details.get('size', 'UNKNOWN'),
|
||||||
|
model=self.details.get('model', 'UNKNOWN'),
|
||||||
|
serial=self.details.get('serial', 'UNKNOWN'))
|
||||||
|
self.model = self.details.get('model', 'UNKNOWN')
|
||||||
|
self.model_size = self.details.get('size', 'UNKNOWN')
|
||||||
|
self.size = get_size_in_bytes(self.details.get('size', 'UNKNOWN'))
|
||||||
|
self.report = get_device_report(self.path)
|
||||||
|
self.parent = self.details.get('pkname', '')
|
||||||
|
self.label = self.details.get('label', '')
|
||||||
|
if not self.label:
|
||||||
|
# Force empty string in case it's set to None
|
||||||
|
self.label = ''
|
||||||
|
self.update_filename_prefix()
|
||||||
|
|
||||||
|
def update_filename_prefix(self):
|
||||||
|
"""Set filename prefix based on details."""
|
||||||
|
self.prefix = '{m_size}_{model}'.format(
|
||||||
|
m_size=self.model_size,
|
||||||
|
model=self.model)
|
||||||
|
if self.parent:
|
||||||
|
# Add child device details
|
||||||
|
self.prefix += '_{c_num}_{c_size}{sep}{c_label}'.format(
|
||||||
|
c_num=self.name.replace(self.parent, ''),
|
||||||
|
c_size=self.details.get('size', 'UNKNOWN'),
|
||||||
|
sep='_' if self.label else '',
|
||||||
|
c_label=self.label)
|
||||||
|
|
||||||
|
|
||||||
|
class DirObj(BaseObj):
|
||||||
|
def self_check(self):
|
||||||
|
"""Verify that self.path points to a directory."""
|
||||||
|
if not pathlib.Path(self.path).is_dir():
|
||||||
|
raise GenericError('Path "{}" is not a directory.'.format(
|
||||||
|
self.path))
|
||||||
|
|
||||||
|
def set_details(self):
|
||||||
|
"""Set details via findmnt."""
|
||||||
|
self.type = 'dir'
|
||||||
|
self.details = get_dir_details(self.path)
|
||||||
|
self.fstype = self.details.get('fstype', 'UNKNOWN')
|
||||||
|
self.name = self.path + '/'
|
||||||
|
self.size = get_size_in_bytes(self.details.get('avail', 'UNKNOWN'))
|
||||||
|
self.report = get_dir_report(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageObj(BaseObj):
|
||||||
|
def self_check(self):
|
||||||
|
"""Verify that self.path points to a file."""
|
||||||
|
if not pathlib.Path(self.path).is_file():
|
||||||
|
raise GenericError('Path "{}" is not an image file.'.format(
|
||||||
|
self.path))
|
||||||
|
|
||||||
|
def set_details(self):
|
||||||
|
"""Setup loopback device, set details via lsblk, then detach device."""
|
||||||
|
self.type = 'image'
|
||||||
|
self.loop_dev = setup_loopback_device(self.path)
|
||||||
|
self.details = get_device_details(self.loop_dev)
|
||||||
|
self.details['model'] = 'ImageFile'
|
||||||
|
self.name = '{name} {size}'.format(
|
||||||
|
name=self.path[self.path.rfind('/')+1:],
|
||||||
|
size=self.details.get('size', 'UNKNOWN'))
|
||||||
|
self.prefix = '{}_ImageFile'.format(
|
||||||
|
self.details.get('size', 'UNKNOWN'))
|
||||||
|
self.size = get_size_in_bytes(self.details.get('size', 'UNKNOWN'))
|
||||||
|
self.report = get_device_report(self.loop_dev)
|
||||||
|
self.report = self.report.replace(
|
||||||
|
self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)')
|
||||||
|
run_program(['losetup', '--detach', self.loop_dev], check=False)
|
||||||
|
|
||||||
|
|
||||||
class RecoveryState():
|
class RecoveryState():
|
||||||
"""Object to track BlockPair objects and overall state."""
|
"""Object to track BlockPair objects and overall state."""
|
||||||
def __init__(self, mode):
|
def __init__(self, mode):
|
||||||
|
|
@ -225,113 +337,6 @@ class RecoveryState():
|
||||||
label='', data=human_readable_size(self.rescued))
|
label='', data=human_readable_size(self.rescued))
|
||||||
|
|
||||||
|
|
||||||
class BaseObj():
|
|
||||||
"""Base object used by DevObj, DirObj, and ImageObj."""
|
|
||||||
def __init__(self, path):
|
|
||||||
self.type = 'base'
|
|
||||||
self.path = os.path.realpath(path)
|
|
||||||
self.set_details()
|
|
||||||
|
|
||||||
def is_dev(self):
|
|
||||||
return self.type == 'dev'
|
|
||||||
|
|
||||||
def is_dir(self):
|
|
||||||
return self.type == 'dir'
|
|
||||||
|
|
||||||
def is_image(self):
|
|
||||||
return self.type == 'image'
|
|
||||||
|
|
||||||
def self_check(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_details(self):
|
|
||||||
self.details = {}
|
|
||||||
|
|
||||||
|
|
||||||
class DevObj(BaseObj):
|
|
||||||
"""Block device object."""
|
|
||||||
def self_check(self):
|
|
||||||
"""Verify that self.path points to a block device."""
|
|
||||||
if not pathlib.Path(self.path).is_block_device():
|
|
||||||
raise GenericError('Path "{}" is not a block device.'.format(
|
|
||||||
self.path))
|
|
||||||
|
|
||||||
def set_details(self):
|
|
||||||
"""Set details via lsblk."""
|
|
||||||
self.type = 'dev'
|
|
||||||
self.details = get_device_details(self.path)
|
|
||||||
self.name = '{name} {size} {model} {serial}'.format(
|
|
||||||
name=self.details.get('name', 'UNKNOWN'),
|
|
||||||
size=self.details.get('size', 'UNKNOWN'),
|
|
||||||
model=self.details.get('model', 'UNKNOWN'),
|
|
||||||
serial=self.details.get('serial', 'UNKNOWN'))
|
|
||||||
self.model = self.details.get('model', 'UNKNOWN')
|
|
||||||
self.model_size = self.details.get('size', 'UNKNOWN')
|
|
||||||
self.size = get_size_in_bytes(self.details.get('size', 'UNKNOWN'))
|
|
||||||
self.report = get_device_report(self.path)
|
|
||||||
self.parent = self.details.get('pkname', '')
|
|
||||||
self.label = self.details.get('label', '')
|
|
||||||
if not self.label:
|
|
||||||
# Force empty string in case it's set to None
|
|
||||||
self.label = ''
|
|
||||||
self.update_filename_prefix()
|
|
||||||
|
|
||||||
def update_filename_prefix(self):
|
|
||||||
"""Set filename prefix based on details."""
|
|
||||||
self.prefix = '{m_size}_{model}'.format(
|
|
||||||
m_size=self.model_size,
|
|
||||||
model=self.model)
|
|
||||||
if self.parent:
|
|
||||||
# Add child device details
|
|
||||||
self.prefix += '_{c_num}_{c_size}{sep}{c_label}'.format(
|
|
||||||
c_num=self.name.replace(self.parent, ''),
|
|
||||||
c_size=self.details.get('size', 'UNKNOWN'),
|
|
||||||
sep='_' if self.label else '',
|
|
||||||
c_label=self.label)
|
|
||||||
|
|
||||||
|
|
||||||
class DirObj(BaseObj):
|
|
||||||
def self_check(self):
|
|
||||||
"""Verify that self.path points to a directory."""
|
|
||||||
if not pathlib.Path(self.path).is_dir():
|
|
||||||
raise GenericError('Path "{}" is not a directory.'.format(
|
|
||||||
self.path))
|
|
||||||
|
|
||||||
def set_details(self):
|
|
||||||
"""Set details via findmnt."""
|
|
||||||
self.type = 'dir'
|
|
||||||
self.details = get_dir_details(self.path)
|
|
||||||
self.fstype = self.details.get('fstype', 'UNKNOWN')
|
|
||||||
self.name = self.path + '/'
|
|
||||||
self.size = get_size_in_bytes(self.details.get('avail', 'UNKNOWN'))
|
|
||||||
self.report = get_dir_report(self.path)
|
|
||||||
|
|
||||||
|
|
||||||
class ImageObj(BaseObj):
|
|
||||||
def self_check(self):
|
|
||||||
"""Verify that self.path points to a file."""
|
|
||||||
if not pathlib.Path(self.path).is_file():
|
|
||||||
raise GenericError('Path "{}" is not an image file.'.format(
|
|
||||||
self.path))
|
|
||||||
|
|
||||||
def set_details(self):
|
|
||||||
"""Setup loopback device, set details via lsblk, then detach device."""
|
|
||||||
self.type = 'image'
|
|
||||||
self.loop_dev = setup_loopback_device(self.path)
|
|
||||||
self.details = get_device_details(self.loop_dev)
|
|
||||||
self.details['model'] = 'ImageFile'
|
|
||||||
self.name = '{name} {size}'.format(
|
|
||||||
name=self.path[self.path.rfind('/')+1:],
|
|
||||||
size=self.details.get('size', 'UNKNOWN'))
|
|
||||||
self.prefix = '{}_ImageFile'.format(
|
|
||||||
self.details.get('size', 'UNKNOWN'))
|
|
||||||
self.size = get_size_in_bytes(self.details.get('size', 'UNKNOWN'))
|
|
||||||
self.report = get_device_report(self.loop_dev)
|
|
||||||
self.report = self.report.replace(
|
|
||||||
self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)')
|
|
||||||
run_program(['losetup', '--detach', self.loop_dev], check=False)
|
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def build_outer_panes(source, dest):
|
def build_outer_panes(source, dest):
|
||||||
"""Build top and side panes."""
|
"""Build top and side panes."""
|
||||||
|
|
@ -565,48 +570,6 @@ def mark_all_passes_pending(source):
|
||||||
child[p_num]['Done'] = False
|
child[p_num]['Done'] = False
|
||||||
|
|
||||||
|
|
||||||
def menu_clone(source_path, dest_path):
|
|
||||||
"""ddrescue cloning menu."""
|
|
||||||
|
|
||||||
# Set devices
|
|
||||||
source = select_device('source', source_path)
|
|
||||||
source['Current Pass'] = 'Pass 1'
|
|
||||||
source['Pass 1'] = {'Status': 'Pending', 'Done': False}
|
|
||||||
source['Pass 2'] = {'Status': 'Pending', 'Done': False}
|
|
||||||
source['Pass 3'] = {'Status': 'Pending', 'Done': False}
|
|
||||||
source['Recovered Size'] = 0
|
|
||||||
source['Started Recovery'] = False
|
|
||||||
source['Total Size'] = 0
|
|
||||||
source['Type'] = 'clone'
|
|
||||||
dest = select_device(
|
|
||||||
'destination', dest_path,
|
|
||||||
skip_device=source['Details'], allow_image_file=False)
|
|
||||||
dest_safety_check(source, dest)
|
|
||||||
|
|
||||||
# Show selection details
|
|
||||||
show_selection_details(source, dest)
|
|
||||||
|
|
||||||
# Set status details
|
|
||||||
set_dest_image_paths(source, dest)
|
|
||||||
get_recovery_scope_size(source)
|
|
||||||
check_dest_paths(source)
|
|
||||||
resume_from_map(source)
|
|
||||||
|
|
||||||
# Confirm
|
|
||||||
if not ask('Proceed with clone?'):
|
|
||||||
raise GenericAbort()
|
|
||||||
show_safety_check()
|
|
||||||
|
|
||||||
# Main menu
|
|
||||||
build_outer_panes(source, dest)
|
|
||||||
menu_main(source, dest)
|
|
||||||
|
|
||||||
# Done
|
|
||||||
run_program(['losetup', '-D'])
|
|
||||||
run_program(['tmux', 'kill-window'])
|
|
||||||
exit_script()
|
|
||||||
|
|
||||||
|
|
||||||
def menu_ddrescue(source_path, dest_path, run_mode):
|
def menu_ddrescue(source_path, dest_path, run_mode):
|
||||||
"""ddrescue menu."""
|
"""ddrescue menu."""
|
||||||
source = None
|
source = None
|
||||||
|
|
@ -661,46 +624,6 @@ def menu_ddrescue(source_path, dest_path, run_mode):
|
||||||
run_program(['tmux', 'kill-window'])
|
run_program(['tmux', 'kill-window'])
|
||||||
exit_script()
|
exit_script()
|
||||||
|
|
||||||
def menu_image(source_path, dest_path):
|
|
||||||
"""ddrescue imaging menu."""
|
|
||||||
|
|
||||||
# Set devices
|
|
||||||
source = select_device('source', source_path, allow_image_file=False)
|
|
||||||
source['Current Pass'] = 'Pass 1'
|
|
||||||
source['Pass 1'] = {'Status': 'Pending', 'Done': False}
|
|
||||||
source['Pass 2'] = {'Status': 'Pending', 'Done': False}
|
|
||||||
source['Pass 3'] = {'Status': 'Pending', 'Done': False}
|
|
||||||
source['Recovered Size'] = 0
|
|
||||||
source['Started Recovery'] = False
|
|
||||||
source['Total Size'] = 0
|
|
||||||
source['Type'] = 'image'
|
|
||||||
dest = select_dest_path(dest_path, skip_device=source['Details'])
|
|
||||||
dest_safety_check(source, dest)
|
|
||||||
|
|
||||||
# Select child device(s)
|
|
||||||
source['Children'] = menu_select_children(source)
|
|
||||||
set_dest_image_paths(source, dest)
|
|
||||||
get_recovery_scope_size(source)
|
|
||||||
check_dest_paths(source)
|
|
||||||
resume_from_map(source)
|
|
||||||
|
|
||||||
# Show selection details
|
|
||||||
show_selection_details(source, dest)
|
|
||||||
|
|
||||||
# Confirm
|
|
||||||
if not ask('Proceed with imaging?'):
|
|
||||||
raise GenericAbort()
|
|
||||||
|
|
||||||
# Main menu
|
|
||||||
build_outer_panes(source, dest)
|
|
||||||
menu_main(source, dest)
|
|
||||||
|
|
||||||
# Done
|
|
||||||
run_program(['losetup', '-D'])
|
|
||||||
run_program(['tmux', 'kill-window'])
|
|
||||||
exit_script()
|
|
||||||
|
|
||||||
|
|
||||||
def menu_main(source, dest):
|
def menu_main(source, dest):
|
||||||
"""Main menu is used to set ddrescue settings."""
|
"""Main menu is used to set ddrescue settings."""
|
||||||
title = '{GREEN}ddrescue TUI: Main Menu{CLEAR}\n\n'.format(**COLORS)
|
title = '{GREEN}ddrescue TUI: Main Menu{CLEAR}\n\n'.format(**COLORS)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue