<commit_before># -*- coding: utf-8 -*-

from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.routing import Map, Rule, BaseConverter
from werkzeug.utils import redirect
from werkzeug.wrappers import Request, Response
from werkzeug.wsgi import SharedDataMiddleware
import ConfigParser
import logging
import logging.config
import os
import struct
import urlparse
import werkzeug

# Private Static Constants 
DOT_BMP = ".bmp"
DOT_JPG = ".jpg"
DOT_JPGS = ('.jpg', 'jpeg')
DOT_JP2 = ".jp2"
DOT_JSON = ".json"
DOT_XML = ".xml"

_LIB = os.path.join(os.path.dirname(__file__), 'lib')
_BIN = os.path.join(os.path.dirname(__file__), 'bin')
_ETC = os.path.join(os.path.dirname(__file__), 'etc')
_ENV = {"LD_LIBRARY_PATH":_LIB, "PATH":_LIB + ":$PATH"}

conf_file = os.path.join(_ETC, 'patokah.conf')
logging.config.fileConfig(conf_file)
logr = logging.getLogger('patokah')
logr.info("Logging initialized")


def create_app():
	app = Patokah()
	return app

# Note when we start to stream big files from the filesystem, see:
# http://stackoverflow.com/questions/5166129/how-do-i-stream-a-file-using-werkzeug

# TODO: do we need a format converter? Would like to support png as well.

class Patokah(object):
	def __init__(self):
		
		# Configuration - Everything else
		_conf = ConfigParser.RawConfigParser()
		_conf.read(conf_file)

		self.CJPEG = _conf.get('utilities', 'cjpeg')
		self.MKFIFO = _conf.get('utilities', 'mkfifo')
		self.RM = _conf.get('utilities', 'rm')
		
		self.TMP_DIR = _conf.get('directories', 'tmp')
		self.CACHE_ROOT = _conf.get('directories', 'cache_root')
		self.SRC_IMAGES_ROOT = _conf.get('directories', 'src_img_root')
		
		for d in (self.TMP_DIR, self.CACHE_ROOT):
			if not os.path.exists(d):
				os.makedirs(d, 0755)
				logr.info("Created " + d)

		converters = {
				'region' : RegionConverter,
				'size' : SizeConverter,
				'rotation' : RotationConverter
			}
		self.url_map = Map([
#			Rule('/', endpoint='???'),
			Rule('/<path:id>/info.<extension>', endpoint='get_img_metadata',),
			Rule('/<path:ident>/<region:region>/<size:size>/<rotation:rotation>/<quality>.<format>', endpoint='get_img'),
			Rule('/ctests/<path:ident>/<region:region>/<size:size>/<rotation:rotation>/<quality>.<format>', endpoint='converter_test')
		], converters=converters)
	
	def dispatch_request(self, request):
		adapter = self.url_map.bind_to_environ(request.environ)
		try:
			endpoint, values = adapter.match()
			return getattr(self, 'on_' + endpoint)(request, **values)
		except NotFound, e:
			return self.error_404(e.description)
		except HTTPException, e:
			return e

	def on_get_img_metadata(self, request, id, extension='xml'):
		# TODO: all of these MIMEs should be constants, and probably support application/* as well
		mime = None
		resp_code = None
		if extension == 'xml': 
			mime = 'text/xml'
		elif extension == 'json': 
			mime = 'text/json'
		else: # support conneg as well.
			mime = request.accept_mimetypes.best_match(['text/json', 'text/xml'])
			if mime == 'text/json': 
				extension = 'json'
			else: 
				mime = 'xml'
			
		img_path = self._resolve_identifier(id)
		
		# TODO: check for a file to return from the cache first, only if not do
		# we make a new one.
		
		if not os.path.exists(img_path):
			raise NotFound('"' + id + '" does not resolve to an image.')
		
		# check the cache
		cache_dir = os.path.join(self.CACHE_ROOT, id)
		if not os.path.exists(cache_dir):
			os.makedirs(cache_dir, 0755)
			logr.debug('made ' + cache_dir)
		cache_path = os.path.join(cache_dir, 'info.') + extension
		
		if os.path.exists(cache_path):
			resp = file(cache_path)
		else:
			info = ImgInfo.fromJP2(img_path)
			info.id = id
			if mime == 'text/xml':
				resp = info.toXML()
			else:
				resp = info.toJSON()
			
			# we could fork this off...
			f = open(cache_path, 'w')
			f.write(resp)
			f.close()
			logr.info('Created: ' + cache_path)
			
		return Response(resp, mimetype=mime,)
	
	# Do we want: http://docs.python.org/library/queue.html ?


	def on_converter_test(self, request, ident, region, size, rotation, quality, format):
		"""
		Helper for our testConverters unit test. 
		"""
		r = {}
		r['region'] = region
		r['size'] = size
		r['rotation'] = rotation
		return Response(str(r))

	def err_to_xml(self, param, text):
		r = '<?xml version="1.0" encoding="UTF-8" ?>\n'
		r = r + '<error xmlns="http://library.stanford.edu/iiif/image-api/ns/">\n'
		r = r + '  <parameter>' + param + '</parameter>\n'
		r = r + '  <text>' + text + '</text>\n'
		r = r + '</error>\n'
		return r

	def on_get_img(self, request, ident, region, size, rotation, quality, format):
		# TODO: 
		"""
		Get an image based on the values or dictionaries returned by our 
		converters.
		"""
		# This method should always return a Response.
		
		#TODO: ulitmately wrap this is try/catches. Most exceptions/messages
		# will come from raises by the converters, but we may have to add a few
		# more 4xx responses based on image dimensions. 
		
		if format in ('jpg','jpeg'):
			mime = 'image/jpeg'
			ext = 'jpg'
		
		jp2 = self._resolve_identifier(ident)
		# TODO: needs unit test
		if not os.path.exists(jp2):
			
			msg = 'Image specified by this identifier does not exist.'
			logr.error(msg + ': ' + ident)
			r = self.err_to_xml(ident, msg)
			return Response(r,status=404,mimetype='text/xml')
			
		
		# TODO: we probably don't want to read this from the file every time.
		# Runtime cache? db?
		info = ImgInfo.fromJP2(jp2)
		
		out_dir = os.path.join(self.CACHE_ROOT, ident, str(region['value']), str(size['value']), str(rotation))
		# hold off making the above dir until we succee
		out_path = os.path.join(out_dir, quality) + '.' + ext
		
		logr.debug('output path: ' + out_path)
		
		return Response(out_path)


