""" Pinximus, an image archive server. Dependencies: CherryPy, available at: http://www.cherrypy.org/ Dejavu, available at: http://projects.amor.org/dejavu/ PIL, available at: http://www.pythonware.com/products/pil/ On Debian: apt-get install python-imaging LICENSE ------- This work, including the source code, documentation and related data, is placed into the public domain. The original author is Robert Brewer. fumanchu@aminus.org svn://projects.amor.org/misc/pinximus/trunk THIS SOFTWARE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR REDISTRIBUTION OF THIS SOFTWARE. """ import os localDir = os.path.join(os.getcwd(), os.path.dirname(__file__)) import re try: set except NameError: from sets import Set as set try: from cStringIO import StringIO except ImportError: from StringIO import StringIO import cherrypy from cherrypy.lib.cptools import serveFile import cobbler import dejavu from dejavu import logic from PIL import Image from model import * config = {'Libraries': {}, 'Icons': {}, } def tags_from_string(tagstr): return [x for x in re.split(r"[,; \t\n\r\f\v]", tagstr) if x] class ResultSet(cobbler.Template): """Template for generating a set of result elements.""" COLUMNS = 3 def __init__(self): cobbler.Template.__init__(self) self.cells = [] def add(self, **params): params.update(self.params) self.cells.append(self.templateText % params) def assemble(self): output = [] curcol = 1 for cell in self.cells: output.append(cell) curcol += 1 if curcol > self.COLUMNS: curcol = 1 output.append("") output.append("") return u'\n'.join(output) class ThumbnailSet(cobbler.Template): """Template for generating a set of thumbnail elements.""" COLUMNS = 3 def __init__(self, resources=None): cobbler.Template.__init__(self, localDir, "thumb.html") if resources: self.resources = set(resources) else: self.resources = set() def add(self, resource): self.resources.add(resource) def assemble(self): output = [] curcol = 1 resources = [(x.name(), x) for x in self.resources] resources.sort() for name, res in resources: p = {'id': res.ID, 'library': res.Library().Name, 'libraryid': res.LibraryID, 'name': name, 'tags': " ".join(res.tags()), 'title': '%s (%sx%s)' % (res.Path, res.Width, res.Height), } p.update(self.params) output.append(self.templateText % p) curcol += 1 if curcol > self.COLUMNS: curcol = 1 output.append("") output.append("") return u'\n'.join(output) class Root: def index(self, q=None): main = cobbler.Template(localDir, "main.html") if q is None: main['results'] = "" else: box = arena.new_sandbox() f = logic.Expression(lambda r, t: t.Value in tags_from_string(q)) results = [res for res, tag in box.recall(Resource & Tag, f)] main['results'] = ThumbnailSet(results).assemble() return main index.exposed = True def library(self, id=None): main = cobbler.Template(localDir, "main.html") box = arena.new_sandbox() if id is None or id == 'upload': # Show a list of libraries with links res = id or 'library' libs = box.recall(Library) libs.sort(dejavu.sort('Name')) rs = ResultSet() rs.templateText = (' %(name)s') for lib in libs: rs.add(id=lib.ID, name=lib.Name, res=res) main['results'] = rs.assemble() else: lib = box.Library(int(id)) if lib is None: raise cherrypy.HTTPError(404) main['results'] = ThumbnailSet(lib.Resource()).assemble() return main library.exposed = True def resource(self, id, *args, **kwargs): box = arena.new_sandbox() res = box.Resource(int(id)) if res is None: raise cherrypy.HTTPError(404) if args: args = list(args) cmd = args.pop(0) if cmd == "tags": if cherrypy.request.method == "POST": res.set_tags(tags_from_string(kwargs['tags'])) cherrypy.response.status = 204 box.flush_all() else: return " ".join(res.tags()) else: raise cherrypy.HTTPError(404) else: return serveFile(res.fullpath()) resource.exposed = True def thumb(self, id): box = arena.new_sandbox() res = box.Resource(int(id)) if res is None: raise cherrypy.HTTPError(404) return serveFile(res.thumbpath()) thumb.exposed = True def tag(self): main = cobbler.Template(localDir, "main.html") box = arena.new_sandbox() tags = box.distinct(Tag, ['Value']) tags.sort() rs = ResultSet() rs.templateText = (' ' '%(name)s') for t in tags: rs.add(name=t) main['results'] = rs.assemble() return main tag.exposed = True def upload(self, id, newfile=None, newname=None): box = arena.new_sandbox() lib = box.Library(int(id)) if lib is None: raise cherrypy.HTTPError(404) if cherrypy.request.method == 'POST': # Accept the uploaded file libpath = os.path.normpath(lib.Path) head, newname = os.path.split(newname or newfile.filename) fullpath = os.path.join(libpath, newname) outfile = open(fullpath, 'wb') while True: data = newfile.file.read(8192) if not data: break outfile.write(data) outfile.close() lib.register_resource(fullpath) box.flush_all() return self.library(id) else: # Show an upload form. main = cobbler.Template(localDir, "main.html") main['results'] = """

Upload file to %s

""" % (lib.Name, cobbler.Template.params['root'], id) return main upload.exposed = True def localize(path): """If path is relative, return localDir + path, else return path unchanged.""" if not os.path.isabs(path): path = os.path.join(localDir, path) return path def init_storage(): # Override arena.load so that we can add our own sections. import ConfigParser configFileName = os.path.join(localDir, "storage.conf") parser = ConfigParser.ConfigParser() # Make names case-sensitive by overriding optionxform. parser.optionxform = unicode parser.read(configFileName) stores = [] for section in parser.sections(): opts = dict(parser.items(section)) if section in config: config[section].update(opts) else: if opts.get("Class") == "dejavu.storage.storeshelve.StorageManagerShelve": path = opts.get("Path") if path: opts['Path'] = localize(path) stores.append((int(opts.get("Load Order", "0")), section, opts)) stores.sort() for order, name, options in stores: arena.add_store(name, options[u'Class'], options) # Register unit classes and upgrade schema. arena.register(Library) arena.register(Resource) arena.register(Tag) ps = PinximusSchema(arena) ps.upgrade() ps.assert_storage() # Compare libraries in conf with libraries in the database. box = arena.new_sandbox() existing = dict([(lib.Path, lib) for lib in box.recall(Library)]) for name, path in config['Libraries'].iteritems(): path = localize(path) lib = existing.get(path) if lib: lib.Name = name else: lib = Library(Name=name, Path=path) cherrypy.log("Loading library: %s" % path, "DEJAVU") box.memorize(lib) cherrypy.log("Library loaded: %s" % path, "DEJAVU") def djvlog(message, flag): """Dejavu logger (writes to error.log).""" if flag & arena.logflags: cherrypy.log(message, "DEJAVU", 0) def shutdown_storage(): arena.shutdown() cherrypy.log('Dejavu arena shut down.', "DEJAVU", 0) def init_web(): conf = os.path.join(localDir, "web.conf") conf = cherrypy.config.dict_from_config_file(conf) conf.setdefault("/", {}).update( {'static_filter.on': True, 'static_filter.dir': os.path.join(localDir, 'static'), 'static_filter.match': '\.(bmp|css|gif|ico|js|jpe?g|png)$', }) logfile = conf['global'].get('server.log_file') if logfile: conf['global']['server.log_file'] = localize(logfile) logfile = conf['global'].get('server.log_access_file') if logfile: conf['global']['server.log_access_file'] = localize(logfile) cherrypy.tree.mount(Root(), conf=conf) cobbler.Template.params.update(conf["cobbler"]) cherrypy.server.on_stop_server_list.append(shutdown_storage) arena.log = djvlog