|
2 | 2 | import re
|
3 | 3 | import shutil
|
4 | 4 | import tempfile
|
| 5 | +import subprocess |
5 | 6 | from subprocess import Popen, PIPE
|
6 | 7 |
|
7 |
| -from whipper.common.common import EjectError, truncate_filename |
| 8 | +from whipper.common.common import truncate_filename |
8 | 9 | from whipper.image.toc import TocFile
|
| 10 | +from whipper.extern.task import task |
| 11 | +from whipper.extern import asyncsub |
9 | 12 |
|
10 | 13 | import logging
|
11 | 14 | logger = logging.getLogger(__name__)
|
12 | 15 |
|
13 | 16 | CDRDAO = 'cdrdao'
|
14 | 17 |
|
| 18 | +_TRACK_RE = re.compile(r"^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]*)") # noqa: E501 |
| 19 | +_CRC_RE = re.compile( |
| 20 | + r"Found (?P<channels>[0-9]*) Q sub-channels with CRC errors") |
| 21 | +_BEGIN_CDRDAO_RE = re.compile(r"-" * 60) |
| 22 | +_LAST_TRACK_RE = re.compile(r"^(?P<track>[0-9]*)") |
| 23 | +_LEADOUT_RE = re.compile( |
| 24 | + r"^Leadout AUDIO\s*[0-9]\s*[0-9]*:[0-9]*:[0-9]*\([0-9]*\)") |
15 | 25 |
|
16 |
| -def read_toc(device, fast_toc=False, toc_path=None): |
| 26 | + |
| 27 | +class ProgressParser: |
| 28 | + tracks = 0 |
| 29 | + currentTrack = 0 |
| 30 | + oldline = '' # for leadout/final track number detection |
| 31 | + |
| 32 | + def parse(self, line): |
| 33 | + cdrdao_m = _BEGIN_CDRDAO_RE.match(line) |
| 34 | + |
| 35 | + if cdrdao_m: |
| 36 | + logger.debug("RE: Begin cdrdao toc-read") |
| 37 | + |
| 38 | + leadout_m = _LEADOUT_RE.match(line) |
| 39 | + |
| 40 | + if leadout_m: |
| 41 | + logger.debug("RE: Reached leadout") |
| 42 | + last_track_m = _LAST_TRACK_RE.match(self.oldline) |
| 43 | + if last_track_m: |
| 44 | + self.tracks = last_track_m.group('track') |
| 45 | + |
| 46 | + track_s = _TRACK_RE.search(line) |
| 47 | + if track_s: |
| 48 | + logger.debug("RE: Began reading track: %d", |
| 49 | + int(track_s.group('track'))) |
| 50 | + self.currentTrack = int(track_s.group('track')) |
| 51 | + |
| 52 | + crc_s = _CRC_RE.search(line) |
| 53 | + if crc_s: |
| 54 | + print("Track %d finished, " |
| 55 | + "found %d Q sub-channels with CRC errors" % |
| 56 | + (self.currentTrack, int(crc_s.group('channels')))) |
| 57 | + |
| 58 | + self.oldline = line |
| 59 | + |
| 60 | + |
| 61 | +class ReadTOCTask(task.Task): |
17 | 62 | """
|
18 |
| - Return cdrdao-generated table of contents for 'device'. |
| 63 | + Task that reads the TOC of the disc using cdrdao |
19 | 64 | """
|
20 |
| - # cdrdao MUST be passed a non-existing filename as its last argument |
21 |
| - # to write the TOC to; it does not support writing to stdout or |
22 |
| - # overwriting an existing file, nor does linux seem to support |
23 |
| - # locking a non-existant file. Thus, this race-condition introducing |
24 |
| - # hack is carried from morituri to whipper and will be removed when |
25 |
| - # cdrdao is fixed. |
26 |
| - fd, tocfile = tempfile.mkstemp(suffix=u'.cdrdao.read-toc.whipper') |
27 |
| - os.close(fd) |
28 |
| - os.unlink(tocfile) |
29 |
| - |
30 |
| - cmd = [CDRDAO, 'read-toc'] + (['--fast-toc'] if fast_toc else []) + [ |
31 |
| - '--device', device, tocfile] |
32 |
| - # PIPE is the closest to >/dev/null we can get |
33 |
| - logger.debug("executing %r", cmd) |
34 |
| - p = Popen(cmd, stdout=PIPE, stderr=PIPE) |
35 |
| - _, stderr = p.communicate() |
36 |
| - if p.returncode != 0: |
37 |
| - msg = 'cdrdao read-toc failed: return code is non-zero: ' + \ |
38 |
| - str(p.returncode) |
39 |
| - logger.critical(msg) |
40 |
| - # Gracefully handle missing disc |
41 |
| - if "ERROR: Unit not ready, giving up." in stderr: |
42 |
| - raise EjectError(device, "no disc detected") |
43 |
| - raise IOError(msg) |
44 |
| - |
45 |
| - toc = TocFile(tocfile) |
46 |
| - toc.parse() |
47 |
| - if toc_path is not None: |
48 |
| - t_comp = os.path.abspath(toc_path).split(os.sep) |
49 |
| - t_dirn = os.sep.join(t_comp[:-1]) |
50 |
| - # If the output path doesn't exist, make it recursively |
51 |
| - if not os.path.isdir(t_dirn): |
52 |
| - os.makedirs(t_dirn) |
53 |
| - t_dst = truncate_filename(os.path.join(t_dirn, t_comp[-1] + '.toc')) |
54 |
| - shutil.copy(tocfile, os.path.join(t_dirn, t_dst)) |
55 |
| - os.unlink(tocfile) |
56 |
| - return toc |
| 65 | + description = "Reading TOC" |
| 66 | + toc = None |
| 67 | + |
| 68 | + def __init__(self, device, fast_toc=False, toc_path=None): |
| 69 | + """ |
| 70 | + Read the TOC for 'device'. |
| 71 | +
|
| 72 | + @param device: block device to read TOC from |
| 73 | + @type device: str |
| 74 | + @param fast_toc: If to use fast-toc cdrdao mode |
| 75 | + @type fast_toc: bool |
| 76 | + @param toc_path: Where to save TOC if wanted. |
| 77 | + @type toc_path: str |
| 78 | + """ |
| 79 | + |
| 80 | + self.device = device |
| 81 | + self.fast_toc = fast_toc |
| 82 | + self.toc_path = toc_path |
| 83 | + self._buffer = "" # accumulate characters |
| 84 | + self._parser = ProgressParser() |
| 85 | + |
| 86 | + self.fd, self.tocfile = tempfile.mkstemp( |
| 87 | + suffix=u'.cdrdao.read-toc.whipper.task') |
| 88 | + |
| 89 | + def start(self, runner): |
| 90 | + task.Task.start(self, runner) |
| 91 | + os.close(self.fd) |
| 92 | + os.unlink(self.tocfile) |
| 93 | + |
| 94 | + cmd = ([CDRDAO, 'read-toc'] |
| 95 | + + (['--fast-toc'] if self.fast_toc else []) |
| 96 | + + ['--device', self.device, self.tocfile]) |
| 97 | + |
| 98 | + self._popen = asyncsub.Popen(cmd, |
| 99 | + bufsize=1024, |
| 100 | + stdin=subprocess.PIPE, |
| 101 | + stdout=subprocess.PIPE, |
| 102 | + stderr=subprocess.PIPE, |
| 103 | + close_fds=True) |
| 104 | + |
| 105 | + self.schedule(0.01, self._read, runner) |
| 106 | + |
| 107 | + def _read(self, runner): |
| 108 | + ret = self._popen.recv_err() |
| 109 | + if not ret: |
| 110 | + if self._popen.poll() is not None: |
| 111 | + self._done() |
| 112 | + return |
| 113 | + self.schedule(0.01, self._read, runner) |
| 114 | + return |
| 115 | + self._buffer += ret |
| 116 | + |
| 117 | + # parse buffer into lines if possible, and parse them |
| 118 | + if "\n" in self._buffer: |
| 119 | + lines = self._buffer.split('\n') |
| 120 | + if lines[-1] != "\n": |
| 121 | + # last line didn't end yet |
| 122 | + self._buffer = lines[-1] |
| 123 | + del lines[-1] |
| 124 | + else: |
| 125 | + self._buffer = "" |
| 126 | + for line in lines: |
| 127 | + self._parser.parse(line) |
| 128 | + if (self._parser.currentTrack is not 0 and |
| 129 | + self._parser.tracks is not 0): |
| 130 | + progress = (float('%d' % self._parser.currentTrack) / |
| 131 | + float(self._parser.tracks)) |
| 132 | + if progress < 1.0: |
| 133 | + self.setProgress(progress) |
| 134 | + |
| 135 | + # 0 does not give us output before we complete, 1.0 gives us output |
| 136 | + # too late |
| 137 | + self.schedule(0.01, self._read, runner) |
| 138 | + |
| 139 | + def _poll(self, runner): |
| 140 | + if self._popen.poll() is None: |
| 141 | + self.schedule(1.0, self._poll, runner) |
| 142 | + return |
| 143 | + |
| 144 | + self._done() |
| 145 | + |
| 146 | + def _done(self): |
| 147 | + self.setProgress(1.0) |
| 148 | + self.toc = TocFile(self.tocfile) |
| 149 | + self.toc.parse() |
| 150 | + if self.toc_path is not None: |
| 151 | + t_comp = os.path.abspath(self.toc_path).split(os.sep) |
| 152 | + t_dirn = os.sep.join(t_comp[:-1]) |
| 153 | + # If the output path doesn't exist, make it recursively |
| 154 | + if not os.path.isdir(t_dirn): |
| 155 | + os.makedirs(t_dirn) |
| 156 | + t_dst = truncate_filename( |
| 157 | + os.path.join(t_dirn, t_comp[-1] + '.toc')) |
| 158 | + shutil.copy(self.tocfile, os.path.join(t_dirn, t_dst)) |
| 159 | + os.unlink(self.tocfile) |
| 160 | + self.stop() |
| 161 | + return |
57 | 162 |
|
58 | 163 |
|
59 | 164 | def DetectCdr(device):
|
@@ -88,20 +193,6 @@ def version():
|
88 | 193 | return m.group('version')
|
89 | 194 |
|
90 | 195 |
|
91 |
| -def ReadTOCTask(device): |
92 |
| - """ |
93 |
| - stopgap morituri-insanity compatibility layer |
94 |
| - """ |
95 |
| - return read_toc(device, fast_toc=True) |
96 |
| - |
97 |
| - |
98 |
| -def ReadTableTask(device, toc_path=None): |
99 |
| - """ |
100 |
| - stopgap morituri-insanity compatibility layer |
101 |
| - """ |
102 |
| - return read_toc(device, toc_path=toc_path) |
103 |
| - |
104 |
| - |
105 | 196 | def getCDRDAOVersion():
|
106 | 197 | """
|
107 | 198 | stopgap morituri-insanity compatibility layer
|
|
0 commit comments