1 from __future__ import unicode_literals
2
3 import errno
4 import io
5 import json
6 import os
7 import re
8 import shutil
9 import traceback
10
11 from .compat import compat_getenv
12 from .utils import (
13 error_to_compat_str,
14 expand_path,
15 is_outdated_version,
16 try_get,
17 write_json_file,
18 )
19 from .version import __version__
20
21
22 class Cache(object):
23
24 _YTDL_DIR = 'youtube-dl'
25 _VERSION_KEY = _YTDL_DIR + '_version'
26 _DEFAULT_VERSION = '2021.12.17'
27
28 def __init__(self, ydl):
29 self._ydl = ydl
30
31 def _get_root_dir(self):
32 res = self._ydl.params.get('cachedir')
33 if res is None:
34 cache_root = compat_getenv('XDG_CACHE_HOME', '~/.cache')
35 res = os.path.join(cache_root, self._YTDL_DIR)
36 return expand_path(res)
37
38 def _get_cache_fn(self, section, key, dtype):
39 assert re.match(r'^[a-zA-Z0-9_.-]+$', section), \
40 'invalid section %r' % section
41 assert re.match(r'^[a-zA-Z0-9_.-]+$', key), 'invalid key %r' % key
42 return os.path.join(
43 self._get_root_dir(), section, '%s.%s' % (key, dtype))
44
45 @property
46 def enabled(self):
47 return self._ydl.params.get('cachedir') is not False
48
49 def store(self, section, key, data, dtype='json'):
50 assert dtype in ('json',)
51
52 if not self.enabled:
53 return
54
55 fn = self._get_cache_fn(section, key, dtype)
56 try:
57 try:
58 os.makedirs(os.path.dirname(fn))
59 except OSError as ose:
60 if ose.errno != errno.EEXIST:
61 raise
62 write_json_file({self._VERSION_KEY: __version__, 'data': data}, fn)
63 except Exception:
64 tb = traceback.format_exc()
65 self._ydl.report_warning(
66 'Writing cache to %r failed: %s' % (fn, tb))
67
68 def _validate(self, data, min_ver):
69 version = try_get(data, lambda x: x[self._VERSION_KEY])
70 if not version: # Backward compatibility
71 data, version = {'data': data}, self._DEFAULT_VERSION
72 if not is_outdated_version(version, min_ver or '0', assume_new=False):
73 return data['data']
74 self._ydl.to_screen(
75 'Discarding old cache from version {version} (needs {min_ver})'.format(**locals()))
76
77 def load(self, section, key, dtype='json', default=None, min_ver=None):
78 assert dtype in ('json',)
79
80 if not self.enabled:
81 return default
82
83 cache_fn = self._get_cache_fn(section, key, dtype)
84 try:
85 try:
86 with io.open(cache_fn, 'r', encoding='utf-8') as cachef:
87 return self._validate(json.load(cachef), min_ver)
88 except ValueError:
89 try:
90 file_size = os.path.getsize(cache_fn)
91 except (OSError, IOError) as oe:
92 file_size = error_to_compat_str(oe)
93 self._ydl.report_warning(
94 'Cache retrieval from %s failed (%s)' % (cache_fn, file_size))
95 except IOError:
96 pass # No cache available
97
98 return default
99
100 def remove(self):
101 if not self.enabled:
102 self._ydl.to_screen('Cache is disabled (Did you combine --no-cache-dir and --rm-cache-dir?)')
103 return
104
105 cachedir = self._get_root_dir()
106 if not any((term in cachedir) for term in ('cache', 'tmp')):
107 raise Exception('Not removing directory %s - this does not look like a cache dir' % cachedir)
108
109 self._ydl.to_screen(
110 'Removing cache dir %s .' % cachedir, skip_eol=True)
111 if os.path.exists(cachedir):
112 self._ydl.to_screen('.', skip_eol=True)
113 shutil.rmtree(cachedir)
114 self._ydl.to_screen('.')
|