#		# Use a named pipe to give kdu and cjpeg format info.
#		fifopath = os.path.join(self.TMP_DIR, rand_str() + _BMP)
#		mkfifo_cmd = self.MKFIFO + " " + fifopath
#		logr.debug(mkfifo_cmd) 
#		mkfifo_proc = subprocess.Popen(mkfifo_cmd, shell=True)
#		mkfifo_proc.wait()
#		
#		# Build the kdu_expand call - this should be in a function that takes 
#		#the image info plus the region, size, rotation<commit_msg>comments only<commit_after># -*- coding: utf-8 -*-

from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.routing import Map, Rule, BaseConverter
from werkzeug.utils import redirect
from werkzeug.wrappers import Request, Response
from werkzeug.wsgi import SharedDataMiddleware
import ConfigParser
import logging
import logging.config
import os
import struct
import urlparse
import werkzeug

# Private Static Constants 
DOT_BMP = ".bmp"
DOT_JPG = ".jpg"
DOT_JPGS = ('.jpg', 'jpeg')
DOT_JP2 = ".jp2"
DOT_JSON = ".json"
DOT_XML = ".xml"

_LIB = os.path.join(os.path.dirname(__file__), 'lib')
_BIN = os.path.join(os.path.dirname(__file__), 'bin')
_ETC = os.path.join(os.path.dirname(__file__), 'etc')
_ENV = {"LD_LIBRARY_PATH":_LIB, "PATH":_LIB + ":$PATH"}

INFO_CACHE={}

conf_file = os.path.join(_ETC, 'patokah.conf')
logging.config.fileConfig(conf_file)
logr = logging.getLogger('patokah')
logr.info("Logging initialized")


def create_app():
	app = Patokah()
	return app

# Note when we start to stream big files from the filesystem, see:
# http://stackoverflow.com/questions/5166129/how-do-i-stream-a-file-using-werkzeug

# TODO: do we need a format converter? Would like to support png as well.

