aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_dl/downloader/common.py
diff options
context:
space:
mode:
Diffstat (limited to 'hypervideo_dl/downloader/common.py')
-rw-r--r--hypervideo_dl/downloader/common.py99
1 files changed, 78 insertions, 21 deletions
diff --git a/hypervideo_dl/downloader/common.py b/hypervideo_dl/downloader/common.py
index 27ca2cd..7cef3e8 100644
--- a/hypervideo_dl/downloader/common.py
+++ b/hypervideo_dl/downloader/common.py
@@ -4,14 +4,17 @@ import os
import re
import time
import random
+import errno
from ..utils import (
decodeArgument,
encodeFilename,
error_to_compat_str,
format_bytes,
+ sanitize_open,
shell_quote,
timeconvert,
+ timetuple_from_msec,
)
from ..minicurses import (
MultilineLogger,
@@ -38,6 +41,7 @@ class FileDownloader(object):
ratelimit: Download speed limit, in bytes/sec.
throttledratelimit: Assume the download is being throttled below this speed (bytes/sec)
retries: Number of times to retry for HTTP error 5xx
+ file_access_retries: Number of times to retry on file access error
buffersize: Size of download buffer in bytes.
noresizebuffer: Do not automatically resize the download buffer.
continuedl: Try to continue downloads if possible.
@@ -75,14 +79,12 @@ class FileDownloader(object):
@staticmethod
def format_seconds(seconds):
- (mins, secs) = divmod(seconds, 60)
- (hours, mins) = divmod(mins, 60)
- if hours > 99:
+ time = timetuple_from_msec(seconds * 1000)
+ if time.hours > 99:
return '--:--:--'
- if hours == 0:
- return '%02d:%02d' % (mins, secs)
- else:
- return '%02d:%02d:%02d' % (hours, mins, secs)
+ if not time.hours:
+ return '%02d:%02d' % time[1:-1]
+ return '%02d:%02d:%02d' % time[:-1]
@staticmethod
def calc_percent(byte_counter, data_len):
@@ -94,6 +96,8 @@ class FileDownloader(object):
def format_percent(percent):
if percent is None:
return '---.-%'
+ elif percent == 100:
+ return '100%'
return '%6s' % ('%3.1f%%' % percent)
@staticmethod
@@ -155,7 +159,7 @@ class FileDownloader(object):
return int(round(number * multiplier))
def to_screen(self, *args, **kargs):
- self.ydl.to_stdout(*args, quiet=self.params.get('quiet'), **kargs)
+ self.ydl.to_screen(*args, quiet=self.params.get('quiet'), **kargs)
def to_stderr(self, message):
self.ydl.to_stderr(message)
@@ -206,13 +210,41 @@ class FileDownloader(object):
def ytdl_filename(self, filename):
return filename + '.ytdl'
+ def wrap_file_access(action, *, fatal=False):
+ def outer(func):
+ def inner(self, *args, **kwargs):
+ file_access_retries = self.params.get('file_access_retries', 0)
+ retry = 0
+ while True:
+ try:
+ return func(self, *args, **kwargs)
+ except (IOError, OSError) as err:
+ retry = retry + 1
+ if retry > file_access_retries or err.errno not in (errno.EACCES, errno.EINVAL):
+ if not fatal:
+ self.report_error(f'unable to {action} file: {err}')
+ return
+ raise
+ self.to_screen(
+ f'[download] Unable to {action} file due to file access error. '
+ f'Retrying (attempt {retry} of {self.format_retries(file_access_retries)}) ...')
+ time.sleep(0.01)
+ return inner
+ return outer
+
+ @wrap_file_access('open', fatal=True)
+ def sanitize_open(self, filename, open_mode):
+ return sanitize_open(filename, open_mode)
+
+ @wrap_file_access('remove')
+ def try_remove(self, filename):
+ os.remove(filename)
+
+ @wrap_file_access('rename')
def try_rename(self, old_filename, new_filename):
if old_filename == new_filename:
return
- try:
- os.replace(old_filename, new_filename)
- except (IOError, OSError) as err:
- self.report_error(f'unable to rename file: {err}')
+ os.replace(old_filename, new_filename)
def try_utime(self, filename, last_modified_hdr):
"""Try to set the last-modified time of the given file."""
@@ -245,14 +277,32 @@ class FileDownloader(object):
elif self.ydl.params.get('logger'):
self._multiline = MultilineLogger(self.ydl.params['logger'], lines)
elif self.params.get('progress_with_newline'):
- self._multiline = BreaklineStatusPrinter(self.ydl._screen_file, lines)
+ self._multiline = BreaklineStatusPrinter(self.ydl._out_files['screen'], lines)
else:
- self._multiline = MultilinePrinter(self.ydl._screen_file, lines, not self.params.get('quiet'))
+ self._multiline = MultilinePrinter(self.ydl._out_files['screen'], lines, not self.params.get('quiet'))
+ self._multiline.allow_colors = self._multiline._HAVE_FULLCAP and not self.params.get('no_color')
def _finish_multiline_status(self):
self._multiline.end()
- def _report_progress_status(self, s):
+ _progress_styles = {
+ 'downloaded_bytes': 'light blue',
+ 'percent': 'light blue',
+ 'eta': 'yellow',
+ 'speed': 'green',
+ 'elapsed': 'bold white',
+ 'total_bytes': '',
+ 'total_bytes_estimate': '',
+ }
+
+ def _report_progress_status(self, s, default_template):
+ for name, style in self._progress_styles.items():
+ name = f'_{name}_str'
+ if name not in s:
+ continue
+ s[name] = self._format_progress(s[name], style)
+ s['_default_template'] = default_template % s
+
progress_dict = s.copy()
progress_dict.pop('info_dict')
progress_dict = {'info': s['info_dict'], 'progress': progress_dict}
@@ -265,6 +315,10 @@ class FileDownloader(object):
progress_template.get('download-title') or 'hypervideo %(progress._default_template)s',
progress_dict))
+ def _format_progress(self, *args, **kwargs):
+ return self.ydl._format_text(
+ self._multiline.stream, self._multiline.allow_colors, *args, **kwargs)
+
def report_progress(self, s):
if s['status'] == 'finished':
if self.params.get('noprogress'):
@@ -277,8 +331,7 @@ class FileDownloader(object):
s['_elapsed_str'] = self.format_seconds(s['elapsed'])
msg_template += ' in %(_elapsed_str)s'
s['_percent_str'] = self.format_percent(100)
- s['_default_template'] = msg_template % s
- self._report_progress_status(s)
+ self._report_progress_status(s, msg_template)
return
if s['status'] != 'downloading':
@@ -287,7 +340,7 @@ class FileDownloader(object):
if s.get('eta') is not None:
s['_eta_str'] = self.format_eta(s['eta'])
else:
- s['_eta_str'] = 'Unknown ETA'
+ s['_eta_str'] = 'Unknown'
if s.get('total_bytes') and s.get('downloaded_bytes') is not None:
s['_percent_str'] = self.format_percent(100 * s['downloaded_bytes'] / s['total_bytes'])
@@ -319,9 +372,12 @@ class FileDownloader(object):
else:
msg_template = '%(_downloaded_bytes_str)s at %(_speed_str)s'
else:
- msg_template = '%(_percent_str)s % at %(_speed_str)s ETA %(_eta_str)s'
- s['_default_template'] = msg_template % s
- self._report_progress_status(s)
+ msg_template = '%(_percent_str)s at %(_speed_str)s ETA %(_eta_str)s'
+ if s.get('fragment_index') and s.get('fragment_count'):
+ msg_template += ' (frag %(fragment_index)s/%(fragment_count)s)'
+ elif s.get('fragment_index'):
+ msg_template += ' (frag %(fragment_index)s)'
+ self._report_progress_status(s, msg_template)
def report_resuming_byte(self, resume_len):
"""Report attempt to resume at given byte."""
@@ -372,6 +428,7 @@ class FileDownloader(object):
'status': 'finished',
'total_bytes': os.path.getsize(encodeFilename(filename)),
}, info_dict)
+ self._finish_multiline_status()
return True, False
if subtitle is False: