diff --git a/morituri/command/cd.py b/morituri/command/cd.py index f2804689..b0a45035 100644 --- a/morituri/command/cd.py +++ b/morituri/command/cd.py @@ -34,7 +34,7 @@ from morituri.common import ( accurip, common, config, drive, gstreamer, program, task ) -from morituri.program import cdrdao, cdparanoia +from morituri.program import cdrdao, cdparanoia, utils from morituri.result import result import logging @@ -108,8 +108,8 @@ def do(self): self.device = self.options.device sys.stdout.write('Checking device %s\n' % self.device) - self.program.loadDevice(self.device) - self.program.unmountDevice(self.device) + utils.load_device(self.device) + utils.unmount_device(self.device) # first, read the normal TOC, which is fast self.ittoc = self.program.getFastToc(self.runner, @@ -140,8 +140,8 @@ def do(self): # also used by rip cd info if not getattr(self.options, 'unknown', False): - if self.eject: - self.program.ejectDevice(self.device) + logger.critical("unable to retrieve disc metadata, " + "--unknown not passed") return -1 # FIXME ????? @@ -205,8 +205,8 @@ def do(self): self.doCommand() - if self.eject: - self.program.ejectDevice(self.device) + if self.options.eject in ('success', 'always'): + utils.eject_device(self.device) def doCommand(self): pass @@ -587,8 +587,6 @@ def writeFile(handle, path, length): # write log file self.program.writeLog(discName, self.logger) - self.program.ejectDevice(self.device) - class CD(BaseCommand): summary = "handle CDs" diff --git a/morituri/command/main.py b/morituri/command/main.py index 0f123384..9ca6fa96 100644 --- a/morituri/command/main.py +++ b/morituri/command/main.py @@ -11,6 +11,7 @@ from morituri.common import common, directory from morituri.configure import configure from morituri.extern.task import task +from morituri.program.utils import eject_device import logging logger = logging.getLogger(__name__) @@ -25,9 +26,13 @@ def main(): ) map(pkg_resources.working_set.add, distributions) try: - ret = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None).do() + cmd = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None) + ret = cmd.do() except SystemError, e: - sys.stderr.write('whipper: error: %s\n' % e.args) + sys.stderr.write('whipper: error: %s\n' % e) + if (type(e) is common.EjectError and + cmd.options.eject in ('failure', 'always')): + eject_device(e.device) return 255 except ImportError, e: raise ImportError(e) @@ -76,6 +81,10 @@ def add_arguments(self): self.parser.add_argument('-h', '--help', action="store_true", dest="help", help="show this help message and exit") + self.parser.add_argument('-e', '--eject', + action="store", dest="eject", default="always", + choices=('never', 'failure', 'success', 'always'), + help="when to eject disc (default: always)") def handle_arguments(self): if self.options.help: diff --git a/morituri/command/offset.py b/morituri/command/offset.py index f8e97746..b8a86b2b 100644 --- a/morituri/command/offset.py +++ b/morituri/command/offset.py @@ -31,7 +31,7 @@ from morituri.command.basecommand import BaseCommand from morituri.common import accurip, common, config, drive, program from morituri.common import task as ctask -from morituri.program import cdrdao, cdparanoia +from morituri.program import cdrdao, cdparanoia, utils from morituri.extern.task import task @@ -88,8 +88,8 @@ def do(self): # if necessary, load and unmount sys.stdout.write('Checking device %s\n' % device) - prog.loadDevice(device) - prog.unmountDevice(device) + utils.load_device(device) + utils.unmount_device(device) # first get the Table Of Contents of the CD t = cdrdao.ReadTOCTask(device) diff --git a/morituri/common/common.py b/morituri/common/common.py index b7d7ecd7..d597e4bb 100644 --- a/morituri/common/common.py +++ b/morituri/common/common.py @@ -39,6 +39,19 @@ BYTES_PER_FRAME = SAMPLES_PER_FRAME * 4 +class EjectError(SystemError): + """ + Possibly ejects the drive in command.main. + """ + def __init__(self, device, *args): + """ + args is a tuple used by BaseException.__str__ + device is the device path to eject + """ + self.args = args + self.device = device + + def msfToFrames(msf): """ Converts a string value in MM:SS:FF to frames. diff --git a/morituri/common/program.py b/morituri/common/program.py index 1d40cd27..9f65827b 100644 --- a/morituri/common/program.py +++ b/morituri/common/program.py @@ -90,32 +90,6 @@ def setWorkingDirectory(self, workingDirectory): logger.info('Changing to working directory %s' % workingDirectory) os.chdir(workingDirectory) - def loadDevice(self, device): - """ - Load the given device. - """ - os.system('eject -t %s' % device) - - def ejectDevice(self, device): - """ - Eject the given device. - """ - os.system('eject %s' % device) - - def unmountDevice(self, device): - """ - Unmount the given device if it is mounted, as happens with automounted - data tracks. - - If the given device is a symlink, the target will be checked. - """ - device = os.path.realpath(device) - logger.debug('possibly unmount real path %r' % device) - proc = open('/proc/mounts').read() - if device in proc: - print 'Device %s is mounted, unmounting' % device - os.system('umount %s' % device) - def getFastToc(self, runner, toc_pickle, device): """ Retrieve the normal TOC table from a toc pickle or the drive. diff --git a/morituri/program/cdrdao.py b/morituri/program/cdrdao.py index 97a79289..682d1a6d 100644 --- a/morituri/program/cdrdao.py +++ b/morituri/program/cdrdao.py @@ -1,8 +1,9 @@ import os import re import tempfile -from subprocess import check_call, Popen, PIPE, CalledProcessError +from subprocess import Popen, PIPE +from morituri.common.common import EjectError from morituri.image.toc import TocFile import logging @@ -27,12 +28,18 @@ def read_toc(device, fast_toc=False): cmd = [CDRDAO, 'read-toc'] + (['--fast-toc'] if fast_toc else []) + [ '--device', device, tocfile] # PIPE is the closest to >/dev/null we can get - try: - check_call(cmd, stdout=PIPE, stderr=PIPE) - except CalledProcessError, e: - logger.warning('cdrdao read-toc failed: return code is non-zero: ' + - str(e.returncode)) - raise e + logger.debug("executing %r", cmd) + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + _, stderr = p.communicate() + if p.returncode != 0: + msg = 'cdrdao read-toc failed: return code is non-zero: ' + \ + str(p.returncode) + logger.critical(msg) + # Gracefully handle missing disc + if "ERROR: Unit not ready, giving up." in stderr: + raise EjectError(device, "no disc detected") + raise IOError(msg) + toc = TocFile(tocfile) toc.parse() os.unlink(tocfile) diff --git a/morituri/program/utils.py b/morituri/program/utils.py new file mode 100644 index 00000000..8acc80a5 --- /dev/null +++ b/morituri/program/utils.py @@ -0,0 +1,35 @@ +import os + +import logging +logger = logging.getLogger(__name__) + + +def eject_device(device): + """ + Eject the given device. + """ + logger.debug("ejecting device %s", device) + os.system('eject %s' % device) + + +def load_device(device): + """ + Load the given device. + """ + logger.debug("loading (eject -t) device %s", device) + os.system('eject -t %s' % device) + + +def unmount_device(device): + """ + Unmount the given device if it is mounted, as happens with automounted + data tracks. + + If the given device is a symlink, the target will be checked. + """ + device = os.path.realpath(device) + logger.debug('possibly unmount real path %r' % device) + proc = open('/proc/mounts').read() + if device in proc: + print 'Device %s is mounted, unmounting' % device + os.system('umount %s' % device)