class Patokah(object):
	def __init__(self):
		
		# Configuration - Everything else
		_conf = ConfigParser.RawConfigParser()
		_conf.read(conf_file)

		self.CJPEG = _conf.get('utilities', 'cjpeg')
		self.MKFIFO = _conf.get('utilities', 'mkfifo')
		self.RM = _conf.get('utilities', 'rm')
		
		self.TMP_DIR = _conf.get('directories', 'tmp')
		self.CACHE_ROOT = _conf.get('directories', 'cache_root')
		self.SRC_IMAGES_ROOT = _conf.get('directories', 'src_img_root')
		
		for d in (self.TMP_DIR, self.CACHE_ROOT):
			if not os.path.exists(d):
				os.makedirs(d, 0755)
				logr.info("Created " + d)

		converters = {
				'region' : RegionConverter,
				'size' : SizeConverter,
				'rotation' : RotationConverter
			}
		self.url_map = Map([
#			Rule('/', endpoint='???'),
			Rule('/<path:id>/info.<extension>', endpoint='get_img_metadata',),
			Rule('/<path:ident>/<region:region>/<size:size>/<rotation:rotation>/<quality>.<format>', endpoint='get_img'),
			Rule('/ctests/<path:ident>/<region:region>/<size:size>/<rotation:rotation>/<quality>.<format>', endpoint='converter_test')
		], converters=converters)
	
	def dispatch_request(self, request):
		adapter = self.url_map.bind_to_environ(request.environ)
		try:
			endpoint, values = adapter.match()
			return getattr(self, 'on_' + endpoint)(request, **values)
		except NotFound, e:
			return self.error_404(e.description)
		except HTTPException, e:
			return e

	def on_get_img_metadata(self, request, id, extension='xml'):
		# TODO: all of these MIMEs should be constants, and probably support application/* as well
		mime = None
		resp_code = None
		if extension == 'xml': 	mime = 'text/xml'
		elif extension == 'json': mime = 'text/json'
		else: # support conneg as well.
			mime = request.accept_mimetypes.best_match(['text/json', 'text/xml'])
			if mime == 'text/json': 
				extension = 'json'
			else: 
				mime = 'xml'
			
		img_path = self._resolve_identifier(id)
		
		# TODO: check for a file to return from the cache first, only if not do
		# we make a new one.
		
		if not os.path.exists(img_path):
			raise NotFound('"' + id + '" does not resolve to an image.')
		
		# check the cache
		cache_dir = os.path.join(self.CACHE_ROOT, id)
		if not os.path.exists(cache_dir):
			os.makedirs(cache_dir, 0755)
			logr.debug('made ' + cache_dir)
		cache_path = os.path.join(cache_dir, 'info.') + extension
		
		if os.path.exists(cache_path):
			resp = file(cache_path)
		else:
			info = ImgInfo.fromJP2(img_path)
			info.id = id
			if mime == 'text/xml':
				resp = info.toXML()
			else:
				resp = info.toJSON()
			
			# we could fork this off... http://www.doughellmann.com/PyMOTW/threading/
			f = open(cache_path, 'w')
			f.write(resp)
			f.close()
			logr.info('Created: ' + cache_path)
			
		return Response(resp, mimetype=mime,)
	
	# Do we want: http://docs.python.org/library/queue.html ?


	def on_converter_test(self, request, ident, region, size, rotation, quality, format):
		"""
		Helper for our testConverters unit test. 
		"""
		r = {}
		r['region'] = region
		r['size'] = size
		r['rotation'] = rotation
		return Response(str(r))

	def err_to_xml(self, param, text):
		r = '<?xml version="1.0" encoding="UTF-8" ?>\n'
		r = r + '<error xmlns="http://library.stanford.edu/iiif/image-api/ns/">\n'
		r = r + '  <parameter>' + param + '</parameter>\n'
		r = r + '  <text>' + text + '</text>\n'
		r = r + '</error>\n'
		return r

	def on_get_img(self, request, ident, region, size, rotation, quality, format):
		# TODO: 
		"""
		Get an image based on the values or dictionaries returned by our 
		converters.
		"""
		# This method should always return a Response.
		
		#TODO: ulitmately wrap this is try/catches. Most exceptions/messages
		# will come from raises by the converters, but we may have to add a few
		# more 4xx responses based on image dimensions. 
		
		if format in ('jpg','jpeg'):
			mime = 'image/jpeg'
			ext = 'jpg'
		
		jp2 = self._resolve_identifier(ident)
		# TODO: needs unit test
		if not os.path.exists(jp2):
			
			msg = 'Image specified by this identifier does not exist.'
			logr.error(msg + ': ' + ident)
			r = self.err_to_xml(ident, msg)
			return Response(r,status=404,mimetype='text/xml')
			
		
		# TODO: we probably don't want to read this from the file every time.
		# Runtime cache? db? in memory dicts and Shelve/pickle are not thread 
		# safe for writing.
		# ZODB? : http://www.zodb.org/
		info = ImgInfo.fromJP2(jp2)
		
		out_dir = os.path.join(self.CACHE_ROOT, ident, str(region['value']), str(size['value']), str(rotation))
		# hold off making the above dir until we succee
		out_path = os.path.join(out_dir, quality) + '.' + ext
		
		logr.debug('output path: ' + out_path)
		
		return Response(out_path)


#		# Use a named pipe to give kdu and cjpeg format info.
#		fifopath = os.path.join(self.TMP_DIR, rand_str() + _BMP)
#		mkfifo_cmd = self.MKFIFO + " " + fifopath
#		logr.debug(mkfifo_cmd) 
#		mkfifo_proc = subprocess.Popen(mkfifo_cmd, shell=True)
#		mkfifo_proc.wait()
#		
#		# Build the kdu_expand call - this should be in a function that takes 
#		#the image info plus the region, size, rotation
{'fraction_non_alphanumeric': 0.1445966514459665, 'fraction_numerical': 0.005287190579187695, 'mean_word_length': 3.7849750862399385, 'pattern_counts': {'":': 6, '<': 45, '<?xml version=': 2, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 24}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4383587', 'n_tokens_mistral': 4944, 'n_tokens_neox': 4417, 'n_words': 1133}