argparse & logging (#92)

* introduce logcommand.Lager, Whipper(); use argparse for whipper image commands, stub logging

* update Lager docstring to mention config.Config()

* make incorrect subcommand and --version work on toplevel command

* migrate accurip show, expand Lager, do not attempt to return from Lager.__init__.

* migrate offset find, add Lager.error

* correct offset find drive symlink handling

* migrate drive

* change Lager.__init__(prog) to arg from kwarg

* but actually

* remove Whipper.usage

* add and use Lager.device_option() context manager

* help I married an axe murderer

* use unified options namespace for entire command tree

* migrate whipper cd without comprehensive config loading

* switch to logging module

- use logging instead of flog for non-extern modules
- use WHIPPER_DEBUG and WHIPPER_LOGFILE env variables

* convert self.log calls to logger.debug

* convert self.error calls to logger.error

* remove log.Loggable, use logger not logging

* Logging conversion continues

- Convert log.* calls to logger.*
- Remove morituri.common.log imports

* remove morituri.common.log from tests

* remove extern/flog, bare minimum Debug conversion

* update README for logging changes

* update soxi to use logging

* refactor Lager for more declarative subcommands

* Refactor Lager.device_option:

- inline into __init__
- throw IOError instead of Exception for missing drives
- remove CommandError checking in rip/main

* rename rip to whipper in rip.main

* convert rip.debug commands

* Rename logcommand.Lager to command.BaseCommand

- remove command.CommandError occurrences
- remove python-command external module

* remove submodules from README, update rclog formatter

* update minor ambiguity in readme for command invocation

* update version number to match setup.py

* remove gitmodules

* update version number in tests as well (boo)

* convert logger.error to logger.critical

* Change morituri.rip to morituri.command

- mv common.command to command.basecommand
- move TEMPLATES used only by rip.cd out of rip.common
- update entry point for command to command.main

* update basecommand documentation

* go pyflaking: import fixing

* replace self.stdout with sys.stdout

* remove BaseCommand.config, alphabetise imports

* convert self.stdXXX leftovers

* convert last getRootCommand to config.Config

* convert last getExceptionMessage's to str

* change musicbrainz useragent to whipper
This commit is contained in:
Samantha Baldwin
2016-12-20 17:11:30 -05:00
committed by JoeLametta
parent 8f4607de4c
commit d1ed80d62a
48 changed files with 1049 additions and 1072 deletions

View File

@@ -1,6 +1,8 @@
import logging
from subprocess import Popen, PIPE
from os.path import exists
from subprocess import Popen, PIPE
import logging
logger = logging.getLogger(__name__)
ARB = 'accuraterip-checksum'
FLAC = 'flac'
@@ -33,18 +35,18 @@ def accuraterip_checksum(f, track, tracks, wave=False, v2=False):
arc_rc = arc.returncode
if not wave and flac_rc != 0:
logging.warning('ARC calculation failed: flac return code is non zero')
logger.warning('ARC calculation failed: flac return code is non zero')
return None
if arc_rc != 0:
logging.warning('ARC calculation failed: arc return code is non zero')
logger.warning('ARC calculation failed: arc return code is non zero')
return None
out = out.strip()
try:
outh = int('0x%s' % out, base=16)
except ValueError:
logging.warning('ARC output is not usable')
logger.warning('ARC output is not usable')
return None
return outh

View File

@@ -20,21 +20,23 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see <http://www.gnu.org/licenses/>.
import os
import errno
import time
import os
import re
import stat
import shutil
import stat
import subprocess
import tempfile
import time
from morituri.common import log, common
from morituri.common import common
from morituri.common import task as ctask
from morituri.extern import asyncsub
from morituri.extern.task import task
import logging
logger = logging.getLogger(__name__)
class FileSizeError(Exception):
@@ -78,7 +80,7 @@ _ERROR_RE = re.compile("^scsi_read error:")
# number of single-channel samples, ie. 2 bytes (word) per unit, and absolute
class ProgressParser(log.Loggable):
class ProgressParser:
read = 0 # last [read] frame
wrote = 0 # last [wrote] frame
errors = 0 # count of number of scsi errors
@@ -130,12 +132,12 @@ class ProgressParser(log.Loggable):
# set nframes if not yet set
if self._nframes is None and self.read != 0:
self._nframes = frameOffset - self.read
self.debug('set nframes to %r', self._nframes)
logger.debug('set nframes to %r', self._nframes)
# set firstFrames if not yet set
if self._firstFrames is None:
self._firstFrames = frameOffset - self.start
self.debug('set firstFrames to %r', self._firstFrames)
logger.debug('set firstFrames to %r', self._firstFrames)
markStart = None
markEnd = None # the next unread frame (half-inclusive)
@@ -192,7 +194,7 @@ class ProgressParser(log.Loggable):
"""
frames = self.stop - self.start + 1 # + 1 since stop is inclusive
reads = self.reads
self.debug('getTrackQuality: frames %d, reads %d' % (frames, reads))
logger.debug('getTrackQuality: frames %d, reads %d' % (frames, reads))
# don't go over a 100%; we know cdparanoia reads each frame at least
# twice
@@ -202,7 +204,7 @@ class ProgressParser(log.Loggable):
# FIXME: handle errors
class ReadTrackTask(log.Loggable, task.Task):
class ReadTrackTask(task.Task):
"""
I am a task that reads a track using cdparanoia.
@@ -271,11 +273,11 @@ class ReadTrackTask(log.Loggable, task.Task):
stopTrack = i + 1
stopOffset = self._stop - self._table.getTrackStart(i + 1)
self.debug('Ripping from %d to %d (inclusive)',
logger.debug('Ripping from %d to %d (inclusive)',
self._start, self._stop)
self.debug('Starting at track %d, offset %d',
logger.debug('Starting at track %d, offset %d',
startTrack, startOffset)
self.debug('Stopping at track %d, offset %d',
logger.debug('Stopping at track %d, offset %d',
stopTrack, stopOffset)
bufsize = 1024
@@ -291,7 +293,7 @@ class ReadTrackTask(log.Loggable, task.Task):
startTrack, common.framesToHMSF(startOffset),
stopTrack, common.framesToHMSF(stopOffset)),
self.path])
self.debug('Running %s' % (" ".join(argv), ))
logger.debug('Running %s' % (" ".join(argv), ))
try:
self._popen = asyncsub.Popen(argv,
bufsize=bufsize,
@@ -333,7 +335,7 @@ class ReadTrackTask(log.Loggable, task.Task):
# fail if too many errors
if self._parser.errors > self._MAXERROR:
self.debug('%d errors, terminating', self._parser.errors)
logger.debug('%d errors, terminating', self._parser.errors)
self._popen.terminate()
num = self._parser.wrote - self._start + 1
@@ -366,13 +368,13 @@ class ReadTrackTask(log.Loggable, task.Task):
expected = offsetLength * common.BYTES_PER_FRAME + 44
if size != expected:
# FIXME: handle errors better
self.warning('file size %d did not match expected size %d',
logger.warning('file size %d did not match expected size %d',
size, expected)
if (size - expected) % common.BYTES_PER_FRAME == 0:
self.warning('%d frames difference' % (
logger.warning('%d frames difference' % (
(size - expected) / common.BYTES_PER_FRAME))
else:
self.warning('non-integral amount of frames difference')
logger.warning('non-integral amount of frames difference')
self.setAndRaiseException(FileSizeError(self.path,
"File size %d did not match expected size %d" % (
@@ -382,7 +384,7 @@ class ReadTrackTask(log.Loggable, task.Task):
if self._errors:
print "\n".join(self._errors)
else:
self.warning('exit code %r', self._popen.returncode)
logger.warning('exit code %r', self._popen.returncode)
self.exception = ReturnCodeError(self._popen.returncode)
self.quality = self._parser.getTrackQuality()
@@ -393,7 +395,7 @@ class ReadTrackTask(log.Loggable, task.Task):
return
class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
class ReadVerifyTrackTask(task.MultiSeparateTask):
"""
I am a task that reads and verifies a track using cdparanoia.
I also encode the track.
@@ -449,10 +451,10 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
"""
task.MultiSeparateTask.__init__(self)
self.debug('Creating read and verify task on %r', path)
logger.debug('Creating read and verify task on %r', path)
if taglist:
self.debug('read and verify with taglist %r', taglist)
logger.debug('read and verify with taglist %r', taglist)
# FIXME: choose a dir on the same disk/dir as the final path
fd, tmppath = tempfile.mkstemp(suffix='.morituri.wav')
tmppath = unicode(tmppath)
@@ -504,7 +506,7 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
self.quality = max(self.tasks[0].quality,
self.tasks[2].quality)
self.peak = self.tasks[6].peak
self.debug('peak: %r', self.peak)
logger.debug('peak: %r', self.peak)
self.testspeed = self.tasks[0].speed
self.copyspeed = self.tasks[2].speed
self.testduration = self.tasks[0].duration
@@ -513,11 +515,11 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
self.testchecksum = c1 = self.tasks[1].checksum
self.copychecksum = c2 = self.tasks[3].checksum
if c1 == c2:
self.info('Checksums match, %08x' % c1)
logger.info('Checksums match, %08x' % c1)
self.checksum = self.testchecksum
else:
# FIXME: detect this before encoding
self.info('Checksums do not match, %08x %08x' % (
logger.info('Checksums do not match, %08x %08x' % (
c1, c2))
self.exception = ChecksumException(
'read and verify failed: test checksum')
@@ -531,17 +533,17 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
if not self.exception:
try:
self.debug('Moving to final path %r', self.path)
logger.debug('Moving to final path %r', self.path)
os.rename(self._tmppath, self.path)
except Exception, e:
self.debug('Exception while moving to final path %r: '
logger.debug('Exception while moving to final path %r: '
'%r',
self.path, log.getExceptionMessage(e))
self.path, str(e))
self.exception = e
else:
os.unlink(self._tmppath)
else:
self.debug('stop: exception %r', self.exception)
logger.debug('stop: exception %r', self.exception)
except Exception, e:
print 'WARNING: unhandled exception %r' % (e, )

View File

@@ -1,4 +1,3 @@
import logging
import os
import re
import tempfile
@@ -6,6 +5,9 @@ from subprocess import check_call, Popen, PIPE, CalledProcessError
from morituri.image.toc import TocFile
import logging
logger = logging.getLogger(__name__)
CDRDAO = 'cdrdao'
def read_toc(device, fast_toc=False):
@@ -28,7 +30,7 @@ def read_toc(device, fast_toc=False):
try:
check_call(cmd, stdout=PIPE, stderr=PIPE)
except CalledProcessError, e:
logging.warning('cdrdao read-toc failed: return code is non-zero: ' +
logger.warning('cdrdao read-toc failed: return code is non-zero: ' +
str(e.returncode))
raise e
toc = TocFile(tocfile)
@@ -43,14 +45,14 @@ def version():
cdrdao = Popen(CDRDAO, stderr=PIPE)
out, err = cdrdao.communicate()
if cdrdao.returncode != 1:
logging.warning("cdrdao version detection failed: "
"return code is " + str(cdrdao.returncode))
logger.warning("cdrdao version detection failed: "
"return code is " + str(cdrdao.returncode))
return None
m = re.compile(r'^Cdrdao version (?P<version>.*) - \(C\)').search(
err.decode('utf-8'))
if not m:
logging.warning("cdrdao version detection failed: "
+ "could not find version")
logger.warning("cdrdao version detection failed: "
"could not find version")
return None
return m.group('version')

View File

@@ -1,7 +1,9 @@
import logging
import os
from subprocess import Popen, PIPE
import logging
logger = logging.getLogger(__name__)
SOX = 'sox'
def peak_level(track_path):
@@ -12,12 +14,12 @@ def peak_level(track_path):
Returns None on error.
"""
if not os.path.exists(track_path):
logging.warning("SoX peak detection failed: file not found")
logger.warning("SoX peak detection failed: file not found")
return None
sox = Popen([SOX, track_path, "-n", "stat"], stderr=PIPE)
out, err = sox.communicate()
if sox.returncode:
logging.warning("SoX peak detection failed: " + str(sox.returncode))
logger.warning("SoX peak detection failed: " + str(sox.returncode))
return None
# relevant captured line looks like:
# Maximum amplitude: 0.123456

View File

@@ -1,11 +1,14 @@
import os
from morituri.common import log, common
from morituri.common import common
from morituri.common import task as ctask
import logging
logger = logging.getLogger(__name__)
SOXI = 'soxi'
class AudioLengthTask(ctask.PopenTask, log.Loggable):
class AudioLengthTask(ctask.PopenTask):
"""
I calculate the length of a track in audio samples.
@@ -42,5 +45,5 @@ class AudioLengthTask(ctask.PopenTask, log.Loggable):
def done(self):
if self._error:
self.warning("soxi reported on stderr: %s", "".join(self._error))
logger.warning("soxi reported on stderr: %s", "".join(self._error))
self.length = int("".join(self._output))