Skip to content

Commit

Permalink
Provide progress of cdrdao toc reading to console
Browse files Browse the repository at this point in the history
  • Loading branch information
jtl999 committed Dec 8, 2018
1 parent 0dfb8a1 commit 8361301
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 92 deletions.
1 change: 0 additions & 1 deletion whipper/command/cd.py
Expand Up @@ -96,7 +96,6 @@ def do(self):
utils.unmount_device(self.device)

# first, read the normal TOC, which is fast
print("Reading TOC...")
self.ittoc = self.program.getFastToc(self.runner, self.device)

# already show us some info based on this
Expand Down
38 changes: 15 additions & 23 deletions whipper/common/program.py
Expand Up @@ -100,12 +100,17 @@ def getFastToc(self, runner, device):
sys.stdout.write('Warning: cdrdao older than 1.2.3 has a '
'pre-gap length bug.\n'
'See http://sourceforge.net/tracker/?func=detail&aid=604751&group_id=2171&atid=102171\n') # noqa: E501
toc = cdrdao.ReadTOCTask(device).table


t = cdrdao.ReadTOC_Task(device)
runner.run(t)
toc = t.toc.table

assert toc.hasTOC()
return toc

def getTable(self, runner, cddbdiscid, mbdiscid, device, offset,
out_path):
toc_path):
"""
Retrieve the Table either from the cache or the drive.
Expand All @@ -115,27 +120,14 @@ def getTable(self, runner, cddbdiscid, mbdiscid, device, offset,
ptable = tcache.get(cddbdiscid, mbdiscid)
itable = None
tdict = {}

# Ignore old cache, since we do not know what offset it used.
if isinstance(ptable.object, dict):
tdict = ptable.object

if offset in tdict:
itable = tdict[offset]

if not itable:
logger.debug('getTable: cddbdiscid %s, mbdiscid %s not '
'in cache for offset %s, reading table' % (
cddbdiscid, mbdiscid, offset))
t = cdrdao.ReadTableTask(device, out_path)
itable = t.table
tdict[offset] = itable
ptable.persist(tdict)
logger.debug('getTable: read table %r' % itable)
else:
logger.debug('getTable: cddbdiscid %s, mbdiscid %s in cache '
'for offset %s' % (cddbdiscid, mbdiscid, offset))
logger.debug('getTable: loaded table %r' % itable)

t = cdrdao.ReadTOC_Task(device)
t.description = "Reading table"
t.toc_path = toc_path
runner.run(t)
itable = t.toc.table
tdict[offset] = itable
logger.debug('getTable: read table %r' % itable)

assert itable.hasTOC()

Expand Down
218 changes: 150 additions & 68 deletions whipper/program/cdrdao.py
@@ -1,73 +1,158 @@
import os
import sys
import re
import shutil
import tempfile
import subprocess
import time
from subprocess import Popen, PIPE

from whipper.common.common import EjectError, truncate_filename
from whipper.image.toc import TocFile
from whipper.extern.task import task
from whipper.extern import asyncsub

import logging
logger = logging.getLogger(__name__)

CDRDAO = 'cdrdao'


def read_toc(device, fast_toc=False, toc_path=None):
_TRACK_RE = re.compile("^Analyzing track (?P<track>[0-9]*) \(AUDIO\): start (?P<start>[0-9]*:[0-9]*:[0-9]*), length (?P<length>[0-9]*:[0-9]*:[0-9]*)")
_CRC_RE = re.compile("Found (?P<channels>[0-9]*) Q sub-channels with CRC errors")
_BEGIN_CDRDAO_RE = re.compile("-"*60)
_LAST_TRACK_RE = re.compile("^(?P<track>[0-9]*)")
_LEADOUT_RE = re.compile("^Leadout AUDIO\s*[0-9]\s*[0-9]*:[0-9]*:[0-9]*\([0-9]*\)")

class ProgressParser:
tracks = 0
currentTrack = 0
oldline = '' # for leadout/final track number detection
def parse(self, line):
cdrdao_m = _BEGIN_CDRDAO_RE.match(line)

if cdrdao_m:
logger.debug("RE: Begin cdrdao toc-read")

leadout_m = _LEADOUT_RE.match(line)

if leadout_m:
logger.debug("RE: Reached leadout")
last_track_m = _LAST_TRACK_RE.match(self.oldline)
if last_track_m:
self.tracks = last_track_m.group('track')
track_s = _TRACK_RE.search(line)
if track_s:
logger.debug("RE: Began reading track: %d" % int(track_s.group('track')))
self.currentTrack = int(track_s.group('track'))
crc_s = _CRC_RE.search(line)
if crc_s:
sys.stdout.write("Track %d finished, found %d Q sub-channels with CRC errors\n" % (self.currentTrack, int(crc_s.group('channels'))) )

self.oldline = line


class ReadTOC_Task(task.Task):
"""
Return cdrdao-generated table of contents for 'device'.
Task that reads the TOC of the disc using cdrdao
"""
# cdrdao MUST be passed a non-existing filename as its last argument
# to write the TOC to; it does not support writing to stdout or
# overwriting an existing file, nor does linux seem to support
# locking a non-existant file. Thus, this race-condition introducing
# hack is carried from morituri to whipper and will be removed when
# cdrdao is fixed.
fd, tocfile = tempfile.mkstemp(suffix=u'.cdrdao.read-toc.whipper')
os.close(fd)
os.unlink(tocfile)

cmd = [CDRDAO, 'read-toc'] + (['--fast-toc'] if fast_toc else []) + [
'--device', device, tocfile]
# PIPE is the closest to >/dev/null we can get
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()
if toc_path is not None:
t_comp = os.path.abspath(toc_path).split(os.sep)
t_dirn = os.sep.join(t_comp[:-1])
# If the output path doesn't exist, make it recursively
if not os.path.isdir(t_dirn):
os.makedirs(t_dirn)
t_dst = truncate_filename(os.path.join(t_dirn, t_comp[-1] + '.toc'))
shutil.copy(tocfile, os.path.join(t_dirn, t_dst))
os.unlink(tocfile)
return toc


def DetectCdr(device):
"""
Return whether cdrdao detects a CD-R for 'device'.
"""
cmd = [CDRDAO, 'disk-info', '-v1', '--device', device]
logger.debug("executing %r", cmd)
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
if 'CD-R medium : n/a' in p.stdout.read():
return False
else:
return True

description = "Reading TOC"
toc = None

def __init__(self, device, fast_toc=False, toc_path=None):
"""
Read the TOC for 'device'.
@device: path of device
@type device: str
@param fast_toc: use cdrdao fast-toc mode
@type fast_toc: bool
"""

self.device = device
self.fast_toc = fast_toc
self.toc_path = toc_path
self._buffer = "" # accumulate characters
self._parser = ProgressParser()
self.fd, self.tocfile = tempfile.mkstemp(suffix=u'.cdrdao.read-toc.whipper.task')
def start(self, runner):
task.Task.start(self, runner)
## TODO: Remove these hardcoded values (for testing)
fast_toc = self.fast_toc
device = self.device


os.close(self.fd)
os.unlink(self.tocfile)

cmd = [CDRDAO, 'read-toc'] + (['--fast-toc'] if fast_toc else []) + [
'--device', device, self.tocfile]

self._popen = asyncsub.Popen(cmd,
bufsize=1024,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True)

self._start_time = time.time()
self.schedule(1.0, self._read, runner)

def _read(self, runner):
ret = self._popen.recv_err()
if not ret:
if self._popen.poll() is not None:
self._done()
return
self.schedule(0.01, self._read, runner)
return
self._buffer += ret

# parse buffer into lines if possible, and parse them
if "\n" in self._buffer:

lines = self._buffer.split('\n')
if lines[-1] != "\n":
# last line didn't end yet
self._buffer = lines[-1]
del lines[-1]
else:
self._buffer = ""
for line in lines:
self._parser.parse(line)
if (self._parser.currentTrack is not 0 and self._parser.tracks is not 0):
progress = float('%d' % self._parser.currentTrack) / float(self._parser.tracks)
if progress < 1.0:
self.setProgress(progress)
# 0 does not give us output before we complete, 1.0 gives us output
# too late
self.schedule(0.01, self._read, runner)


def _poll(self, runner):

sys.stdout.write("_poll\n")
if self._popen.poll() is None:
self.schedule(1.0, self._poll, runner)
return

self._done()


def _done(self):
end_time = time.time()
self.setProgress(1.0)
self.toc = TocFile(self.tocfile)
self.toc.parse()
if self.toc_path is not None:
t_comp = os.path.abspath(self.toc_path).split(os.sep)
t_dirn = os.sep.join(t_comp[:-1])
# If the output path doesn't exist, make it recursively
if not os.path.isdir(t_dirn):
os.makedirs(t_dirn)
t_dst = truncate_filename(os.path.join(t_dirn, t_comp[-1] + '.toc'))
shutil.copy(self.tocfile, os.path.join(t_dirn, t_dst))
os.unlink(self.tocfile)
self.stop()
return

def version():
"""
Expand All @@ -87,23 +172,20 @@ def version():
return None
return m.group('version')


def ReadTOCTask(device):
"""
stopgap morituri-insanity compatibility layer
"""
return read_toc(device, fast_toc=True)


def ReadTableTask(device, toc_path=None):
def getCDRDAOVersion():
"""
stopgap morituri-insanity compatibility layer
"""
return read_toc(device, toc_path=toc_path)

return version()

def getCDRDAOVersion():
def DetectCdr(device):
"""
stopgap morituri-insanity compatibility layer
Return whether cdrdao detects a CD-R for 'device'.
"""
return version()
cmd = [CDRDAO, 'disk-info', '-v1', '--device', device]
logger.debug("executing %r", cmd)
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
if 'CD-R medium : n/a' in p.stdout.read():
return False
else:
return True

0 comments on commit 8361301

Please sign in to comment.