""" ImageCache.py

Module for handling images
"""
__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
__license__ = """
Straw is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

Straw is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA. """

import NetworkConstants
import Event
import URLFetch
import PollManager
import ItemStore
import utils
import error
import Config

STATUS_IMAGE_WAITING = None
STATUS_IMAGE_BROKEN = None

class CacheEntry(object):
    def __init__(self, image, count, pollstopper = None, restore = False):
        self._image = image
        self._count = count
        self._pollstopper = pollstopper
        if not restore:
            self._save_count()

    @apply
    def image():
        doc="The image object that is associated with this class"
        def fget(self):
            return self._image
        def fset(self, image):
            self._image = image
        return property(**locals())

    @apply
    def count():
        doc="The number of instances of this image"
        def fget(self):
            return self._count
        def fset(self, count):
            self._count = count
            self._save_count()
        return property(**locals())

    @apply
    def pollstopper():
        doc="the stopper object for this cache entry"
        def fget(self):
            return self._pollstopper
        def fset(self, pollstopper):
            self._pollstopper = pollstopper
            if pollstopper:
                pollstopper.signal_connect(Event.PollingStoppedSignal,
                                           self._polling_stopped)
        return property(**locals())

    def incr_count(self): self._count += 1
    def decr_count(self): self._count -= 1

    def _polling_stopped(self):
        self._pollstopper = None

    def _save_count(self):
        ItemStore.get_instance().set_image_count(self._image.url, self._count)


class Cache(Event.SignalEmitter):
    """Image cache with explicit reference counting."""
    def __init__(self):
        Event.SignalEmitter.__init__(self)
        self.initialize_slots(Event.ImageUpdatedSignal)
        # self.__cache contains items of the form [Image, refcount]
        self.__cache = dict()

    def __getitem__(self, key):
        return self.__cache[key].image

    def add_refer(self, key, restore = False, item = None):
        if key not in self.__cache:
            if not restore:
                # fetch image
                image = Image(key, Image.WAITING)
                ic = ImageConsumer(image)
                headers = {}
                stopper = None
                if item and item.feed:
                    headers['Referer'] = item.feed.location
                try:
                    stopper = URLFetch.connection_manager.request(
                        key, ic,
                        priority=NetworkConstants.PRIORITY_IMAGE,
                        headers=headers)
                except URLFetch.RequestSchemeException, e:
                    ic.http_failed(e)
                except Exception, e:
                    error.logtb("ImageCache.add_refer:  ", str(e))
                    ic.http_failed(e)
                self.__cache[key] = CacheEntry(
                    image, 1, PollManager.PollStopper(stopper, image), restore=restore)
            else:
                image = Image(key, Image.DATA_IN_DB)
                self.__cache[key] = CacheEntry(image, 1, pollstopper=None, restore=restore)
        elif key in self.__cache and not restore:
            self.__cache[key].incr_count()

    def remove_refer(self, key):
        if self.__cache.has_key(key):
            entry = self.__cache[key]
            entry.decr_count()
            if entry.count == 0:
                del self.__cache[key]
                self.emit_signal(Event.ImageUpdatedSignal(self, key, None))

    def image_updated(self, url, data):
        self.__cache[url].pollstopper = None
        self.emit_signal(Event.ImageUpdatedSignal(self, url, data))

    def set_image(self, url, image):
        if self.__cache.has_key(url):
            self.__cache[url].incr_count()
        else:
            self.__cache[url] = CacheEntry(image, 1)

    def set_image_with_count(self, url, image, count, restore=False):
        self.__cache[url] = CacheEntry(image, count, restore=restore)

    def stop_transfer(self, url):
        image = self.__cache.get(url, None)
        if image and image.pollstopper:
            self.__cache[url].pollstopper.stop()
            self.__cache[url].pollstopper = None

class Image:
    WAITING = 1
    DATA_IN_DB = 2
    FAILED = 3

    def __init__(self, url, status = DATA_IN_DB):
        self.url = url
        self.status = status

    def get_data(self):
        if self.status == self.WAITING:
            return STATUS_IMAGE_WAITING
        elif self.status == self.DATA_IN_DB:
            data = self.read_data()
            if data is None:
                self.status = self.FAILED
                return STATUS_IMAGE_BROKEN
            return data
        else:
            return STATUS_IMAGE_BROKEN

    def set_data(self, data):
        cache.image_updated(self.url, data)
        self.status = self.DATA_IN_DB

    def set_failed(self):
        self.status = self.FAILED

    def read_data(self):
        istore = ItemStore.get_instance()
        return istore.read_image(self.url)

    def _return_id(self):
        return "%d %s" % (id(self), self.url)

cache = Cache()

class ImageConsumer:
    def __init__(self, imobj):
        self._imobj = imobj

    def http_results(self, status, headers, data):
        if status[1] == 200:
            self._imobj.set_data(data)
        else:
            self._imobj.set_failed()

    def http_failed(self, exception):
        self._imobj.set_failed()

    def operation_stopped(self):
        self._imobj.set_failed()

def initialize():
    global STATUS_IMAGE_WAITING
    global STATUS_IMAGE_BROKEN
    libdir = utils.find_image_dir()
    STATUS_IMAGE_WAITING = open(
        libdir + "/image-waiting.png").read()
    STATUS_IMAGE_BROKEN = open(
        libdir + "/image-broken.png").read()
