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