"""
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