Updated ddrescue-tui tmux pane size handling
This commit is contained in:
parent
ad15cdad56
commit
62b8e51705
3 changed files with 130 additions and 66 deletions
|
|
@ -8,8 +8,10 @@ import signal
|
||||||
import stat
|
import stat
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
from functions.common import *
|
from functions.common import *
|
||||||
from functions.data import *
|
from functions.data import *
|
||||||
|
from functions.tmux import *
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -30,6 +32,11 @@ DDRESCUE_SETTINGS = {
|
||||||
}
|
}
|
||||||
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
|
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
|
||||||
SIDE_PANE_WIDTH = 21
|
SIDE_PANE_WIDTH = 21
|
||||||
|
TMUX_LAYOUT = OrderedDict({
|
||||||
|
'Source': {'y': 2, 'Check': True},
|
||||||
|
'Started': {'x': SIDE_PANE_WIDTH, 'Check': True},
|
||||||
|
'Progress': {'x': SIDE_PANE_WIDTH, 'Check': True},
|
||||||
|
})
|
||||||
USAGE = """ {script_name} clone [source [destination]]
|
USAGE = """ {script_name} clone [source [destination]]
|
||||||
{script_name} image [source [destination]]
|
{script_name} image [source [destination]]
|
||||||
(e.g. {script_name} clone /dev/sda /dev/sdb)
|
(e.g. {script_name} clone /dev/sda /dev/sdb)
|
||||||
|
|
@ -276,6 +283,7 @@ class RecoveryState():
|
||||||
self.current_pass_str = '0: Initializing'
|
self.current_pass_str = '0: Initializing'
|
||||||
self.settings = DDRESCUE_SETTINGS.copy()
|
self.settings = DDRESCUE_SETTINGS.copy()
|
||||||
self.finished = False
|
self.finished = False
|
||||||
|
self.panes = {}
|
||||||
self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
|
self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
|
||||||
self.rescued = 0
|
self.rescued = 0
|
||||||
self.resumed = False
|
self.resumed = False
|
||||||
|
|
@ -425,46 +433,22 @@ class RecoveryState():
|
||||||
# Functions
|
# Functions
|
||||||
def build_outer_panes(state):
|
def build_outer_panes(state):
|
||||||
"""Build top and side panes."""
|
"""Build top and side panes."""
|
||||||
clear_screen()
|
state.panes['Source'] = tmux_split_window(
|
||||||
result = run_program(['tput', 'cols'])
|
behind=True, vertical=True, lines=2,
|
||||||
width = int(
|
text='{BLUE}Source{CLEAR}'.format(**COLORS))
|
||||||
(int(result.stdout.decode().strip()) - SIDE_PANE_WIDTH) / 2) - 2
|
state.panes['Started'] = tmux_split_window(
|
||||||
|
lines=SIDE_PANE_WIDTH, target_pane=state.panes['Source'],
|
||||||
# Top panes
|
text='{BLUE}Started{CLEAR}\n{s}'.format(
|
||||||
source_str = state.source.name
|
s=time.strftime("%Y-%m-%d %H:%M %Z"),
|
||||||
if len(source_str) > width:
|
|
||||||
source_str = '{}...'.format(source_str[:width-3])
|
|
||||||
dest_str = state.dest.name
|
|
||||||
if len(dest_str) > width:
|
|
||||||
if state.mode == 'clone':
|
|
||||||
dest_str = '{}...'.format(dest_str[:width-3])
|
|
||||||
else:
|
|
||||||
dest_str = '...{}'.format(dest_str[-width+3:])
|
|
||||||
source_pane = tmux_splitw(
|
|
||||||
'-bdvl', '2',
|
|
||||||
'-PF', '#D',
|
|
||||||
'echo-and-hold "{BLUE}Source{CLEAR}\n{text}"'.format(
|
|
||||||
text=source_str,
|
|
||||||
**COLORS))
|
|
||||||
tmux_splitw(
|
|
||||||
'-t', source_pane,
|
|
||||||
'-dhl', '{}'.format(SIDE_PANE_WIDTH),
|
|
||||||
'echo-and-hold "{BLUE}Started{CLEAR}\n{text}"'.format(
|
|
||||||
text=time.strftime("%Y-%m-%d %H:%M %Z"),
|
|
||||||
**COLORS))
|
|
||||||
tmux_splitw(
|
|
||||||
'-t', source_pane,
|
|
||||||
'-dhp', '50',
|
|
||||||
'echo-and-hold "{BLUE}Destination{CLEAR}\n{text}"'.format(
|
|
||||||
text=dest_str,
|
|
||||||
**COLORS))
|
**COLORS))
|
||||||
|
state.panes['Destination'] = tmux_split_window(
|
||||||
|
percent=50, target_pane=state.panes['Source'],
|
||||||
|
text='{BLUE}Destination{CLEAR}'.format(**COLORS))
|
||||||
|
|
||||||
# Side pane
|
# Side pane
|
||||||
update_sidepane(state)
|
update_sidepane(state)
|
||||||
tmux_splitw(
|
state.panes['Progress'] = tmux_split_window(
|
||||||
'-dhl', str(SIDE_PANE_WIDTH),
|
lines=SIDE_PANE_WIDTH, watch=state.progress_out)
|
||||||
'watch', '--color', '--no-title', '--interval', '1',
|
|
||||||
'cat', state.progress_out)
|
|
||||||
|
|
||||||
|
|
||||||
def create_path_obj(path):
|
def create_path_obj(path):
|
||||||
|
|
@ -491,6 +475,94 @@ def double_confirm_clone():
|
||||||
return ask('Asking again to confirm, is this correct?')
|
return ask('Asking again to confirm, is this correct?')
|
||||||
|
|
||||||
|
|
||||||
|
def fix_tmux_panes(state, forced=False):
|
||||||
|
"""Fix pane sizes if the winodw has been resized."""
|
||||||
|
needs_fixed = False
|
||||||
|
|
||||||
|
# Check layout
|
||||||
|
for k, v in TMUX_LAYOUT.items():
|
||||||
|
if not v.get('Check'):
|
||||||
|
# Not concerned with the size of this pane
|
||||||
|
continue
|
||||||
|
# Get target
|
||||||
|
target = None
|
||||||
|
if k != 'Current':
|
||||||
|
if k not in state.panes:
|
||||||
|
# Skip missing panes
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
target = state.panes[k]
|
||||||
|
|
||||||
|
# Check pane size
|
||||||
|
x, y = tmux_get_pane_size(pane_id=target)
|
||||||
|
if v.get('x', False) and v['x'] != x:
|
||||||
|
needs_fixed = True
|
||||||
|
if v.get('y', False) and v['y'] != y:
|
||||||
|
needs_fixed = True
|
||||||
|
|
||||||
|
# Bail?
|
||||||
|
if not needs_fixed and not forced:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove Destination pane (temporarily)
|
||||||
|
tmux_kill_pane(state.panes['Destination'])
|
||||||
|
|
||||||
|
# Update layout
|
||||||
|
for k, v in TMUX_LAYOUT.items():
|
||||||
|
# Get target
|
||||||
|
target = None
|
||||||
|
if k != 'Current':
|
||||||
|
if k not in state.panes:
|
||||||
|
# Skip missing panes
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
target = state.panes[k]
|
||||||
|
|
||||||
|
# Resize pane
|
||||||
|
tmux_resize_pane(pane_id=target, **v)
|
||||||
|
|
||||||
|
# Calc Source/Destination pane sizes
|
||||||
|
width, height = tmux_get_pane_size()
|
||||||
|
width = int(width / 2) - 1
|
||||||
|
|
||||||
|
# Update Source string
|
||||||
|
source_str = state.source.name
|
||||||
|
if len(source_str) > width:
|
||||||
|
source_str = '{}...'.format(source_str[:width-3])
|
||||||
|
|
||||||
|
# Update Destination string
|
||||||
|
dest_str = state.dest.name
|
||||||
|
if len(dest_str) > width:
|
||||||
|
if state.mode == 'clone':
|
||||||
|
dest_str = '{}...'.format(dest_str[:width-3])
|
||||||
|
else:
|
||||||
|
dest_str = '...{}'.format(dest_str[-width+3:])
|
||||||
|
|
||||||
|
# Rebuild Source/Destination panes
|
||||||
|
tmux_update_pane(
|
||||||
|
pane_id=state.panes['Source'],
|
||||||
|
text='{BLUE}Source{CLEAR}\n{s}'.format(
|
||||||
|
s=source_str, **COLORS))
|
||||||
|
state.panes['Destination'] = tmux_split_window(
|
||||||
|
percent=50, target_pane=state.panes['Source'],
|
||||||
|
text='{BLUE}Destination{CLEAR}\n{s}'.format(
|
||||||
|
s=dest_str, **COLORS))
|
||||||
|
|
||||||
|
if 'SMART' in state.panes:
|
||||||
|
# Calc SMART/ddrescue/Journal panes sizes
|
||||||
|
ratio = [12, 22, 4]
|
||||||
|
width, height = tmux_get_pane_size(pane_id=state.panes['Progress'])
|
||||||
|
height -= 2
|
||||||
|
total = sum(ratio)
|
||||||
|
p_ratio = [int((x/total) * height) for x in ratio]
|
||||||
|
p_ratio[1] = height - p_ratio[0] - p_ratio[2]
|
||||||
|
|
||||||
|
# Resize SMART/Journal panes
|
||||||
|
tmux_resize_pane(state.panes['SMART'], y=ratio[0])
|
||||||
|
tmux_resize_pane(y=ratio[1])
|
||||||
|
tmux_resize_pane(state.panes['Journal'], y=ratio[2])
|
||||||
|
|
||||||
|
|
||||||
def get_device_details(dev_path):
|
def get_device_details(dev_path):
|
||||||
"""Get device details via lsblk, returns JSON dict."""
|
"""Get device details via lsblk, returns JSON dict."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -687,7 +759,9 @@ def menu_ddrescue(source_path, dest_path, run_mode):
|
||||||
raise GenericAbort()
|
raise GenericAbort()
|
||||||
|
|
||||||
# Main menu
|
# Main menu
|
||||||
|
clear_screen()
|
||||||
build_outer_panes(state)
|
build_outer_panes(state)
|
||||||
|
fix_tmux_panes(state, forced=True)
|
||||||
menu_main(state)
|
menu_main(state)
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
|
|
@ -877,29 +951,24 @@ def run_ddrescue(state, pass_settings):
|
||||||
pause('Press Enter to return to main menu...')
|
pause('Press Enter to return to main menu...')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Set heights
|
|
||||||
# NOTE: 12/33 is based on min heights for SMART/ddrescue panes (12+22+1sep)
|
|
||||||
result = run_program(['tput', 'lines'])
|
|
||||||
height = int(result.stdout.decode().strip())
|
|
||||||
height_smart = int(height * (8 / 33))
|
|
||||||
height_journal = int(height * (4 / 33))
|
|
||||||
height_ddrescue = height - height_smart - height_journal
|
|
||||||
|
|
||||||
# Show SMART status
|
# Show SMART status
|
||||||
smart_dev = state.source_path
|
smart_dev = state.source_path
|
||||||
if state.source.parent:
|
if state.source.parent:
|
||||||
smart_dev = state.source.parent
|
smart_dev = state.source.parent
|
||||||
smart_pane = tmux_splitw(
|
smart_cmd = [
|
||||||
'-bdvl', str(height_smart),
|
'watch', '--color', '--no-title', '--interval', '5',
|
||||||
'-PF', '#D',
|
'ddrescue-tui-smart-display', smart_dev,
|
||||||
'watch', '--color', '--no-title', '--interval', '300',
|
]
|
||||||
'ddrescue-tui-smart-display', smart_dev)
|
state.panes['SMART'] = tmux_split_window(
|
||||||
|
behind=True, lines=12, vertical=True, command=smart_cmd)
|
||||||
|
|
||||||
# Show systemd journal output
|
# Show systemd journal output
|
||||||
journal_pane = tmux_splitw(
|
state.panes['Journal'] = tmux_split_window(
|
||||||
'-dvl', str(height_journal),
|
lines=4, vertical=True,
|
||||||
'-PF', '#D',
|
command=['sudo', 'journalctl', '-f'])
|
||||||
'journalctl', '-f')
|
|
||||||
|
# Fix layout
|
||||||
|
fix_tmux_panes(state, forced=True)
|
||||||
|
|
||||||
# Run pass for each block-pair
|
# Run pass for each block-pair
|
||||||
for bp in state.block_pairs:
|
for bp in state.block_pairs:
|
||||||
|
|
@ -931,8 +1000,9 @@ def run_ddrescue(state, pass_settings):
|
||||||
while True:
|
while True:
|
||||||
bp.update_progress(state.current_pass)
|
bp.update_progress(state.current_pass)
|
||||||
update_sidepane(state)
|
update_sidepane(state)
|
||||||
|
fix_tmux_panes(state)
|
||||||
try:
|
try:
|
||||||
ddrescue_proc.wait(timeout=10)
|
ddrescue_proc.wait(timeout=1)
|
||||||
sleep(2)
|
sleep(2)
|
||||||
bp.update_progress(state.current_pass)
|
bp.update_progress(state.current_pass)
|
||||||
update_sidepane(state)
|
update_sidepane(state)
|
||||||
|
|
@ -967,8 +1037,9 @@ def run_ddrescue(state, pass_settings):
|
||||||
if str(return_code) != '0':
|
if str(return_code) != '0':
|
||||||
# Pause on errors
|
# Pause on errors
|
||||||
pause('Press Enter to return to main menu... ')
|
pause('Press Enter to return to main menu... ')
|
||||||
run_program(['tmux', 'kill-pane', '-t', smart_pane])
|
|
||||||
run_program(['tmux', 'kill-pane', '-t', journal_pane])
|
# Cleanup
|
||||||
|
tmux_kill_pane(state.panes['SMART'], state.panes['Journal'])
|
||||||
|
|
||||||
|
|
||||||
def select_parts(source_device):
|
def select_parts(source_device):
|
||||||
|
|
@ -1219,13 +1290,6 @@ def show_usage(script_name):
|
||||||
pause()
|
pause()
|
||||||
|
|
||||||
|
|
||||||
def tmux_splitw(*args):
|
|
||||||
"""Run tmux split-window command and return output as str."""
|
|
||||||
cmd = ['tmux', 'split-window', *args]
|
|
||||||
result = run_program(cmd)
|
|
||||||
return result.stdout.decode().strip()
|
|
||||||
|
|
||||||
|
|
||||||
def update_sidepane(state):
|
def update_sidepane(state):
|
||||||
"""Update progress file for side pane."""
|
"""Update progress file for side pane."""
|
||||||
output = []
|
output = []
|
||||||
|
|
|
||||||
|
|
@ -619,7 +619,7 @@ def build_status_string(label, status, info_label=False):
|
||||||
**COLORS)
|
**COLORS)
|
||||||
|
|
||||||
def fix_tmux_panes(state, tmux_layout):
|
def fix_tmux_panes(state, tmux_layout):
|
||||||
"""Fix pane sizes in case the window has been resized."""
|
"""Fix pane sizes if the window has been resized."""
|
||||||
needs_fixed = False
|
needs_fixed = False
|
||||||
|
|
||||||
# Check layout
|
# Check layout
|
||||||
|
|
@ -636,7 +636,7 @@ def fix_tmux_panes(state, tmux_layout):
|
||||||
else:
|
else:
|
||||||
target = state.panes[k]
|
target = state.panes[k]
|
||||||
|
|
||||||
# Get pane size
|
# Check pane size
|
||||||
x, y = tmux_get_pane_size(pane_id=target)
|
x, y = tmux_get_pane_size(pane_id=target)
|
||||||
if v.get('x', False) and v['x'] != x:
|
if v.get('x', False) and v['x'] != x:
|
||||||
needs_fixed = True
|
needs_fixed = True
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ def tmux_split_window(
|
||||||
lines=None, percent=None,
|
lines=None, percent=None,
|
||||||
behind=False, vertical=False,
|
behind=False, vertical=False,
|
||||||
follow=False, target_pane=None,
|
follow=False, target_pane=None,
|
||||||
working_dir=None, command=None,
|
command=None, working_dir=None,
|
||||||
text=None, watch=None, watch_cmd='cat'):
|
text=None, watch=None, watch_cmd='cat'):
|
||||||
"""Run tmux split-window command and return pane_id as str."""
|
"""Run tmux split-window command and return pane_id as str."""
|
||||||
# Bail early
|
# Bail early
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue