work on aggregating items of category feeds and minor fixes
[straw.git] / src / lib / ImageCache.py
blobbc12a60cf0a0b75851c6645571732254fbf08917
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', key, None)
132 def image_updated(self, url, data):
133 #self.__cache[url].pollstopper = None
134 self.set_image(url,data)
135 self.emit('image-updated', url, data)
137 def set_image(self, url, image):
138 if self.__cache.has_key(url):
139 self.__cache[url].incr_count()
140 else:
141 self.__cache[url] = CacheEntry(image, 1)
143 def set_image_with_count(self, url, image, count, restore=False):
144 self.__cache[url] = CacheEntry(image, count, restore=restore)
146 def stop_transfer(self, url):
147 image = self.__cache.get(url, None)
148 if image and image.pollstopper:
149 self.__cache[url].pollstopper.stop()
150 self.__cache[url].pollstopper = None
152 class Image:
153 WAITING = 1
154 DATA_IN_DB = 2
155 FAILED = 3
157 def __init__(self, url, status = DATA_IN_DB):
158 self.url = url
159 self.status = status
161 def get_data(self):
162 if self.status == self.WAITING:
163 return STATUS_IMAGE_WAITING
164 elif self.status == self.DATA_IN_DB:
165 data = self.read_data()
166 if data is None:
167 self.status = self.FAILED
168 return STATUS_IMAGE_BROKEN
169 return data
170 else:
171 self.status = self.FAILED
172 return STATUS_IMAGE_BROKEN
174 def set_data(self, data):
175 cache.image_updated(self.url, data)
176 self.status = self.DATA_IN_DB
178 def set_failed(self):
179 self.status = self.FAILED
181 def read_data(self):
182 istore = ItemStore.get_instance()
183 return istore.read_image(self.url)
185 def _return_id(self):
186 return "%d %s" % (id(self), self.url)
188 cache = Cache()
190 class ImageConsumer:
191 def __init__(self, imobj):
192 self._imobj = imobj
194 def http_results(self, status, data):
195 if status[1] == 200:
196 self._imobj.set_data(data)
197 else:
198 self._imobj.set_failed()
200 def http_failed(self, exception):
201 self._imobj.set_failed()
203 def operation_stopped(self):
204 # XXX operation stopped is not really failed
205 self._imobj.set_failed()
207 def initialize():
208 global STATUS_IMAGE_WAITING
209 global STATUS_IMAGE_BROKEN
210 libdir = utils.find_image_dir()
211 STATUS_IMAGE_WAITING = open(
212 libdir + "/image-loading.svg").read()
213 STATUS_IMAGE_BROKEN = open(
214 libdir + "/image-missing.svg").read()