1from sorl.thumbnail.conf import settings
2from sorl.thumbnail.helpers import serialize, deserialize, ThumbnailError
3from sorl.thumbnail.images import serialize_image_file, deserialize_image_file
4
5
6def add_prefix(key, identity='image'):
7    """
8    Adds prefixes to the key
9    """
10    return '||'.join([settings.THUMBNAIL_KEY_PREFIX, identity, key])
11
12
13def del_prefix(key):
14    """
15    Removes prefixes from the key
16    """
17    return key.split('||')[-1]
18
19
20class KVStoreBase(object):
21    def get(self, image_file):
22        """
23        Gets the ``image_file`` from store. Returns ``None`` if not found.
24        """
25        return self._get(image_file.key)
26
27    def set(self, image_file, source=None):
28        """
29        Updates store for the `image_file`. Makes sure the `image_file` has a
30        size set.
31        """
32        image_file.set_size()  # make sure its got a size
33        self._set(image_file.key, image_file)
34        if source is not None:
35            if not self.get(source):
36                # make sure the source is in kvstore
37                raise ThumbnailError('Cannot add thumbnails for source: `%s` '
38                                     'that is not in kvstore.' % source.name)
39
40            # Update the list of thumbnails for source.
41            thumbnails = self._get(source.key, identity='thumbnails') or []
42            thumbnails = set(thumbnails)
43            thumbnails.add(image_file.key)
44
45            self._set(source.key, list(thumbnails), identity='thumbnails')
46
47    def get_or_set(self, image_file):
48        cached = self.get(image_file)
49        if cached is not None:
50            return cached
51        self.set(image_file)
52        return image_file
53
54    def delete(self, image_file, delete_thumbnails=True):
55        """
56        Deletes the reference to the ``image_file`` and deletes the references
57        to thumbnails as well as thumbnail files if ``delete_thumbnails`` is
58        `True``. Does not delete the ``image_file`` is self.
59        """
60        if delete_thumbnails:
61            self.delete_thumbnails(image_file)
62        self._delete(image_file.key)
63
64    def delete_thumbnails(self, image_file):
65        """
66        Deletes references to thumbnails as well as thumbnail ``image_files``.
67        """
68        thumbnail_keys = self._get(image_file.key, identity='thumbnails')
69        if thumbnail_keys:
70            # Delete all thumbnail keys from store and delete the
71            # thumbnail ImageFiles.
72
73            for key in thumbnail_keys:
74                thumbnail = self._get(key)
75                if thumbnail:
76                    self.delete(thumbnail, False)
77                    thumbnail.delete()  # delete the actual file
78
79            # Delete the thumbnails key from store
80            self._delete(image_file.key, identity='thumbnails')
81
82    def delete_all_thumbnail_files(self):
83        for key in self._find_keys(identity='thumbnails'):
84            thumbnail_keys = self._get(key, identity='thumbnails')
85            if thumbnail_keys:
86                for key in thumbnail_keys:
87                    thumbnail = self._get(key)
88                    if thumbnail:
89                        thumbnail.delete()
90
91    def cleanup(self):
92        """
93        Cleans up the key value store. In detail:
94        1. Deletes all key store references for image_files that do not exist
95           and all key references for its thumbnails *and* their image_files.
96        2. Deletes or updates all invalid thumbnail keys
97        """
98        for key in self._find_keys(identity='image'):
99            image_file = self._get(key)
100
101            if image_file and not image_file.exists():
102                self.delete(image_file)
103
104        for key in self._find_keys(identity='thumbnails'):
105            # We do not need to check for file existence in here since we
106            # already did that above for all image references
107            image_file = self._get(key)
108
109            if image_file:
110                # if there is an image_file then we check all of its thumbnails
111                # for existence
112                thumbnail_keys = self._get(key, identity='thumbnails') or []
113                thumbnail_keys_set = set(thumbnail_keys)
114
115                for thumbnail_key in thumbnail_keys:
116                    if not self._get(thumbnail_key):
117                        thumbnail_keys_set.remove(thumbnail_key)
118
119                thumbnail_keys = list(thumbnail_keys_set)
120
121                if thumbnail_keys:
122                    self._set(key, thumbnail_keys, identity='thumbnails')
123                    continue
124
125            # if there is no image_file then this thumbnails key is just
126            # hangin' loose, If the thumbnail_keys ended up empty there is no
127            # reason for keeping it either
128            self._delete(key, identity='thumbnails')
129
130    def clear(self):
131        """
132        Brutely clears the key value store for keys with THUMBNAIL_KEY_PREFIX
133        prefix. Use this in emergency situations. Normally you would probably
134        want to use the ``cleanup`` method instead.
135        """
136        all_keys = self._find_keys_raw(settings.THUMBNAIL_KEY_PREFIX)
137        if all_keys:
138            self._delete_raw(*all_keys)
139
140    def _get(self, key, identity='image'):
141        """
142        Deserializing, prefix wrapper for _get_raw
143        """
144        value = self._get_raw(add_prefix(key, identity))
145
146        if not value:
147            return None
148
149        if identity == 'image':
150            return deserialize_image_file(value)
151
152        return deserialize(value)
153
154    def _set(self, key, value, identity='image'):
155        """
156        Serializing, prefix wrapper for _set_raw
157        """
158        if identity == 'image':
159            s = serialize_image_file(value)
160        else:
161            s = serialize(value)
162        self._set_raw(add_prefix(key, identity), s)
163
164    def _delete(self, key, identity='image'):
165        """
166        Prefix wrapper for _delete_raw
167        """
168        self._delete_raw(add_prefix(key, identity))
169
170    def _find_keys(self, identity='image'):
171        """
172        Finds and returns all keys for identity,
173        """
174        prefix = add_prefix('', identity)
175        raw_keys = self._find_keys_raw(prefix) or []
176        for raw_key in raw_keys:
177            yield del_prefix(raw_key)
178
179    #
180    # Methods which key-value stores need to implement
181    #
182    def _get_raw(self, key):
183        """
184        Gets the value from keystore, returns `None` if not found.
185        """
186        raise NotImplementedError()
187
188    def _set_raw(self, key, value):
189        """
190        Sets value associated to key. Key is expected to be shorter than 200
191        chars. Value is a ``unicode`` object with an unknown (reasonable)
192        length.
193        """
194        raise NotImplementedError()
195
196    def _delete_raw(self, *keys):
197        """
198        Deletes the keys. Silent failure for missing keys.
199        """
200        raise NotImplementedError()
201
202    def _find_keys_raw(self, prefix):
203        """
204        Finds all keys with prefix
205        """
206        raise NotImplementedError()
207