terom@166: terom@166: import pypngtile terom@166: terom@166: import os.path terom@166: import threading terom@166: terom@166: class Error (Exception): terom@166: pass terom@166: terom@166: class NotFound (KeyError): terom@166: pass terom@166: terom@166: class InvalidImage (Exception): terom@166: pass terom@166: terom@166: class UncachedImage (Exception): terom@166: pass terom@166: terom@166: class PNGTileStore (object): terom@166: """ terom@166: Access pypngtile.Image's on a filesystem. terom@166: terom@166: Intended to be threadsafe for open() terom@166: """ terom@166: terom@166: IMAGE_TYPES = ( terom@166: 'png', terom@166: ) terom@166: terom@166: def __init__ (self, image_root): terom@166: if not os.path.isdir(image_root) : terom@166: raise Error("Given image_root does not exist: {image_root}".format(image_root=image_root)) terom@166: terom@166: self.image_root = os.path.abspath(image_root) terom@166: terom@166: # cache opened Images terom@166: self.images = { } terom@166: self.images_lock = threading.Lock() terom@166: terom@166: def lookup (self, url): terom@166: """ terom@166: Lookup image by request path. terom@166: terom@166: Returns name, path, type. For dirs, type will be None. terom@166: terom@166: Raises Error, NotFound, InvalidImage terom@166: terom@166: Threadless. terom@166: """ terom@166: terom@166: if not os.path.isdir(self.image_root): terom@166: raise Error("Server image_root has gone missing") terom@166: terom@166: # path to image terom@166: name = url.lstrip('/') terom@166: terom@166: # build absolute path terom@166: path = os.path.abspath(os.path.join(self.image_root, name)) terom@166: terom@166: # ensure the path points inside the data root terom@166: if not path.startswith(self.image_root): terom@166: raise NotFound(name) terom@166: terom@166: if not os.path.exists(path): terom@166: raise NotFound(name) terom@166: terom@166: # determine time terom@166: if os.path.isdir(path): terom@166: return name, path, None terom@166: else: terom@166: basename, type = path.rsplit('.', 1) terom@166: terom@166: if type not in self.IMAGE_TYPES: terom@166: raise InvalidImage(name) terom@166: terom@166: return name, path, type terom@166: terom@166: def list (self, url): terom@166: """ terom@166: Yield a series of valid sub-names for the given directory: terom@166: foo.type terom@166: foo/ terom@166: terom@166: The yielded items should be sorted before use. terom@166: """ terom@166: terom@166: name, root, type = self.lookup(url) terom@166: terom@166: if type: terom@166: raise InvalidImage(name) terom@166: terom@166: for name in os.listdir(root): terom@166: path = os.path.join(root, name) terom@166: terom@166: # skip dotfiles terom@166: if name.startswith('.'): terom@166: continue terom@166: terom@166: # show dirs terom@166: if os.path.isdir(path): terom@166: if not os.access(path, os.R_OK): terom@166: # skip inaccessible dirs terom@166: continue terom@166: terom@166: yield name + '/' terom@166: terom@166: # examine ext terom@166: if '.' in name: terom@166: name_base, name_type = name.rsplit('.', 1) terom@166: else: terom@166: name_base = name terom@166: name_type = None terom@166: terom@166: # show .png files with a .cache file terom@166: if name_type in self.IMAGE_TYPES and os.path.exists(os.path.join(root, name_base + '.cache')): terom@166: yield name terom@166: terom@166: def open (self, url): terom@166: """ terom@166: Return Image object for given URL. terom@166: terom@166: Raises UncachedImage, pypngtile.Error terom@166: terom@166: Threadsafe. terom@166: """ terom@166: terom@166: name, path, type = self.lookup(url) terom@166: terom@166: if not type: terom@166: raise InvalidImage(name) terom@166: terom@166: # get Image object terom@166: with self.images_lock: terom@166: image = self.images.get(path) terom@166: terom@166: if not image: terom@166: # open terom@166: image = pypngtile.Image(path) terom@166: terom@166: # check terom@166: if image.status() not in (pypngtile.CACHE_FRESH, pypngtile.CACHE_STALE): terom@166: raise UncachedImage(name) terom@166: terom@166: # load terom@166: image.open() terom@166: terom@166: # cache terom@166: with self.images_lock: terom@166: # we don't really care if some other thread raced us on the same Image and opened it up concurrently... terom@166: # this is just a cache terom@166: self.images[path] = image terom@166: terom@166: return image, name