"""Bildschirm fuer die Transfer-Laufwerke. Bewusst knapp gehalten: im Notfall soll niemand Parameter im Kopf haben. Anlegen, oeffnen, aushaengen, loeschen - vier Tasten. Wird aus pvesnap-recovery (Taste v) und aus pvesnap-explorer (F2) heraus benutzt, damit man von beiden Seiten an dieselben Laufwerke kommt. """ from __future__ import annotations import curses import os from datetime import datetime from .curses_util import (C_DIM, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL, C_WARN, confirm, fill, is_enter, is_escape, keybar, message, prompt, put, read_key) from .transfer import (DEFAULT_SIZE, TransferError, create, formatter_missing, host_mount, host_umount, human_bytes, load, remove) from .util import truncate KEYS = [ ("Enter", "Oeffnen", "Oeffn."), ("n", "Neu", "Neu"), ("u", "Aushaengen", "Aush."), ("l", "Loeschen", "Losch."), ("r", "Aktualisieren", "Aktual."), ("q", "Zurueck", "Zurueck"), ] def _wrap(text, width): words, lines, current = str(text).split(), [], "" for word in words: if len(current) + len(word) + 1 > width: lines.append(current) current = word else: current = (current + " " + word).strip() if current: lines.append(current) return lines def _pages(win, lines, title, footer="beliebige Taste = weiter"): height, width = win.getmaxyx() win.erase() fill(win, 0, " " + title, curses.color_pair(C_HEADER) | curses.A_BOLD) for row, (text, attr) in enumerate(lines[: height - 4]): put(win, row + 2, 2, truncate(text, width - 4), attr) fill(win, height - 1, " " + footer, curses.color_pair(C_FOOTER)) win.refresh() return read_key(win) def _new(win): """Ein Laufwerk anlegen. Nur zwei Fragen - mehr braucht es nicht.""" fehlt = formatter_missing() if fehlt: _pages(win, [(line, curses.color_pair(C_WARN)) for line in fehlt.split("\n")], "Geht hier nicht") return name = prompt(win, "Name (kurz, klein, z.B. dumps)") if not name: return size = prompt(win, "Groesse", DEFAULT_SIZE) if size is None: return note = prompt(win, "Wofuer ist das? (optional)", "") if note is None: note = "" height, _ = win.getmaxyx() lines = [] def progress(text): lines.append(text) fill(win, height - 2, " " + text, curses.color_pair(C_SEL)) win.refresh() try: volume = create(name, size, note=note, progress=progress) except TransferError as exc: _pages(win, [(line, curses.color_pair(C_WARN)) for line in _wrap(str(exc), 74)], "Fehlgeschlagen") return message(win, "%r angelegt - im Gast als Datentraeger %s" % (volume.name, volume.label)) def _open(win, volume, browse): """Am Host einhaengen und den Commander darauf loslassen.""" try: path = host_mount(volume.name) except TransferError as exc: _pages(win, [(line, curses.color_pair(C_WARN)) for line in _wrap(str(exc), 74)], "Nicht moeglich") return if browse is None: _pages(win, [("Eingehaengt unter:", 0), ("", 0), (" " + path, curses.color_pair(C_MARK))], "Transfer-Laufwerk %s" % volume.name) return try: browse(win, path, volume) finally: host_umount(volume.name) def screen(win, browse=None): """Die Uebersicht. `browse(win, pfad, laufwerk)` oeffnet den Commander.""" index = 0 volumes = [] reload_ = True while True: if reload_: volumes = load() reload_ = False index = max(0, min(index, len(volumes) - 1)) height, width = win.getmaxyx() win.erase() fill(win, 0, " Transfer-Laufwerke - Austausch mit der Wiederherstellung", curses.color_pair(C_HEADER) | curses.A_BOLD) if not volumes: put(win, 3, 3, "Noch kein Transfer-Laufwerk angelegt.", curses.A_BOLD) for row, text in enumerate([ "", "Ein Transfer-Laufwerk ist eine Abbilddatei, die du hier in Ruhe", "befuellst und im Notfall nur noch auswaehlst. Sie gehoert zu", "keiner Maschine und ueberlebt jede Wiederherstellung.", "", "Mit n eines anlegen.", ]): put(win, 4 + row, 3, text, curses.color_pair(C_DIM)) fehlt = formatter_missing() if fehlt: for row, line in enumerate(fehlt.split("\n")): put(win, 11 + row, 3, line, curses.color_pair(C_WARN)) else: put(win, 2, 2, "%-14s %8s %8s %-24s %s" % ("Name", "Groesse", "belegt", "Zustand", "Wofuer"), curses.A_BOLD | curses.A_UNDERLINE) rows = max(1, height - 6) top = max(0, min(index - rows + 1, len(volumes) - rows)) for row in range(min(rows, len(volumes) - top)): volume = volumes[top + row] attr = curses.color_pair(C_SEL) if top + row == index else 0 if not attr and volume.state == "guest": attr = curses.color_pair(C_MARK) elif not attr and volume.state == "host": attr = curses.color_pair(C_OK) fill(win, row + 3, " %-14s %8s %8s %-24s %s" % (truncate(volume.name, 14), human_bytes(volume.size), human_bytes(volume.used_bytes), volume.state_text, truncate(volume.note, 28)), attr) for offset, line in enumerate(keybar(width, KEYS)): fill(win, height - 2 + offset, " " + line, curses.color_pair(C_FOOTER)) win.refresh() key = read_key(win) current = volumes[index] if volumes else None if key in ("q", "Q") or is_escape(key): return if key in ("n", "N"): _new(win) reload_ = True elif key in ("r", "R"): reload_ = True elif key == curses.KEY_UP and volumes: index = (index - 1) % len(volumes) elif key == curses.KEY_DOWN and volumes: index = (index + 1) % len(volumes) elif current is None: continue elif is_enter(key): _open(win, current, browse) reload_ = True elif key in ("u", "U"): try: host_umount(current.name) except TransferError as exc: message(win, str(exc), error=True) reload_ = True elif key in ("l", "L"): if confirm(win, "%r endgueltig loeschen? Der Inhalt ist dann weg." % current.name): try: remove(current.name) except TransferError as exc: message(win, str(exc), error=True) reload_ = True def choose(win, preselected=()): """Mehrfachauswahl fuer die Optionsmaske. Liefert eine Liste von Namen.""" volumes = [v for v in load()] if not volumes: message(win, "Es gibt noch keine Transfer-Laufwerke. In der Uebersicht " "mit v anlegen.", error=True) return list(preselected) chosen = set(preselected) index = 0 while True: height, width = win.getmaxyx() win.erase() fill(win, 0, " Welche Transfer-Laufwerke anhaengen?", curses.color_pair(C_HEADER) | curses.A_BOLD) put(win, 2, 2, "%-3s %-14s %8s %-24s %s" % ("", "Name", "Groesse", "Zustand", "Wofuer"), curses.A_BOLD | curses.A_UNDERLINE) for row, volume in enumerate(volumes[: height - 7]): marker = "[x]" if volume.name in chosen else "[ ]" attr = curses.color_pair(C_SEL) if row == index else 0 if not volume.free and not attr: attr = curses.color_pair(C_WARN) fill(win, row + 3, " %-3s %-14s %8s %-24s %s" % (marker, truncate(volume.name, 14), human_bytes(volume.size), volume.state_text, truncate(volume.note, 26)), attr) put(win, height - 4, 3, "Belegte Laufwerke lassen sich nicht anhaengen - erst dort aushaengen.", curses.color_pair(C_DIM)) for offset, line in enumerate(keybar(width, [ ("Leertaste", "Aus/Abwaehlen", "Waehlen"), ("Enter", "Uebernehmen", "OK"), ("q", "Abbrechen", "Abbr.")])): fill(win, height - 2 + offset, " " + line, curses.color_pair(C_FOOTER)) win.refresh() key = read_key(win) if key == curses.KEY_UP: index = (index - 1) % len(volumes) elif key == curses.KEY_DOWN: index = (index + 1) % len(volumes) elif key == " ": volume = volumes[index] if volume.name in chosen: chosen.discard(volume.name) elif not volume.free: message(win, "%r ist gerade %s." % (volume.name, volume.state_text), error=True) else: chosen.add(volume.name) elif is_enter(key): return [v.name for v in volumes if v.name in chosen] elif is_escape(key) or key in ("q", "Q"): return list(preselected) def summary(names): """Kurzfassung fuer die Optionsmaske.""" if not names: return "keine" return ", ".join(names) def describe(volume): """Mehrzeilige Beschreibung fuer Detailansichten.""" lines = ["Transfer-Laufwerk %s" % volume.name, " Abbild : %s" % volume.image, " Groesse : %s (belegt: %s)" % (human_bytes(volume.size), human_bytes(volume.used_bytes)), " Im Gast : Datentraeger %s" % volume.label, " Zustand : %s" % volume.state_text] if volume.note: lines.append(" Wofuer : %s" % volume.note) if volume.created: lines.append(" Angelegt : %s" % datetime.fromtimestamp(volume.created).strftime("%d.%m.%Y %H:%M")) return lines