more Event cleanups and ItemList module refactoring
[straw.git] / src / lib / ImageCache.py
blob861b1cddd9b8f2feed914ea53bfc7be6e27511dc
1 """ ImageCache.py
3 Module for handling images
4 """
5 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
6 __license__ = """
7 Straw is free software; you can redistribute it and/or modify it under the
8 terms of the GNU General Public License as published by the Free Software
9 Foundation; either version 2 of the License, or (at your option) any later
10 version.
12 Straw is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along with
17 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18 Place - Suite 330, Boston, MA 02111-1307, USA. """
20 import NetworkConstants
21 import URLFetch
22 import PollManager
23 import ItemStore
24 import utils
25 import error
26 import Config
27 import gobject
29 STATUS_IMAGE_WAITING = None
30 STATUS_IMAGE_BROKEN = None
32 class CacheEntry(object):
33 def __init__(self, image, count, pollstopper = None, restore = False):
34 self._image = image
35 self._count = count
36 self._pollstopper = pollstopper
37 if not restore:
38 self._save_count()
40 @apply
41 def image():
42 doc="The image object that is associated with this class"
43 def fget(self):
44 return self._image
45 def fset(self, image):
46 self._image = image
47 return property(**locals())
49 @apply
50 def count():
51 doc="image ref count"
52 def fget(self):
53 return self._count
54 def fset(self, count):
55 self._count = count
56 self._save_count()
57 return property(**locals())
59 @apply
60 def pollstopper():
61 doc="the stopper object for this cache entry"
62 def fget(self):
63 return self._pollstopper
64 def fset(self, pollstopper):
65 self._pollstopper = pollstopper
66 if pollstopper:
67 pollstopper.connect('stopped', self._polling_stopped)
68 return property(**locals())
70 def incr_count(self): self._count += 1
71 def decr_count(self): self._count -= 1
73 def _polling_stopped(self, poller):
74 self._pollstopper = None
76 def _save_count(self):
77 ItemStore.get_instance().set_image_count(self._image.url, self._count)
80 class Cache(gobject.GObject):
81 """Image cache with explicit reference counting."""
83 __gsignals__ = {
84 'image-updated' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
85 (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT))
88 def __init__(self):
89 gobject.GObject.__init__(self)
90 # self.__cache contains items of the form [Image, refcount]
91 self.__cache = dict()
93 def __getitem__(self, key):
94 return self.__cache[key].image
96 def add_refer(self, key, restore = False, item = None):
97 if key not in self.__cache:
98 if not restore:
99 # fetch image
100 image = Image(key, Image.WAITING)
101 ic = ImageConsumer(image)
102 headers = {}
103 stopper = None
104 if item and item.feed:
105 headers['Referer'] = item.feed.location
106 try:
107 stopper = URLFetch.get_instance().request(
108 key, ic,
109 priority=NetworkConstants.PRIORITY_IMAGE,
110 headers=headers)
111 except URLFetch.RequestSchemeException, e:
112 ic.http_failed(e)
113 except Exception, e:
114 error.logtb("ImageCache.add_refer: ", str(e))
115 ic.http_failed(e)
116 self.__cache[key] = CacheEntry(
117 image, 1, PollManager.PollStopper(stopper, image), restore=restore)
118 else:
119 image = Image(key, Image.DATA_IN_DB)
120 self.__cache[key] = CacheEntry(image, 1, pollstopper=None, restore=restore)
121 elif key in self.__cache and not restore:
122 self.__cache[key].incr_count()
124 def remove_refer(self, key):
125 if self.__cache.has_key(key):
126 entry = self.__cache[key]
127 entry.decr_count()
128 if entry.count == 0:
129 del self.__cache[key]
130 self.emit('image-updated', url, None)
132 def image_updated(self, url, data):
133 self.__cache[url].pollstopper = None
134 self.emit('image-updated', url, data)
136 def set_image(self, url, image):
137 if self.__cache.has_key(url):
138 self.__cache[url].incr_count()
139 else:
140 self.__cache[url] = CacheEntry(image, 1)
142 def set_image_with_count(self, url, image, count, restore=False):
143 self.__cache[url] = CacheEntry(image, count, restore=restore)
145 def stop_transfer(self, url):
146 image = self.__cache.get(url, None)
147 if image and image.pollstopper:
148 self.__cache[url].pollstopper.stop()
149 self.__cache[url].pollstopper = None
151 class Image:
152 WAITING = 1
153 DATA_IN_DB = 2
154 FAILED = 3
156 def __init__(self, url, status = DATA_IN_DB):
157 self.url = url
158 self.status = status
160 def get_data(self):
161 if self.status == self.WAITING:
162 return STATUS_IMAGE_WAITING
163 elif self.status == self.DATA_IN_DB:
164 data = self.read_data()
165 if data is None:
166 self.status = self.FAILED
167 return STATUS_IMAGE_BROKEN
168 return data
169 else:
170 self.status = self.FAILED
171 return STATUS_IMAGE_BROKEN
173 def set_data(self, data):
174 cache.image_updated(self.url, data)
175 self.status = self.DATA_IN_DB
177 def set_failed(self):
178 self.status = self.FAILED
180 def read_data(self):
181 istore = ItemStore.get_instance()
182 return istore.read_image(self.url)
184 def _return_id(self):
185 return "%d %s" % (id(self), self.url)
187 cache = Cache()
189 class ImageConsumer:
190 def __init__(self, imobj):
191 self._imobj = imobj
193 def http_results(self, status, data):
194 if status[1] == 200:
195 self._imobj.set_data(data)
196 else:
197 self._imobj.set_failed()
199 def http_failed(self, exception):
200 self._imobj.set_failed()
202 def operation_stopped(self):
203 # XXX operation stopped is not really failed
204 self._imobj.set_failed()
206 def initialize():
207 global STATUS_IMAGE_WAITING
208 global STATUS_IMAGE_BROKEN
209 libdir = utils.find_image_dir()
210 STATUS_IMAGE_WAITING = open(
211 libdir + "/image-loading.svg").read()
212 STATUS_IMAGE_BROKEN = open(
213 libdir + "/image-missing.svg").read()