comparison sat/memory/cache.py @ 2562:26edcf3a30eb

core, setup: huge cleaning: - moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention - move twisted directory to root - removed all hacks from setup.py, and added missing dependencies, it is now clean - use https URL for website in setup.py - removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed - renamed sat.sh to sat and fixed its installation - added python_requires to specify Python version needed - replaced glib2reactor which use deprecated code by gtk3reactor sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author Goffi <goffi@goffi.org>
date Mon, 02 Apr 2018 19:44:50 +0200
parents src/memory/cache.py@cd7a53c31eb6
children 56f94936df1e
comparison
equal deleted inserted replaced
2561:bd30dc3ffe5a 2562:26edcf3a30eb
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SAT: a jabber client
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from sat.core.log import getLogger
21 log = getLogger(__name__)
22 from sat.tools.common import regex
23 from sat.core import exceptions
24 from sat.core.constants import Const as C
25 import cPickle as pickle
26 import mimetypes
27 import os.path
28 import time
29
30 DEFAULT_EXT = '.raw'
31
32
33 class Cache(object):
34 """generic file caching"""
35
36 def __init__(self, host, profile):
37 """
38 @param profile(unicode, None): ame of the profile to set the cache for
39 if None, the cache will be common for all profiles
40 """
41 self.profile = profile
42 path_elts = [host.memory.getConfig('', 'local_dir'), C.CACHE_DIR]
43 if profile:
44 path_elts.extend([u'profiles',regex.pathEscape(profile)])
45 else:
46 path_elts.append(u'common')
47 self.cache_dir = os.path.join(*path_elts)
48
49 if not os.path.exists(self.cache_dir):
50 os.makedirs(self.cache_dir)
51
52 def getPath(self, filename):
53 """return cached file URL
54
55 @param filename(unicode): cached file name (cache data or actual file)
56 """
57 if not filename or u'/' in filename:
58 log.error(u"invalid char found in file name, hack attempt? name:{}".format(filename))
59 raise exceptions.DataError(u"Invalid char found")
60 return os.path.join(self.cache_dir, filename)
61
62 def getMetadata(self, uid):
63 """retrieve metadata for cached data
64
65 @param uid(unicode): unique identifier of file
66 @return (dict, None): metadata with following keys:
67 see [cacheData] for data details, an additional "path" key is the full path to cached file.
68 None if file is not in cache (or cache is invalid)
69 """
70
71 uid = uid.strip()
72 if not uid:
73 raise exceptions.InternalError(u"uid must not be empty")
74 cache_url = self.getPath(uid)
75 if not os.path.exists(cache_url):
76 return None
77
78 try:
79 with open(cache_url, 'rb') as f:
80 cache_data = pickle.load(f)
81 except IOError:
82 log.warning(u"can't read cache at {}".format(cache_url))
83 return None
84 except pickle.UnpicklingError:
85 log.warning(u'invalid cache found at {}'.format(cache_url))
86 return None
87
88 try:
89 eol = cache_data['eol']
90 except KeyError:
91 log.warning(u'no End Of Life found for cached file {}'.format(uid))
92 eol = 0
93 if eol < time.time():
94 log.debug(u"removing expired cache (expired for {}s)".format(
95 time.time() - eol))
96 return None
97
98 cache_data['path'] = self.getPath(cache_data['filename'])
99 return cache_data
100
101 def getFilePath(self, uid):
102 """retrieve absolute path to file
103
104 @param uid(unicode): unique identifier of file
105 @return (unicode, None): absolute path to cached file
106 None if file is not in cache (or cache is invalid)
107 """
108 metadata = self.getMetadata(uid)
109 if metadata is not None:
110 return metadata['path']
111
112 def cacheData(self, source, uid, mime_type=None, max_age=None, filename=None):
113 """create cache metadata and file object to use for actual data
114
115 @param source(unicode): source of the cache (should be plugin's import_name)
116 @param uid(unicode): an identifier of the file which must be unique
117 @param mime_type(unicode): MIME type of the file to cache
118 it will be used notably to guess file extension
119 @param max_age(int, None): maximum age in seconds
120 the cache metadata will have an "eol" (end of life)
121 None to use default value
122 0 to ignore cache (file will be re-downloaded on each access)
123 @param filename: if not None, will be used as filename
124 else one will be generated from uid and guessed extension
125 @return(file): file object opened in write mode
126 you have to close it yourself (hint: use with statement)
127 """
128 cache_url = self.getPath(uid)
129 if filename is None:
130 if mime_type:
131 ext = mimetypes.guess_extension(mime_type, strict=False)
132 if ext is None:
133 log.warning(u"can't find extension for MIME type {}".format(mime_type))
134 ext = DEFAULT_EXT
135 elif ext == u'.jpe':
136 ext = u'.jpg'
137 else:
138 ext = DEFAULT_EXT
139 mime_type = None
140 filename = uid + ext
141 if max_age is None:
142 max_age = C.DEFAULT_MAX_AGE
143 cache_data = {u'source': source,
144 u'filename': filename,
145 u'eol': int(time.time()) + max_age,
146 u'mime_type': mime_type,
147 }
148 file_path = self.getPath(filename)
149
150 with open(cache_url, 'wb') as f:
151 pickle.dump(cache_data, f, protocol=2)
152
153 return open(file_path, 'wb')