refactor: one Range-capable file sender; HEAD support; assorted hygiene
web.py * _download and _stream each had their own copy loop; _download had no Range support at all, so an interrupted download of a multi-hundred-MB recording had to restart. Both now go through _serve_bytes(), which keeps _stream's live-header `prefix` trick (the patched bytes are the same length as the header they replace, so Range math is unaffected). * do_HEAD answers / , /stream/ and /download/ as a body-less do_GET. /api/clip and /api/cut return 405 instead — generating a clip or shelling out to ffmpeg only to discard the result is worse than not answering. * _api_delete drops the deleted file's _DURATION_CACHE entry, which was never evicted. * WAV durations are rounded to 2dp like FLAC's already were. isr.py * _open_output_file flushed the buffer and then called close_current_file(), which is overridden to flush again. * log_level/log_file were merged into the dict copied into every source's config; no recorder reads them. Read them where they are used. webui.html * scoreOrder() is memoised on the section array's identity — U/I otherwise re-sorted a whole day (thousands of sections) per keypress. Verified with a byte-level harness: full/partial/open-ended/unsatisfiable ranges byte-identical to disk, download Range, HEAD on all three supported paths returning headers with an empty body, 405 on /api/clip, and live-header patching still correct both whole-file and for a range straddling the patched/disk boundary. Not done from the audit: BaseRecorder.generate_filename's mkdir per file open. It runs once per split (once an hour by default), so "once at startup suffices" was not worth a behaviour change. `nul` stays in .gitignore — it guards against a Windows shell artifact on a Windows box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -641,8 +641,7 @@ class SoundcardRecorder(BaseRecorder):
|
||||
|
||||
def _open_output_file(self):
|
||||
"""Open a new WAV or FLAC output file for recording."""
|
||||
self._flush_buffer_to_file()
|
||||
self.close_current_file()
|
||||
self.close_current_file() # overridden below to flush first
|
||||
|
||||
self.current_filename = self.generate_filename(self.file_format)
|
||||
self.current_file = _AudioFileWriter(
|
||||
@@ -747,19 +746,21 @@ class RecorderManager:
|
||||
config.read(self.config_file)
|
||||
|
||||
# Get general settings (defaults)
|
||||
# Defaults every source inherits. Logging is read separately — it is
|
||||
# a process-wide setting, not something a recorder ever looks at.
|
||||
general = {
|
||||
'output_directory': config.get('general', 'output_directory', fallback='recordings'),
|
||||
'split_minutes': config.getint('general', 'split_minutes', fallback=60),
|
||||
'max_retries': config.getint('general', 'max_retries', fallback=10),
|
||||
'retry_delay_seconds': config.getint('general', 'retry_delay_seconds', fallback=5),
|
||||
'log_level': config.get('general', 'log_level', fallback='INFO').upper(),
|
||||
'log_file': config.get('general', 'log_file', fallback='recorder.log'),
|
||||
}
|
||||
|
||||
self.output_dir = general['output_directory']
|
||||
|
||||
# Setup logging
|
||||
self._setup_logging(general['log_level'], general['log_file'])
|
||||
self._setup_logging(
|
||||
config.get('general', 'log_level', fallback='INFO').upper(),
|
||||
config.get('general', 'log_file', fallback='recorder.log'))
|
||||
|
||||
# Initialize shared audio system for soundcard recorders
|
||||
self.audio_system = AudioSystem(self.logger)
|
||||
|
||||
Reference in New Issue
Block a user