Even better exception handling

This commit is contained in:
2Shirt 2018-07-26 18:54:31 -06:00
parent a19ac4772b
commit 4047b956f5
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
2 changed files with 37 additions and 51 deletions

View file

@ -43,15 +43,13 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except GenericAbort as ga: except GenericAbort:
if str(ga):
print_warning(str(ga))
abort() abort()
except GenericError as ge: except GenericError as ge:
msg = 'Generic Error'
if str(ge): if str(ge):
print_error(str(ge)) msg = str(ge)
else: print_error(msg)
print_error('Generic Error?')
abort() abort()
except SystemExit: except SystemExit:
pass pass

View file

@ -147,24 +147,24 @@ class RecoveryState():
if self.mode == 'clone': if self.mode == 'clone':
# Cloning safety checks # Cloning safety checks
if source.is_dir(): if source.is_dir():
raise GenericAbort('Invalid source "{}"'.format( raise GenericError('Invalid source "{}"'.format(
source.path)) source.path))
elif not dest.is_dev(): elif not dest.is_dev():
raise GenericAbort('Invalid destination "{}"'.format( raise GenericError('Invalid destination "{}"'.format(
dest.path)) dest.path))
elif source.size > dest.size: elif source.size > dest.size:
raise GenericAbort( raise GenericError(
'Destination is too small, refusing to continue.') 'Destination is too small, refusing to continue.')
else: else:
# Imaging safety checks # Imaging safety checks
if not source.is_dev(): if not source.is_dev():
raise GenericAbort('Invalid source "{}"'.format( raise GenericError('Invalid source "{}"'.format(
source.path)) source.path))
elif not dest.is_dir(): elif not dest.is_dir():
raise GenericAbort('Invalid destination "{}"'.format( raise GenericError('Invalid destination "{}"'.format(
dest.path)) dest.path))
elif (source.size * 1.2) > dest.size: elif (source.size * 1.2) > dest.size:
raise GenericAbort( raise GenericError(
'Destination is too small, refusing to continue.') 'Destination is too small, refusing to continue.')
elif dest.fstype.lower() not in RECOMMENDED_FSTYPES: elif dest.fstype.lower() not in RECOMMENDED_FSTYPES:
print_error( print_error(
@ -174,12 +174,12 @@ class RecoveryState():
' / '.join(RECOMMENDED_FSTYPES).upper())) ' / '.join(RECOMMENDED_FSTYPES).upper()))
print_standard(' ') print_standard(' ')
if not ask('Proceed anyways? (Strongly discouraged)'): if not ask('Proceed anyways? (Strongly discouraged)'):
raise GenericAbort('Aborted.') raise GenericAbort()
elif not is_writable_dir(dest): elif not is_writable_dir(dest):
raise GenericAbort( raise GenericError(
'Destination is not writable, refusing to continue.') 'Destination is not writable, refusing to continue.')
elif not is_writable_filesystem(dest): elif not is_writable_filesystem(dest):
raise GenericAbort( raise GenericError(
'Destination is mounted read-only, refusing to continue.') 'Destination is mounted read-only, refusing to continue.')
# Safety checks passed # Safety checks passed
@ -188,11 +188,7 @@ class RecoveryState():
def self_checks(self): def self_checks(self):
"""Run self-checks for each BlockPair object.""" """Run self-checks for each BlockPair object."""
for bp in self.block_pairs: for bp in self.block_pairs:
try: bp.self_check()
bp.self_check()
except GenericError as err:
print_error(err)
raise GenericAbort('Aborted.')
def set_pass_num(self): def set_pass_num(self):
"""Set current pass based on all block-pair's progress.""" """Set current pass based on all block-pair's progress."""
@ -252,7 +248,8 @@ class DevObj(BaseObj):
def self_check(self): def self_check(self):
"""Verify that self.path points to a block device.""" """Verify that self.path points to a block device."""
if not pathlib.Path(self.path).is_block_device(): if not pathlib.Path(self.path).is_block_device():
raise GenericError('TODO') raise GenericError('Path "{}" is not a block device.'.format(
self.path))
def set_details(self): def set_details(self):
"""Set details via lsblk.""" """Set details via lsblk."""
@ -288,7 +285,8 @@ class DirObj(BaseObj):
def self_check(self): def self_check(self):
"""Verify that self.path points to a directory.""" """Verify that self.path points to a directory."""
if not pathlib.Path(self.path).is_dir(): if not pathlib.Path(self.path).is_dir():
raise GenericError('TODO') raise GenericError('Path "{}" is not a directory.'.format(
self.path))
def set_details(self): def set_details(self):
"""Set details via findmnt.""" """Set details via findmnt."""
@ -304,7 +302,8 @@ class ImageObj(BaseObj):
def self_check(self): def self_check(self):
"""Verify that self.path points to a file.""" """Verify that self.path points to a file."""
if not pathlib.Path(self.path).is_file(): if not pathlib.Path(self.path).is_file():
raise GenericError('TODO') raise GenericError('Path "{}" is not an image file.'.format(
self.path))
def set_details(self): def set_details(self):
"""Setup loopback device, set details via lsblk, then detach device.""" """Setup loopback device, set details via lsblk, then detach device."""
@ -322,11 +321,6 @@ class ImageObj(BaseObj):
# Functions # Functions
def abort_ddrescue_tui():
run_program(['losetup', '-D'])
abort()
def build_outer_panes(source, dest): def build_outer_panes(source, dest):
"""Build top and side panes.""" """Build top and side panes."""
clear_screen() clear_screen()
@ -369,7 +363,7 @@ def create_path_obj(path):
elif pathlib.Path(self.path).is_file(): elif pathlib.Path(self.path).is_file():
obj = ImageObj(path) obj = ImageObj(path)
else: else:
raise GenericAbort('TODO') raise GenericError('Invalid path "{}"'.format(path))
return obj return obj
@ -551,7 +545,7 @@ def menu_clone(source_path, dest_path):
# Confirm # Confirm
if not ask('Proceed with clone?'): if not ask('Proceed with clone?'):
abort_ddrescue_tui() raise GenericAbort()
show_safety_check() show_safety_check()
# Main menu # Main menu
@ -634,7 +628,7 @@ def menu_image(source_path, dest_path):
# Confirm # Confirm
if not ask('Proceed with imaging?'): if not ask('Proceed with imaging?'):
abort_ddrescue_tui() raise GenericAbort()
# Main menu # Main menu
build_outer_panes(source, dest) build_outer_panes(source, dest)
@ -805,7 +799,7 @@ def menu_select_children(source):
elif selection == 'P' and one_or_more_devs_selected: elif selection == 'P' and one_or_more_devs_selected:
break break
elif selection == 'Q': elif selection == 'Q':
abort_ddrescue_tui() raise GenericAbort()
# Check selection # Check selection
selected_children = [{ selected_children = [{
@ -833,8 +827,8 @@ def menu_select_device(title='Which device?', skip_device={}):
result = run_program(cmd) result = run_program(cmd)
json_data = json.loads(result.stdout.decode()) json_data = json.loads(result.stdout.decode())
except CalledProcessError: except CalledProcessError:
print_error('Failed to get device details for {}'.format(dev_path)) raise GenericError(
abort_ddrescue_tui() 'Failed to get device details for {}'.format(dev_path))
# Build menu # Build menu
dev_options = [] dev_options = []
@ -854,8 +848,7 @@ def menu_select_device(title='Which device?', skip_device={}):
'Disabled': disable_dev}) 'Disabled': disable_dev})
dev_options = sorted(dev_options, key=itemgetter('Name')) dev_options = sorted(dev_options, key=itemgetter('Name'))
if not dev_options: if not dev_options:
print_error('No devices available.') raise GenericError('No devices available.')
abort_ddrescue_tui()
# Show Menu # Show Menu
actions = [{'Name': 'Quit', 'Letter': 'Q'}] actions = [{'Name': 'Quit', 'Letter': 'Q'}]
@ -868,7 +861,7 @@ def menu_select_device(title='Which device?', skip_device={}):
if selection.isnumeric(): if selection.isnumeric():
return dev_options[int(selection)-1]['Path'] return dev_options[int(selection)-1]['Path']
elif selection == 'Q': elif selection == 'Q':
abort_ddrescue_tui() raise GenericAbort()
def menu_select_path(skip_device={}): def menu_select_path(skip_device={}):
@ -890,7 +883,7 @@ def menu_select_path(skip_device={}):
action_entries=actions) action_entries=actions)
if selection == 'Q': if selection == 'Q':
abort_ddrescue_tui() raise GenericAbort()
elif selection.isnumeric(): elif selection.isnumeric():
index = int(selection) - 1 index = int(selection) - 1
if path_options[index]['Path']: if path_options[index]['Path']:
@ -928,7 +921,7 @@ def menu_select_path(skip_device={}):
if selection.isnumeric(): if selection.isnumeric():
s_path = vol_options[int(selection)-1]['Path'] s_path = vol_options[int(selection)-1]['Path']
elif selection == 'Q': elif selection == 'Q':
abort_ddrescue_tui() raise GenericAbort()
elif path_options[index]['Name'] == 'Enter manually': elif path_options[index]['Name'] == 'Enter manually':
# Manual entry # Manual entry
@ -1007,8 +1000,7 @@ def read_map_file(map_path):
try: try:
map_data[m.group('key')] = float(m.group('value')) map_data[m.group('key')] = float(m.group('value'))
except ValueError: except ValueError:
print_error('Failed to read map data') raise GenericError('Failed to read map data')
abort_ddrescue_tui()
m = re.match(r'.*current status:\s+(?P<status>.*)', line.strip()) m = re.match(r'.*current status:\s+(?P<status>.*)', line.strip())
if m: if m:
map_data['pass completed'] = bool(m.group('status') == 'finished') map_data['pass completed'] = bool(m.group('status') == 'finished')
@ -1133,8 +1125,7 @@ def select_dest_path(provided_path=None, skip_device={}):
# Check path # Check path
if not pathlib.Path(dest['Path']).is_dir(): if not pathlib.Path(dest['Path']).is_dir():
print_error('Invalid path "{}"'.format(dest['Path'])) raise GenericError('Invalid path "{}"'.format(dest['Path']))
abort_ddrescue_tui()
# Create ticket folder # Create ticket folder
if ask('Create ticket folder?'): if ask('Create ticket folder?'):
@ -1144,9 +1135,8 @@ def select_dest_path(provided_path=None, skip_device={}):
try: try:
os.makedirs(dest['Path'], exist_ok=True) os.makedirs(dest['Path'], exist_ok=True)
except OSError: except OSError:
print_error('Failed to create folder "{}"'.format( raise GenericError(
dest['Path'])) 'Failed to create folder "{}"'.format(dest['Path']))
abort_ddrescue_tui()
# Set display name # Set display name
result = run_program(['tput', 'cols']) result = run_program(['tput', 'cols'])
@ -1181,8 +1171,7 @@ def select_device(description='device', provided_path=None,
dev['Dev Path'] = setup_loopback_device(dev['Path']) dev['Dev Path'] = setup_loopback_device(dev['Path'])
dev['Is Image'] = True dev['Is Image'] = True
else: else:
print_error('Invalid {} "{}"'.format(description, dev['Path'])) raise GenericError('Invalid {} "{}"'.format(description, dev['Path']))
abort_ddrescue_tui()
# Get device details # Get device details
dev['Details'] = get_device_details(dev['Dev Path']) dev['Details'] = get_device_details(dev['Dev Path'])
@ -1237,8 +1226,7 @@ def setup_loopback_device(source_path):
dev_path = out.stdout.decode().strip() dev_path = out.stdout.decode().strip()
sleep(1) sleep(1)
except CalledProcessError: except CalledProcessError:
print_error('Failed to setup loopback device for source.') raise GenericError('Failed to setup loopback device for source.')
abort_ddrescue_tui()
else: else:
return dev_path return dev_path
@ -1272,7 +1260,7 @@ def show_safety_check():
print_warning('This is irreversible and will lead ' print_warning('This is irreversible and will lead '
'to {CLEAR}{RED}DATA LOSS.'.format(**COLORS)) 'to {CLEAR}{RED}DATA LOSS.'.format(**COLORS))
if not ask('Asking again to confirm, is this correct?'): if not ask('Asking again to confirm, is this correct?'):
abort_ddrescue_tui() raise GenericAbort()
def show_selection_details(source, dest): def show_selection_details(source, dest):