#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
**caches.py**
**Platform:**
Windows, Linux, Mac Os X.
**Description:**
This module defines the Application caches classes.
**Others:**
"""
#**********************************************************************************************************************
#*** Future imports.
#**********************************************************************************************************************
from __future__ import unicode_literals
#**********************************************************************************************************************
#*** External imports.
#**********************************************************************************************************************
import os
from PyQt4.QtCore import QObject
from PyQt4.QtCore import pyqtSignal
#**********************************************************************************************************************
#*** Internal imports.
#**********************************************************************************************************************
import foundations.exceptions
import foundations.verbose
import sibl_gui.ui.common
import sibl_gui.ui.workers
from umbra.globals.uiConstants import UiConstants
#**********************************************************************************************************************
#*** Module attributes.
#**********************************************************************************************************************
__author__ = "Thomas Mansencal"
__copyright__ = "Copyright (C) 2008 - 2013 - Thomas Mansencal"
__license__ = "GPL V3.0 - http://www.gnu.org/licenses/"
__maintainer__ = "Thomas Mansencal"
__email__ = "thomas.mansencal@gmail.com"
__status__ = "Production"
__all__ = ["LOGGER",
"CacheMetrics",
"AbstractResourcesCache",
"AsynchronousGraphicsItemsCache"]
LOGGER = foundations.verbose.installLogger()
#**********************************************************************************************************************
#*** Module classes and definitions.
#**********************************************************************************************************************
[docs]class CacheMetrics(foundations.dataStructures.Structure):
"""
This class represents a storage object for cache metrics.
"""
def __init__(self, **kwargs):
"""
.. Sphinx: Statements updated for auto-documentation purpose.
:param kwargs: type, content. ( Key / Value pairs )
"""
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
foundations.dataStructures.Structure.__init__(self, **kwargs)
[docs]class AbstractResourcesCache(QObject):
"""
This class is a `QObject <http://doc.qt.nokia.com/qobject.html>`_ subclass used as an abstract resources cache.
"""
contentAdded = pyqtSignal(list)
"""
This signal is emited by the :class:`AsynchronousGraphicsItemsCache` class
whenever content has been added. ( pyqtSignal )
:return: Content added to the cache. ( List )
"""
contentRemoved = pyqtSignal(list)
"""
This signal is emited by the :class:`AsynchronousGraphicsItemsCache` class
whenever content has been removed. ( pyqtSignal )
:return: Content removed from the cache. ( List )
"""
def __init__(self, parent=None):
"""
.. Sphinx: Statements updated for auto-documentation purpose.
:param parent: Object parent. ( QObject )
"""
QObject.__init__(self, parent)
self.__mapping = {}
#******************************************************************************************************************
#*** Attributes properties.
#******************************************************************************************************************
@property
def mapping(self):
"""
This method is the property for **self.__mapping** attribute.
:return: self.__mapping. ( Dictionary )
"""
return self.__mapping
@mapping.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def mapping(self, value):
"""
This method is the setter method for **self.__mapping** attribute.
:param value: Attribute value. ( Dictionary )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "mapping"))
@mapping.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def mapping(self):
"""
This method is the deleter method for **self.__mapping** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "mapping"))
#******************************************************************************************************************
#*** Class methods.
#******************************************************************************************************************
def __getitem__(self, item):
"""
This method reimplements the :meth:`object.__getitem__` method.
:param item: Item name. ( String )
:return: Item. ( Object )
"""
return self.__mapping.__getitem__(item)
def __setitem__(self, key, value):
"""
This method reimplements the :meth:`object.__setitem__` method.
:param key: Key. ( String )
:param value: Item. ( Object )
"""
self.__mapping.__setitem__(key, value)
def __iter__(self):
"""
This method reimplements the :meth:`object.__iter__` method.
:return: Paths iterator. ( Object )
"""
return self.__mapping.iteritems()
def __contains__(self, item):
"""
This method reimplements the :meth:`object.__contains__` method.
:param item: Item name. ( String )
:return: Item existence. ( Boolean )
"""
return item in self.__mapping.keys()
def __len__(self):
"""
This method reimplements the :meth:`object.__len__` method.
:return: Paths count. ( Integer )
"""
return len(self.__mapping.keys())
[docs] def isCached(self, key):
"""
This method returns if given content is cached.
:param key: Content to retrieve. ( Object )
:return: Is content cached. ( Boolean )
"""
return key in self
[docs] def listContent(self):
"""
This method lists the cache content.
:return: Cache content. ( List )
"""
return self.__mapping.keys()
[docs] def addContent(self, **content):
"""
This method adds given content to the cache.
:param \*\*content: Content to add. ( \*\* )
:return: Method success. ( Boolean )
"""
LOGGER.debug("> Adding '{0}' content to the cache.".format(self.__class__.__name__, content))
self.__mapping.update(**content)
self.contentAdded.emit(content.keys())
return True
[docs] def removeContent(self, *keys):
"""
This method removes given content from the cache.
:param \*keys: Content to remove. ( \* )
:return: Method success. ( Boolean )
"""
LOGGER.debug("> Removing '{0}' content from the cache.".format(self.__class__.__name__, keys))
for key in keys:
if not key in self:
raise KeyError("{0} | '{1}' key doesn't exists in cache content!".format(self.__class__.__name__, key))
del(self.__mapping[key])
self.contentRemoved.emit([key])
return True
[docs] def getContent(self, key):
"""
This method gets given content from the cache.
:param key: Content to retrieve. ( Object )
:return: Content. ( Object )
"""
LOGGER.debug("> Retrieving '{0}' content from the cache.".format(self.__class__.__name__, key))
return self.__mapping.get(key)
[docs] def flushContent(self):
"""
This method flushes the cache content.
:return: Method success. ( Boolean )
"""
LOGGER.debug("> Flushing cache content.".format(self.__class__.__name__))
content = self.__mapping.keys()
self.__mapping.clear()
self.contentRemoved.emit(content)
return True
[docs] def getMetrics(self):
"""
This method returns the cache metrics.
:return: Cache metrics. ( Dictionary )
"""
cacheMetrics = CacheMetrics()
cacheMetrics.type = None
cacheMetrics.content = dict.fromkeys(self.__mapping.keys())
return cacheMetrics
[docs]class AsynchronousGraphicsItemsCache(AbstractResourcesCache):
"""
This class provides an asynchronous graphics items cache.
"""
def __init__(self, parent=None, type=None, placeholder=None):
"""
.. Sphinx: Statements updated for auto-documentation purpose.
:param parent: Object parent. ( QObject )
:param type: Cache type. ( QImage / QPixmap / QIcon )
:param placeholder: Placeholder image. ( String )
"""
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
AbstractResourcesCache.__init__(self, parent)
# --- Setting class attributes. ---
self.__type = type
self.__placeholder = placeholder
self.__placeholderGraphicsItem = None
self.__worker = sibl_gui.ui.workers.GraphicsItem_worker()
self.__worker.start()
self.__worker.imageLoaded.connect(self.__worker__imageLoaded)
self.__setPlaceholderGraphicsItem(placeholder)
#******************************************************************************************************************
#*** Attributes properties.
#******************************************************************************************************************
@property
def type(self):
"""
This method is the property for **self.__type** attribute.
:return: self.__type. ( QObject )
"""
return self.__type
@type.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def type(self, value):
"""
This method is the setter method for **self.__type** attribute.
:param value: Attribute value. ( QObject )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "type"))
@type.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def type(self):
"""
This method is the deleter method for **self.__type** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "type"))
@property
def placeholder(self):
"""
This method is the property for **self.__placeholder** attribute.
:return: self.__placeholder. ( String )
"""
return self.__placeholder
@placeholder.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def placeholder(self, value):
"""
This method is the setter method for **self.__placeholder** attribute.
:param value: Attribute value. ( String )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "placeholder"))
@placeholder.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def placeholder(self):
"""
This method is the deleter method for **self.__placeholder** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "placeholder"))
@property
def placeholderGraphicsItem(self):
"""
This method is the property for **self.__placeholderGraphicsItem** attribute.
:return: self.__placeholderGraphicsItem. ( QObject )
"""
return self.__placeholderGraphicsItem
@placeholderGraphicsItem.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
def placeholderGraphicsItem(self, value):
"""
This method is the setter method for **self.__placeholderGraphicsItem** attribute.
:param value: Attribute value. ( QObject )
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is read only!".format(self.__class__.__name__, "placeholderGraphicsItem"))
@placeholderGraphicsItem.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def placeholderGraphicsItem(self):
"""
This method is the deleter method for **self.__placeholderGraphicsItem** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "placeholderGraphicsItem"))
@property
def worker(self):
"""
This method is the property for **self.__worker** attribute.
:return: self.__worker. ( QThread )
"""
return self.__worker
@worker.setter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(AssertionError)
def worker(self, value):
"""
This method is the setter method for **self.__worker** attribute.
:param value: Attribute value. ( QThread )
"""
if value is not None:
assert type(value) is unicode, "'{0}' attribute: '{1}' type is not 'unicode'!".format(
"worker", value)
assert os.path.exists(value), "'{0}' attribute: '{1}' file doesn't exists!".format("worker", value)
self.__worker = value
@worker.deleter
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def worker(self):
"""
This method is the deleter method for **self.__worker** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "worker"))
#******************************************************************************************************************
#*** Class methods.
#******************************************************************************************************************
def __worker__imageLoaded(self, image, size):
"""
This method is triggered by the :obj:`AsynchronousGraphicsItemsCache.worker` method when an image has been loaded.
:param image: Loaded image. ( QImage )
:param size: Image size. ( String )
"""
graphicsItem = sibl_gui.ui.common.convertImage(image, self.__type)
graphicsItem.data = image.data
path = graphicsItem.data.path
if not self.isCached(path):
return
self[path][foundations.strings.toString(size)] = graphicsItem
self.contentAdded.emit([path])
def __setPlaceholderGraphicsItem(self, path):
"""
This method sets the placeholderGraphicsItem graphics item.
:param path: Placeholder image path. ( String )
"""
if not foundations.common.pathExists(path):
LOGGER.warning(
"!> {0} | '{1}' placeholder graphics item file doesn't exists, unexpected behavior may occur!".format(
self.__class__.__name__, self))
return
self.__placeholderGraphicsItem = self.__type(path)
self.__placeholderGraphicsItem.data = sibl_gui.ui.common.getImageInformationsHeader(path, self.__placeholderGraphicsItem)
[docs] def getContent(self, key, size="Default"):
"""
This method reimplements the :meth:`AbstractResourcesCache.getContent` method.
:param key: Content to retrieve. ( Object )
:param size: Size to retrieve. ( String )
:return: Content. ( Object )
"""
LOGGER.debug("> Retrieving '{0}' content from the cache.".format(self.__class__.__name__, key))
content = self.mapping.get(key)
if content is not None:
return content.get(size)
[docs] def flushContent(self):
"""
This method reimplements the :meth:`AbstractResourcesCache.flushContent` method.
:return: Method success. ( Boolean )
"""
LOGGER.debug("> Flushing cache content.".format(self.__class__.__name__))
if self.__worker.flushRequests():
content = self.mapping.keys()
self.mapping.clear()
self.contentRemoved.emit(content)
return True
return False
[docs] def loadContent(self, **content):
"""
This method loads given content into the cache.
:param \*\*content: Content to add. ( \*\* )
:return: Method success. ( Boolean )
"""
LOGGER.debug("> Adding '{0}' content to the cache.".format(self.__class__.__name__, content))
for path, data in content.iteritems():
type, size = data
if not foundations.common.pathExists(path):
LOGGER.warning("!> {0} | '{1}' file doesn't exists and has been skipped!".format(
self.__class__.__name__, path))
continue
if not self.isCached(path):
self[path] = dict.fromkeys(UiConstants.thumbnailsSizes.keys())
image = sibl_gui.ui.common.loadGraphicsItem(path, type, size)
image.data = sibl_gui.ui.common.getImageInformationsHeader(path, image)
self[path][size] = image
self.contentAdded.emit([path])
return True
#*** Sphinx: Decorator commented for auto-documentation purpose. @foundations.exceptions.handleExceptions(foundations.exceptions.FileExistsError)
[docs] def loadAsynchronousContent(self, **content):
"""
This method loads given content asynchronously into the cache.
:param \*\*content: Content to add. ( \*\* )
:return: Method success. ( Boolean )
"""
LOGGER.debug("> Adding '{0}' content to the cache.".format(self.__class__.__name__, content))
for path, data in content.iteritems():
type, size, placeholder = data
if not foundations.common.pathExists(path):
raise foundations.exceptions.FileExistsError("{0} | '{1}' file doesn't exists!".format(
self.__class__.__name__, path))
if not self.isCached(path):
self[path] = dict.fromkeys(UiConstants.thumbnailsSizes.keys())
image = self.getContent(path, size)
if image is not None:
if not hasattr(image, "data"):
LOGGER.debug("> {0} | '{1}' object has not 'data' attribute and has been skipped!".format(
self.__class__.__name__, image))
continue
if image.data.path != path:
continue
if image.data.osStats.st_mtime == os.stat(path).st_mtime:
continue
else:
LOGGER.info("{0} | '{1}' file has been modified and will be reloaded!".format(
self.__class__.__name__, path))
self[path][size] = placeholder if placeholder is not None else self.__placeholderGraphicsItem
self.contentAdded.emit([path])
self.__worker.addRequest((path, size))
return True
[docs] def getMetrics(self):
"""
This method reimplements the :meth:`AbstractResourcesCache.getMetrics` method.
:return: Cache metrics. ( Dictionary )
"""
cacheMetrics = AbstractResourcesCache.getMetrics(self)
cacheMetrics.type = self.__type
content = {}
for path, data in self.mapping.iteritems():
thumbnails = {}
for size, thumbnail in data.iteritems():
thumbnails[size] = None if thumbnail is None else (sibl_gui.ui.common.getThumbnailPath(path, size),
thumbnail.data)
content[path] = thumbnails
cacheMetrics.content = content
return cacheMetrics