<commit_before>import logging

from os.path import basename, getsize
from os import unlink, listdir, sep, stat
import time
from subprocess import Popen, PIPE
from tempfile import NamedTemporaryFile, gettempdir
import re
import simplejson

from routes.util import url_for
from pylons.controllers import WSGIController
from pylons import config, request, response, session

def addRoutes(map, baseUrl="print/", controller="printer"):
    """
    Add the pylons routes for the print module to the given map
    """
    map.connect(baseUrl + "info.json", controller = controller,
                action = 'info', conditions = dict(method = ['GET']))
    map.connect(baseUrl + "print.pdf", controller = controller,
                action = 'doPrint', conditions  =  dict(method = ['GET']))
    map.connect(baseUrl + "create.json", controller = controller,
                action = 'create', conditions = dict(method = ['POST']))
    map.connect(baseUrl + ":id.pdf", controller = controller,
                action = 'get', conditions = dict(method = ['GET']))

log = logging.getLogger(__name__)

class PrinterController(WSGIController):
    TEMP_FILE_PREFIX = "mfPrintTempFile"
    TEMP_FILE_SUFFIX = ".pdf"
    TEMP_FILE_PURGE_SECONDS = 60

    def __init__(self):
        WSGIController.__init__(self)
        self._setupConfig()

    def info(self):
        """
        To get (in JSON) the information about the available formats and CO.
        """
        cmd = ['java', '-cp', self.jarPath, 'org.mapfish.print.ShellMapPrinter',
               '--config=' + self.configPath, '--clientConfig', '--verbose=0']
        exe = Popen(cmd, stdout = PIPE, stderr = PIPE)
        result = exe.stdout.read()
        error = exe.stderr.read()
        if len(error)>0:
            log.error(error)
        ret = exe.wait()
        if ret == 0:
            self.start_response('200 OK', [
                    ('Content-Type','application/json; charset=utf-8')])
            result = self._addURLs(result)
            if request.params.has_key('var'):
                return 'var ' + request.params['var'].encode('utf8') + '=' + result + ';'
            else:
                return result
        else:
            self.start_response('500 Java error '+ret, [
                    ('Content-Type','text/plain; charset=utf-8')])
            return "ERROR\n\n" + error

    def doPrint(self):
        """
        All in one method: creates and returns the PDF to the client.
        """
        cmd = ['java', '-cp', self.jarPath, 'org.mapfish.print.ShellMapPrinter',
             '--config=' + self.configPath, '--verbose=0']
        exe = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
        spec = request.params['spec'].encode('utf8')
        exe.stdin.write(spec)
        exe.stdin.close()
        result = exe.stdout.read()
        error = exe.stderr.read()
        if len(error)>0:
            log.error(error)
        ret = exe.wait()
        if ret == 0:
            self.start_response('200 OK', [
                    ('Content-Type','application/x-pdf')])
            return result
        else:
            self.start_response('500 Java error', [
                    ('Content-Type','text/plain; charset=utf-8')
            ])
            return "ERROR(" + str(ret) + ")\n\nspec=" + spec + "\n\n" + error

    def create(self):
        """
        Create the PDF and returns to the client (in JSON) the URL to get the
        PDF.
        """
        self._purgeOldFiles()
        pdfFile = NamedTemporaryFile("w+b", -1, self.TEMP_FILE_SUFFIX, self.TEMP_FILE_PREFIX)
        pdfFilename = pdfFile.name
        pdfFile.close()
        cmd = ['java',
               '-cp', self.jarPath,
               'org.mapfish.print.ShellMapPrinter',
               '--config=' + self.configPath,
               '--verbose=0',
               '--output=' + pdfFilename]
        exe = Popen(cmd, stdin = PIPE, stderr = PIPE)
        spec = request.environ['wsgi.input'].read()
        exe.stdin.write(spec)
        exe.stdin.close()
        error = exe.stderr.read()
        if len(error)>0:
            log.error(error)
        ret = exe.wait()
        if ret == 0:
            curId = basename(pdfFilename)[len(self.TEMP_FILE_PREFIX):-len(self.TEMP_FILE_SUFFIX)]

            self.start_response('200 OK', [
                    ('Content-Type','application/json; charset=utf-8')])
            getURL = self._urlForAction("create", "get", id = curId)
            return simplejson.dumps({
                'getURL': getURL
            })
        else:
            unlink(pdfFilename)
            self.start_response('500 Java error', [
                    ('Content-Type','text/plain; charset=utf-8')
            ])
            return "ERROR(" + str(ret) + ")\n\nspec=" + spec + "\n\n" + error
        return request

    def get(self, id):
        """
        To get the PDF created previously.
        """
        name = gettempdir() + sep + self.TEMP_FILE_PREFIX + id + self.TEMP_FILE_SUFFIX

        response.headers['Content-Type'] = 'application/x-pdf'
        response.headers['Content-Length'] = getsize(name)
        response.content = FileIterable(name)
        return

    def _setupConfig(self):
        self.jarPath = config['print.jar']
        self.configPath = config['print.config']

    def _urlForAction(self, fromAction, actionName, id = None):
        """
        We cannot trust the URL from the request to get the hostname (proxy).
        This method is returning the base URL for accessing the different
        actions of this controller.
        """
        actionUrl = url_for(action = actionName, id = id)
        if request.params.has_key('url'):
            fullUrl = request.params['url'].encode('utf8')
            myUrl = url_for(action = fromAction)
            if fullUrl == myUrl[1:]:  # support for very short relative URLs
                return actionUrl[1:]
            if fullUrl.endswith(myUrl):
                return fullUrl[0:-len(myUrl)] + actionUrl
            log.warn("Cannot guess the base URL for " + fullUrl + " (action=" + myUrl + ")")
        return request.scheme + "://" + request.host + actionUrl

    def _addURLs(self, json):
        expr = re.compile('}$')
        printURL = simplejson.dumps(self._urlForAction("info", "doPrint"))
        createURL = simplejson.dumps(self._urlForAction("info", "create"))
        return expr.sub(',"printURL":' + printURL + ',' +
                        '"createURL":' + createURL + '}', json)

    def _purgeOldFiles(self):
        """
        Delete temp files that are more than TEMP_FILE_PURGE_SECONDS seconds old
        """
        files=listdir(gettempdir())
        for file in files:
            if file.startswith(self.TEMP_FILE_PREFIX) and file.endswith(self.TEMP_FILE_SUFFIX):
                fullname = gettempdir() + sep + file
                age = time.time() - stat(fullname).st_mtime
                if age > self.TEMP_FILE_PURGE_SECONDS:
                    log.info("deleting leftover file :" + fullname + " (age=" + str(age) + "s)")
                    unlink(fullname)

# taken here: http://pythonpaste.org/webob/file-example.html
class FileIterable(object):
     def __init__(self, filename, start = None, stop = None):
         self.filename = filename
         self.start = start
         self.stop = stop
     def __iter__(self):
         return FileIterator(self.filename, self.start, self.stop)
     def app_iter_range(self, start, stop):
         return self.__class__(self.filename, start, stop)

class FileIterator(object):
     chunk_size = 4096
     def __init__(self, filename, start, stop):
         self.filename = filename
         self.fileobj = open(self.filename, 'rb')
         if start:
             self.fileobj.seek(start)
         if stop is not None:
             self.length = stop - start
         else:
             self.length = None
     def __iter__(self):
         return self
     def next(self):
         if self.length is not None and self.length <= 0:
             unlink(self.filename)
             raise StopIteration
         chunk = self.fileobj.read(self.chunk_size)
         if not chunk:
             unlink(self.filename)
             raise StopIteration
         if self.length is not None:
             self.length -= len(chunk)
             if self.length < 0:
                 # Chop off the extra:
                 chunk = chunk[:self.length]
         return chunk
<commit_msg>add copyright and license header<commit_after>#
# Copyright (C) 2007-2008  Camptocamp
#
# This file is part of MapFish
#
# MapFish is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# MapFish is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with MapFish.  If not, see <http://www.gnu.org/licenses/>.
#

import logging

from os.path import basename, getsize
from os import unlink, listdir, sep, stat
import time
from subprocess import Popen, PIPE
from tempfile import NamedTemporaryFile, gettempdir
import re
import simplejson

from routes.util import url_for
from pylons.controllers import WSGIController
from pylons import config, request, response, session

def addRoutes(map, baseUrl="print/", controller="printer"):
    """
    Add the pylons routes for the print module to the given map
    """
    map.connect(baseUrl + "info.json", controller = controller,
                action = 'info', conditions = dict(method = ['GET']))
    map.connect(baseUrl + "print.pdf", controller = controller,
                action = 'doPrint', conditions  =  dict(method = ['GET']))
    map.connect(baseUrl + "create.json", controller = controller,
                action = 'create', conditions = dict(method = ['POST']))
    map.connect(baseUrl + ":id.pdf", controller = controller,
                action = 'get', conditions = dict(method = ['GET']))

log = logging.getLogger(__name__)

class PrinterController(WSGIController):
    TEMP_FILE_PREFIX = "mfPrintTempFile"
    TEMP_FILE_SUFFIX = ".pdf"
    TEMP_FILE_PURGE_SECONDS = 60

    def __init__(self):
        WSGIController.__init__(self)
        self._setupConfig()

    def info(self):
        """
        To get (in JSON) the information about the available formats and CO.
        """
        cmd = ['java', '-cp', self.jarPath, 'org.mapfish.print.ShellMapPrinter',
               '--config=' + self.configPath, '--clientConfig', '--verbose=0']
        exe = Popen(cmd, stdout = PIPE, stderr = PIPE)
        result = exe.stdout.read()
        error = exe.stderr.read()
        if len(error)>0:
            log.error(error)
        ret = exe.wait()
        if ret == 0:
            self.start_response('200 OK', [
                    ('Content-Type','application/json; charset=utf-8')])
            result = self._addURLs(result)
            if request.params.has_key('var'):
                return 'var ' + request.params['var'].encode('utf8') + '=' + result + ';'
            else:
                return result
        else:
            self.start_response('500 Java error '+ret, [
                    ('Content-Type','text/plain; charset=utf-8')])
            return "ERROR\n\n" + error

    def doPrint(self):
        """
        All in one method: creates and returns the PDF to the client.
        """
        cmd = ['java', '-cp', self.jarPath, 'org.mapfish.print.ShellMapPrinter',
             '--config=' + self.configPath, '--verbose=0']
        exe = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
        spec = request.params['spec'].encode('utf8')
        exe.stdin.write(spec)
        exe.stdin.close()
        result = exe.stdout.read()
        error = exe.stderr.read()
        if len(error)>0:
            log.error(error)
        ret = exe.wait()
        if ret == 0:
            self.start_response('200 OK', [
                    ('Content-Type','application/x-pdf')])
            return result
        else:
            self.start_response('500 Java error', [
                    ('Content-Type','text/plain; charset=utf-8')
            ])
            return "ERROR(" + str(ret) + ")\n\nspec=" + spec + "\n\n" + error

    def create(self):
        """
        Create the PDF and returns to the client (in JSON) the URL to get the
        PDF.
        """
        self._purgeOldFiles()
        pdfFile = NamedTemporaryFile("w+b", -1, self.TEMP_FILE_SUFFIX, self.TEMP_FILE_PREFIX)
        pdfFilename = pdfFile.name
        pdfFile.close()
        cmd = ['java',
               '-cp', self.jarPath,
               'org.mapfish.print.ShellMapPrinter',
               '--config=' + self.configPath,
               '--verbose=0',
               '--output=' + pdfFilename]
        exe = Popen(cmd, stdin = PIPE, stderr = PIPE)
        spec = request.environ['wsgi.input'].read()
        exe.stdin.write(spec)
        exe.stdin.close()
        error = exe.stderr.read()
        if len(error)>0:
            log.error(error)
        ret = exe.wait()
        if ret == 0:
            curId = basename(pdfFilename)[len(self.TEMP_FILE_PREFIX):-len(self.TEMP_FILE_SUFFIX)]

            self.start_response('200 OK', [
                    ('Content-Type','application/json; charset=utf-8')])
            getURL = self._urlForAction("create", "get", id = curId)
            return simplejson.dumps({
                'getURL': getURL
            })
        else:
            unlink(pdfFilename)
            self.start_response('500 Java error', [
                    ('Content-Type','text/plain; charset=utf-8')
            ])
            return "ERROR(" + str(ret) + ")\n\nspec=" + spec + "\n\n" + error
        return request

    def get(self, id):
        """
        To get the PDF created previously.
        """
        name = gettempdir() + sep + self.TEMP_FILE_PREFIX + id + self.TEMP_FILE_SUFFIX

        response.headers['Content-Type'] = 'application/x-pdf'
        response.headers['Content-Length'] = getsize(name)
        response.content = FileIterable(name)
        return

    def _setupConfig(self):
        self.jarPath = config['print.jar']
        self.configPath = config['print.config']

    def _urlForAction(self, fromAction, actionName, id = None):
        """
        We cannot trust the URL from the request to get the hostname (proxy).
        This method is returning the base URL for accessing the different
        actions of this controller.
        """
        actionUrl = url_for(action = actionName, id = id)
        if request.params.has_key('url'):
            fullUrl = request.params['url'].encode('utf8')
            myUrl = url_for(action = fromAction)
            if fullUrl == myUrl[1:]:  # support for very short relative URLs
                return actionUrl[1:]
            if fullUrl.endswith(myUrl):
                return fullUrl[0:-len(myUrl)] + actionUrl
            log.warn("Cannot guess the base URL for " + fullUrl + " (action=" + myUrl + ")")
        return request.scheme + "://" + request.host + actionUrl

    def _addURLs(self, json):
        expr = re.compile('}$')
        printURL = simplejson.dumps(self._urlForAction("info", "doPrint"))
        createURL = simplejson.dumps(self._urlForAction("info", "create"))
        return expr.sub(',"printURL":' + printURL + ',' +
                        '"createURL":' + createURL + '}', json)

    def _purgeOldFiles(self):
        """
        Delete temp files that are more than TEMP_FILE_PURGE_SECONDS seconds old
        """
        files=listdir(gettempdir())
        for file in files:
            if file.startswith(self.TEMP_FILE_PREFIX) and file.endswith(self.TEMP_FILE_SUFFIX):
                fullname = gettempdir() + sep + file
                age = time.time() - stat(fullname).st_mtime
                if age > self.TEMP_FILE_PURGE_SECONDS:
                    log.info("deleting leftover file :" + fullname + " (age=" + str(age) + "s)")
                    unlink(fullname)

# taken here: http://pythonpaste.org/webob/file-example.html
class FileIterable(object):
     def __init__(self, filename, start = None, stop = None):
         self.filename = filename
         self.start = start
         self.stop = stop
     def __iter__(self):
         return FileIterator(self.filename, self.start, self.stop)
     def app_iter_range(self, start, stop):
         return self.__class__(self.filename, start, stop)

class FileIterator(object):
     chunk_size = 4096
     def __init__(self, filename, start, stop):
         self.filename = filename
         self.fileobj = open(self.filename, 'rb')
         if start:
             self.fileobj.seek(start)
         if stop is not None:
             self.length = stop - start
         else:
             self.length = None
     def __iter__(self):
         return self
     def next(self):
         if self.length is not None and self.length <= 0:
             unlink(self.filename)
             raise StopIteration
         chunk = self.fileobj.read(self.chunk_size)
         if not chunk:
             unlink(self.filename)
             raise StopIteration
         if self.length is not None:
             self.length -= len(chunk)
             if self.length < 0:
                 # Chop off the extra:
                 chunk = chunk[:self.length]
         return chunk
<|endoftext|>


<commit_before>import shutil
import time
import sys

from os import path, mkdir

from PIL import Image

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *

class Projector(object):
    DIRECTIONS = {
        'top': (180, 180, 0),
        'bottom': (0, 180, 0),
        'front': (90, 180, 0),
        'left': (90, 180, 90),
        'back': (90, 180, 180),
        'right': (90, 180, 270),
    }

    def __init__(self, size, dest, verbose=False, overwrite=False):
        super(Projector, self).__init__()

        self.dest = dest
        self.x, self.y, self.z = 0, 0, 0
        self.w = self.h = size
        self.texture = None
        self.verbose = True
        self.overwrite = overwrite

        self.init_gl()

    def log(self, msg):
        if self.verbose:
            sys.stderr.write(str(msg) + '\n')

    def load_texture(self, f):
        start = time.time()
        im = Image.open(f)
        # im = im.resize((4096, 2048), Image.ANTIALIAS)

        w, h = im.size
        exif = im._getexif()
        buf = im.convert("RGB").tostring("raw", "RGB")

        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

        glTexImage2D(
            GL_TEXTURE_2D, 0, GL_RGB, w, h, 0,
            GL_RGB, GL_UNSIGNED_BYTE, buf
        )

        elapsed = time.time() - start
        self.log('[+] took %fs to load texture' % elapsed)

        self.texture = texture

    def run(self, src):
        if path.isdir(src):
            self.log('[+] target is a directory: ')
            self.log(' %s' % src)
            filelist = [path.join(src, filename) for filename in os.listdir(src)
                if not filename.startswith('.')]

            [self.run(filename) for filename in filelist]

        elif path.isfile(src):
            self.log('[+] processing image: %s' % src)
            self.render(src)
            self.log('[+] done.')

        else:
            self.log('[!] invalid file: %s' % src)

    def render(self, filename):
        prefix, ext = path.splitext(path.basename(filename))

        if not path.isdir(self.dest):
            mkdir(self.dest)

        subdir = path.join(self.dest, prefix)
        if path.isdir(subdir):
            if self.overwrite:
                self.log('[!] overwriting %s' % subdir)
                shutil.rmtree(subdir)
            else:
                self.log('[!] %s exists, skipping' % subdir)
                return

        mkdir(subdir)

        self.load_texture(filename)
        start = time.time()

        directions = Projector.DIRECTIONS
        for name, vec in directions.iteritems():
            buf = self.rotate(*vec)
            image = Image.frombytes(mode="RGB", size=(self.w, self.h), data=buf)
            image_name = path.join(subdir, "%s.jpg" % name)
            image.save(image_name)

        elapsed = time.time() - start
        self.log('[+] took %fs to render 6 images' % elapsed)

    def init_gl(self):

        glutInit()
        glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE)
        glutInitWindowSize(self.w, self.h)
        glutCreateWindow("glskybox renderer")
        glutHideWindow()

        glClearColor(0., 0., 0., 0.)
        glClearDepth(1.)
        glDepthFunc(GL_LESS)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(90, 1., .1, 100.)
        glMatrixMode(GL_MODELVIEW)

    def rotate(self, *args):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

        glPushMatrix()

        glTranslatef(0., 0., 0.)
        glScalef(-1, 1, 1)

        x, y, z = args
        glRotatef(x, 1., 0., 0.)
        glRotatef(y, 0., 1., 0.)
        glRotatef(z, 0., 0., 1.)

        glEnable(GL_TEXTURE_2D)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
        glBindTexture(GL_TEXTURE_2D, self.texture)

        Q = gluNewQuadric()
        gluQuadricNormals(Q, GL_SMOOTH)
        gluQuadricTexture(Q, GL_TRUE)
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP)
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP)
        gluSphere(Q, 2.0, 100, 100)

        glDisable(GL_TEXTURE_2D)

        buf = glReadPixels(0, 0, self.w, self.h, GL_RGB, GL_UNSIGNED_BYTE)

        glPopMatrix()

        return buf
<commit_msg>upgrade to Pillow 3.0<commit_after>import shutil
import time
import sys

from os import path, mkdir

from PIL import Image

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *

class Projector(object):
    DIRECTIONS = {
        'top': (180, 180, 0),
        'bottom': (0, 180, 0),
        'front': (90, 180, 0),
        'left': (90, 180, 90),
        'back': (90, 180, 180),
        'right': (90, 180, 270),
    }

    def __init__(self, size, dest, verbose=False, overwrite=False):
        super(Projector, self).__init__()

        self.dest = dest
        self.x, self.y, self.z = 0, 0, 0
        self.w = self.h = size
        self.texture = None
        self.verbose = True
        self.overwrite = overwrite

        self.init_gl()

    def log(self, msg):
        if self.verbose:
            sys.stderr.write(str(msg) + '\n')

    def load_texture(self, f):
        start = time.time()
        im = Image.open(f)
        # im = im.resize((4096, 2048), Image.ANTIALIAS)

        w, h = im.size
        exif = im._getexif()
        buf = im.convert("RGB").tobytes("raw", "RGB")

        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

        glTexImage2D(
            GL_TEXTURE_2D, 0, GL_RGB, w, h, 0,
            GL_RGB, GL_UNSIGNED_BYTE, buf
        )

        elapsed = time.time() - start
        self.log('[+] took %fs to load texture' % elapsed)

        self.texture = texture

    def run(self, src):
        if path.isdir(src):
            self.log('[+] target is a directory: ')
            self.log(' %s' % src)
            filelist = [path.join(src, filename) for filename in os.listdir(src)
                if not filename.startswith('.')]

            [self.run(filename) for filename in filelist]

        elif path.isfile(src):
            self.log('[+] processing image: %s' % src)
            self.render(src)
            self.log('[+] done.')

        else:
            self.log('[!] invalid file: %s' % src)

    def render(self, filename):
        prefix, ext = path.splitext(path.basename(filename))

        if not path.isdir(self.dest):
            mkdir(self.dest)

        subdir = path.join(self.dest, prefix)
        if path.isdir(subdir):
            if self.overwrite:
                self.log('[!] overwriting %s' % subdir)
                shutil.rmtree(subdir)
            else:
                self.log('[!] %s exists, skipping' % subdir)
                return

        mkdir(subdir)

        self.load_texture(filename)
        start = time.time()

        directions = Projector.DIRECTIONS
        for name, vec in directions.iteritems():
            buf = self.rotate(*vec)
            image = Image.frombytes(mode="RGB", size=(self.w, self.h), data=buf)
            image_name = path.join(subdir, "%s.jpg" % name)
            image.save(image_name)

        elapsed = time.time() - start
        self.log('[+] took %fs to render 6 images' % elapsed)

    def init_gl(self):

        glutInit()
        glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE)
        glutInitWindowSize(self.w, self.h)
        glutCreateWindow("glskybox renderer")
        glutHideWindow()

        glClearColor(0., 0., 0., 0.)
        glClearDepth(1.)
        glDepthFunc(GL_LESS)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(90, 1., .1, 100.)
        glMatrixMode(GL_MODELVIEW)

    def rotate(self, *args):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

        glPushMatrix()

        glTranslatef(0., 0., 0.)
        glScalef(-1, 1, 1)

        x, y, z = args
        glRotatef(x, 1., 0., 0.)
        glRotatef(y, 0., 1., 0.)
        glRotatef(z, 0., 0., 1.)

        glEnable(GL_TEXTURE_2D)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
        glBindTexture(GL_TEXTURE_2D, self.texture)

        Q = gluNewQuadric()
        gluQuadricNormals(Q, GL_SMOOTH)
        gluQuadricTexture(Q, GL_TRUE)
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP)
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP)
        gluSphere(Q, 2.0, 100, 100)

        glDisable(GL_TEXTURE_2D)

        buf = glReadPixels(0, 0, self.w, self.h, GL_RGB, GL_UNSIGNED_BYTE)

        glPopMatrix()

        return buf
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
Created on Wed Mar 26 13:44:56 2014

@author: stuart
"""
from __future__ import print_function, absolute_import

import os
import time
import urlparse
import warnings

import requests
import astropy.time

from sunpy import config
from sunpy.time import parse_time, TimeRange
from sunpy.net.download import Downloader
from sunpy.net.vso.vso import Results
from sunpy.net.attr import and_
from sunpy.net.jsoc.attrs import walker
__all__ = ['JSOCClient']

JSOC_URL = 'http://jsoc.stanford.edu/cgi-bin/ajax/jsoc_fetch'
BASE_DL_URL = 'http://jsoc.stanford.edu'

class JSOCClient(object):
    """
    This is a Client to the JSOC Data Export service.

    This is not warm and fluffy like the VSO, this is hardcore crazy. It is
    more efficient for large scale SDO queries than the VSO, however harder to
    use.

    Notes
    -----
    This Client mocks input to this site: http://jsoc.stanford.edu/ajax/exportdata.html
    Therefore that is a good resource if things are mis-behaving.
    The full list of 'series' is availible through this site: http://jsoc.stanford.edu/

    You can build more complex queries by specifiying parameters to POST to JSOC via keyword
    arguments. You can generate these kwargs using the Export Data page at JSOC.

    Examples
    --------
    Query JSOC for some HMI data at 45 second cadence

    >>> from sunpy.net import jsoc
    >>> client = jsoc.JSOCClient()
    >>> IDs = client.jsoc_query('2012/1/1T00:00:00', '2012/1/1T00:00:45', 'hmi.m_45s')

    The returned `IDs` is a list of JSOC request identifiers, you can check the status of
    a request thus:

    >>> status = client.check_status(IDs)

    Once the request has been staged (Status 1) you can download the data:

    >>> res = client.get(IDs)

    This returns a Results instance which can be used to watch the progress
    of the download.
    """

    def jsoc_query(self, *query, **kwargs):
        """
        Build a JSOC query and submit it to JSOC for processing.

        Parameters
        ----------
        start_time: datetime, astropy.time or string
            The time in UTC (or astropy.time object) specifying the start time.

        end_time: datetime, astropy.time or string
            The time in UTC (or astropy.time object) specifying the end time.

        series: string
            A string representing the JSOC data series to download e.g. 'hmi.M_45s'

        notify: string
            An email address to get a notification to when JSOC has staged your request

        protocol: string
            The type of download to request one of ("FITS", "JPEG", "MPG", "MP4", or "as-is").
            Only FITS is supported, the others will require extra keywords.

        compression: string
            'rice' or None, download FITS files with RICE compression.

        kwargs: dict
            Extra keywords to put into the POST payload to JSOC.

        Returns
        -------
        requestIDs: list
            A list of the requestIDs generated from your query
        """

        # A little (hidden) debug feature
        return_resp = kwargs.pop('return_resp', False)
        return_response = []
        return_reqid = []
        query = and_(*query)
        for block in walker.create(query):
	    iargs = kwargs.copy()
	    iargs.update(block)

	    # Do a multi-request for each query block
	    responses = self._multi_request(**iargs)
            for i, response in enumerate(responses):
                #TODD: catch non 200 return
                if response.json()['status'] != 2:
                    warnings.warn(
                    Warning("Query {0} retuned status {1} with error {2}".format(i,
                                                     response.json()['status'],
                                                     response.json()['error'])))
                    responses.pop(i)
            #Extract the IDs from the JSON
	    requestIDs = [response.json()['requestid'] for response in responses]
	    return_reqid.extend(requestIDs)
	    return_response.extend(responses)

	if return_resp:
            return return_response

        return return_reqid

    def check_request(self, requestIDs):
        """
        Check the status of a request and print out a messgae about it

        Parameters
        ----------
        requestIDs: list or string
            A list of requestIDs to check

        Returns
        -------
        status: list
            A list of status' that were returned by JSOC
        """
        # Convert IDs to a list if not already
        if not astropy.utils.misc.isiterable(requestIDs) or isinstance(requestIDs, basestring):
            requestIDs = [requestIDs]

        allstatus = []
        for request_id in requestIDs:
            u = self._request_status(request_id)
            status = int(u.json()['status'])

            if status == 0: #Data ready to download
                print("Request {0} was exported at {1} and is ready to download.".format(u.json()['requestid'],
                                                                                u.json()['exptime']))
            elif status == 1:
                print("Request {0} was submitted {1} seconds ago, it is not ready to download.".format(
                                                             u.json()['requestid'], u.json()['wait']))
            else:
                print("Request returned status: {0} with error: {1}".format(
                                    u.json()['status'], u.json()['error']))

            allstatus.append(status)

        return allstatus

    def wait_get(self, requestIDs, path=None, overwrite=False, progress=True,
            max_conn=5, sleep=10):
        """
        Same as get() excepts it will wait until the download has been staged.

        Parameters
        ----------
        requestIDs: list or string
            One or many requestID strings

        path: string
            Path to save data to, defaults to SunPy download dir

        overwrite: bool
            Replace files with the same name if True

        progress: bool
            Print progress info to terminal

        max_conns: int
            Maximum number of download connections.

        downloader: sunpy.download.Downloder instance
            A Custom downloader to use

        sleep: int
            The number of seconds to wait between calls to JSOC to check the status
            of the request.

        Returns
        -------
        downloader: a sunpy.net.download.Downloader instance
            A Downloader instance
        """
        # Convert IDs to a list if not already
        if not astropy.utils.misc.isiterable(requestIDs) or isinstance(requestIDs, basestring):
            requestIDs = [requestIDs]

        r = Results(lambda x: None)

        while requestIDs:
            for i, request_id in enumerate(requestIDs):
                u = self._request_status(request_id)

                if progress:
                    self.check_request(request_id)

                if u.status_code == 200 and u.json()['status'] == '0':
                    rID = requestIDs.pop(i)
                    r = self.get(rID, path=path, overwrite=overwrite,
                             progress=progress, results=r)

                else:
                    time.sleep(sleep)

        return r

    def get(self, requestIDs, path=None, overwrite=False, progress=True,
            max_conn=5, downloader=None, results=None):
        """
        Query JSOC to see if request_id is ready for download.

        If the request is ready for download download it.

        Parameters
        ----------
        requestIDs: list or string
            One or many requestID strings

        path: string
            Path to save data to, defaults to SunPy download dir

        overwrite: bool
            Replace files with the same name if True

        progress: bool
            Print progress info to terminal

        max_conns: int
            Maximum number of download connections.

        downloader: sunpy.download.Downloder instance
            A Custom downloader to use

        results: Results instance
            A Results manager to use.

        Returns
        -------
        res: Results
            A Results instance or None if no URLs to download
        """

        # Convert IDs to a list if not already
        if not astropy.utils.misc.isiterable(requestIDs) or isinstance(requestIDs, basestring):
            requestIDs = [requestIDs]

        if path is None:
            path = config.get('downloads','download_dir')

        if downloader is None:
            downloader = Downloader(max_conn=max_conn, max_total=max_conn)

        # A Results object tracks the number of downloads requested and the
        # number that have been completed.
        if results is None:
            results = Results(lambda x: None)

        urls = []
        for request_id in requestIDs:
            u = self._request_status(request_id)

            if u.status_code == 200 and u.json()['status'] == '0':
                for ar in u.json()['data']:
                    if overwrite or not os.path.isfile(os.path.join(path, ar['filename'])):
                        urls.append(urlparse.urljoin(BASE_DL_URL + u.json()['dir']+'/', ar['filename']))
                if progress:
                    print("{0} URLs found for Download. Totalling {1}MB".format(len(urls), u.json()['size']))

            else:
                if progress:
                    self.check_request(request_id)

        if urls:
            for url, rcall in list(zip(urls, list(map(lambda x: results.require([x]), urls)))):
                downloader.download(url, callback=rcall, path=path)

        else:
            #Make Results think it has finished.
            results.require([])

        results.poke()
        return results

    def _process_time(self, time):
        """
        Take a UTC time string or datetime instance and generate a astropy.time
        object in TAI frame. Alternatively convert a astropy time object to TAI

        Parameters
        ----------
        time: basestring or datetime or astropy.time
            Input time

        Returns
        -------
        datetime, in TAI
        """
        # Convert from any input (in UTC) to TAI
        if isinstance(time, basestring):
            time = parse_time(time)
        time = astropy.time.Time(time, scale='utc')
        time = astropy.time.Time(time, scale='tai')

        return time.datetime

    def _make_query_payload(self, start_time, end_time, series, notify='',
                          protocol='FITS', compression='rice', **kwargs):
        """
        Build the POST payload for the query parameters
        """

        if protocol.upper() == 'FITS' and compression and compression.lower() == 'rice':
            jprotocol = 'FITS,compress Rice'
        elif protocol.upper() == 'FITS':
            jprotocol = 'FITS, **NONE**'
        else:
            jprotocol = protocol

        payload = {'ds':'{0}[{1}-{2}]'.format(series, start_time.strftime("%Y.%m.%d_%H:%M:%S_TAI"),
                                           end_time.strftime("%Y.%m.%d_%H:%M:%S_TAI")),
                   'format':'json',
                   'method':'url',
                   'notify':notify,
                   'op':'exp_request',
                   'process':'n=0|no_op',
                   'protocol':jprotocol,
                   'requestor':'none',
                   'filenamefmt':'{0}.{{T_REC:A}}.{{CAMERA}}.{{segment}}'.format(series)}

        if kwargs.has_key('wavelength'):
	    if series[0:3] != 'aia':
	        raise TypeError
	    else:
	       if isinstance(kwargs['wavelength'],list):
	           tmp = str(kwargs['wavelength'])
	       else:
	           tmp = '[{0}]'.format(kwargs['wavelength'])
	       payload['ds'] += tmp
            kwargs.pop('wavelength')

        payload.update(kwargs)
        return payload

    def _send_jsoc_request(self, start_time, end_time, series, notify='',
                          protocol='FITS', compression='rice', **kwargs):
        """
        Request that JSOC stages data for download

        This routine puts in a POST request to JSOC
        """

        payload = self._make_query_payload(start_time, end_time, series, notify=notify,
                          protocol=protocol, compression=compression, **kwargs)

        r = requests.post(JSOC_URL, data=payload)

        if r.status_code != 200:
            raise Exception("JSOC POST Request returned code {0}".format(r.status_code))

        return r, r.json()

    def _multi_request(self, **kwargs):
        """
        Make a series of requests to avoid the 100GB limit
        """
        start_time = kwargs.pop('start_time',None)
        end_time = kwargs.pop('end_time',None)
        series = kwargs.pop('series',None)
        if any(x is None for x in (start_time, end_time, series)):
            return []
        start_time = self._process_time(start_time)
        end_time = self._process_time(end_time)
        tr = TimeRange(start_time, end_time)
        returns = []
        response, json_response = self._send_jsoc_request(start_time, end_time, series, **kwargs)

        if json_response['status'] == 3 and json_response['error'] == 'Request exceeds max byte limit of 100000MB':
            returns.append(self._multi_request(tr.start(), tr.center(), series, **kwargs)[0])
            returns.append(self._multi_request(tr.center(), tr.end(), series, **kwargs)[0])
        else:
            returns.append(response)

        return returns

    def _request_status(self, request_id):
        """
        GET the status of a request ID
        """
        payload = {'op':'exp_status', 'requestid':request_id}
        u = requests.get(JSOC_URL, params=payload)

        return u

<commit_msg>Error message added<commit_after># -*- coding: utf-8 -*-
"""
Created on Wed Mar 26 13:44:56 2014

@author: stuart
"""
from __future__ import print_function, absolute_import

import os
import time
import urlparse
import warnings

import requests
import astropy.time

from sunpy import config
from sunpy.time import parse_time, TimeRange
from sunpy.net.download import Downloader
from sunpy.net.vso.vso import Results
from sunpy.net.attr import and_
from sunpy.net.jsoc.attrs import walker
__all__ = ['JSOCClient']

JSOC_URL = 'http://jsoc.stanford.edu/cgi-bin/ajax/jsoc_fetch'
BASE_DL_URL = 'http://jsoc.stanford.edu'

class JSOCClient(object):
    """
    This is a Client to the JSOC Data Export service.

    This is not warm and fluffy like the VSO, this is hardcore crazy. It is
    more efficient for large scale SDO queries than the VSO, however harder to
    use.

    Notes
    -----
    This Client mocks input to this site: http://jsoc.stanford.edu/ajax/exportdata.html
    Therefore that is a good resource if things are mis-behaving.
    The full list of 'series' is availible through this site: http://jsoc.stanford.edu/

    You can build more complex queries by specifiying parameters to POST to JSOC via keyword
    arguments. You can generate these kwargs using the Export Data page at JSOC.

    Examples
    --------
    Query JSOC for some HMI data at 45 second cadence

    >>> from sunpy.net import jsoc
    >>> client = jsoc.JSOCClient()
    >>> IDs = client.jsoc_query('2012/1/1T00:00:00', '2012/1/1T00:00:45', 'hmi.m_45s')

    The returned `IDs` is a list of JSOC request identifiers, you can check the status of
    a request thus:

    >>> status = client.check_status(IDs)

    Once the request has been staged (Status 1) you can download the data:

    >>> res = client.get(IDs)

    This returns a Results instance which can be used to watch the progress
    of the download.
    """

    def jsoc_query(self, *query, **kwargs):
        """
        Build a JSOC query and submit it to JSOC for processing.

        Parameters
        ----------
        start_time: datetime, astropy.time or string
            The time in UTC (or astropy.time object) specifying the start time.

        end_time: datetime, astropy.time or string
            The time in UTC (or astropy.time object) specifying the end time.

        series: string
            A string representing the JSOC data series to download e.g. 'hmi.M_45s'

        notify: string
            An email address to get a notification to when JSOC has staged your request

        protocol: string
            The type of download to request one of ("FITS", "JPEG", "MPG", "MP4", or "as-is").
            Only FITS is supported, the others will require extra keywords.

        compression: string
            'rice' or None, download FITS files with RICE compression.

        kwargs: dict
            Extra keywords to put into the POST payload to JSOC.

        Returns
        -------
        requestIDs: list
            A list of the requestIDs generated from your query
        """

        # A little (hidden) debug feature
        return_resp = kwargs.pop('return_resp', False)
        return_response = []
        return_reqid = []
        query = and_(*query)
        for block in walker.create(query):
	    iargs = kwargs.copy()
	    iargs.update(block)

	    # Do a multi-request for each query block
	    responses = self._multi_request(**iargs)
            for i, response in enumerate(responses):
                #TODD: catch non 200 return
                if response.json()['status'] != 2:
                    warnings.warn(
                    Warning("Query {0} retuned status {1} with error {2}".format(i,
                                                     response.json()['status'],
                                                     response.json()['error'])))
                    responses.pop(i)
            #Extract the IDs from the JSON
	    requestIDs = [response.json()['requestid'] for response in responses]
	    return_reqid.extend(requestIDs)
	    return_response.extend(responses)

	if return_resp:
            return return_response

        return return_reqid

    def check_request(self, requestIDs):
        """
        Check the status of a request and print out a messgae about it

        Parameters
        ----------
        requestIDs: list or string
            A list of requestIDs to check

        Returns
        -------
        status: list
            A list of status' that were returned by JSOC
        """
        # Convert IDs to a list if not already
        if not astropy.utils.misc.isiterable(requestIDs) or isinstance(requestIDs, basestring):
            requestIDs = [requestIDs]

        allstatus = []
        for request_id in requestIDs:
            u = self._request_status(request_id)
            status = int(u.json()['status'])

            if status == 0: #Data ready to download
                print("Request {0} was exported at {1} and is ready to download.".format(u.json()['requestid'],
                                                                                u.json()['exptime']))
            elif status == 1:
                print("Request {0} was submitted {1} seconds ago, it is not ready to download.".format(
                                                             u.json()['requestid'], u.json()['wait']))
            else:
                print("Request returned status: {0} with error: {1}".format(
                                    u.json()['status'], u.json()['error']))

            allstatus.append(status)

        return allstatus

    def wait_get(self, requestIDs, path=None, overwrite=False, progress=True,
            max_conn=5, sleep=10):
        """
        Same as get() excepts it will wait until the download has been staged.

        Parameters
        ----------
        requestIDs: list or string
            One or many requestID strings

        path: string
            Path to save data to, defaults to SunPy download dir

        overwrite: bool
            Replace files with the same name if True

        progress: bool
            Print progress info to terminal

        max_conns: int
            Maximum number of download connections.

        downloader: sunpy.download.Downloder instance
            A Custom downloader to use

        sleep: int
            The number of seconds to wait between calls to JSOC to check the status
            of the request.

        Returns
        -------
        downloader: a sunpy.net.download.Downloader instance
            A Downloader instance
        """
        # Convert IDs to a list if not already
        if not astropy.utils.misc.isiterable(requestIDs) or isinstance(requestIDs, basestring):
            requestIDs = [requestIDs]

        r = Results(lambda x: None)

        while requestIDs:
            for i, request_id in enumerate(requestIDs):
                u = self._request_status(request_id)

                if progress:
                    self.check_request(request_id)

                if u.status_code == 200 and u.json()['status'] == '0':
                    rID = requestIDs.pop(i)
                    r = self.get(rID, path=path, overwrite=overwrite,
                             progress=progress, results=r)

                else:
                    time.sleep(sleep)

        return r

    def get(self, requestIDs, path=None, overwrite=False, progress=True,
            max_conn=5, downloader=None, results=None):
        """
        Query JSOC to see if request_id is ready for download.

        If the request is ready for download download it.

        Parameters
        ----------
        requestIDs: list or string
            One or many requestID strings

        path: string
            Path to save data to, defaults to SunPy download dir

        overwrite: bool
            Replace files with the same name if True

        progress: bool
            Print progress info to terminal

        max_conns: int
            Maximum number of download connections.

        downloader: sunpy.download.Downloder instance
            A Custom downloader to use

        results: Results instance
            A Results manager to use.

        Returns
        -------
        res: Results
            A Results instance or None if no URLs to download
        """

        # Convert IDs to a list if not already
        if not astropy.utils.misc.isiterable(requestIDs) or isinstance(requestIDs, basestring):
            requestIDs = [requestIDs]

        if path is None:
            path = config.get('downloads','download_dir')

        if downloader is None:
            downloader = Downloader(max_conn=max_conn, max_total=max_conn)

        # A Results object tracks the number of downloads requested and the
        # number that have been completed.
        if results is None:
            results = Results(lambda x: None)

        urls = []
        for request_id in requestIDs:
            u = self._request_status(request_id)

            if u.status_code == 200 and u.json()['status'] == '0':
                for ar in u.json()['data']:
                    if overwrite or not os.path.isfile(os.path.join(path, ar['filename'])):
                        urls.append(urlparse.urljoin(BASE_DL_URL + u.json()['dir']+'/', ar['filename']))
                if progress:
                    print("{0} URLs found for Download. Totalling {1}MB".format(len(urls), u.json()['size']))

            else:
                if progress:
                    self.check_request(request_id)

        if urls:
            for url, rcall in list(zip(urls, list(map(lambda x: results.require([x]), urls)))):
                downloader.download(url, callback=rcall, path=path)

        else:
            #Make Results think it has finished.
            results.require([])

        results.poke()
        return results

    def _process_time(self, time):
        """
        Take a UTC time string or datetime instance and generate a astropy.time
        object in TAI frame. Alternatively convert a astropy time object to TAI

        Parameters
        ----------
        time: basestring or datetime or astropy.time
            Input time

        Returns
        -------
        datetime, in TAI
        """
        # Convert from any input (in UTC) to TAI
        if isinstance(time, basestring):
            time = parse_time(time)
        time = astropy.time.Time(time, scale='utc')
        time = astropy.time.Time(time, scale='tai')

        return time.datetime

    def _make_query_payload(self, start_time, end_time, series, notify='',
                          protocol='FITS', compression='rice', **kwargs):
        """
        Build the POST payload for the query parameters
        """

        if protocol.upper() == 'FITS' and compression and compression.lower() == 'rice':
            jprotocol = 'FITS,compress Rice'
        elif protocol.upper() == 'FITS':
            jprotocol = 'FITS, **NONE**'
        else:
            jprotocol = protocol

        payload = {'ds':'{0}[{1}-{2}]'.format(series, start_time.strftime("%Y.%m.%d_%H:%M:%S_TAI"),
                                           end_time.strftime("%Y.%m.%d_%H:%M:%S_TAI")),
                   'format':'json',
                   'method':'url',
                   'notify':notify,
                   'op':'exp_request',
                   'process':'n=0|no_op',
                   'protocol':jprotocol,
                   'requestor':'none',
                   'filenamefmt':'{0}.{{T_REC:A}}.{{CAMERA}}.{{segment}}'.format(series)}

        if kwargs.has_key('wavelength'):
	    if series[0:3] != 'aia':
	        raise TypeError("This series does not support wavelength attribute.")
	    else:
	       if isinstance(kwargs['wavelength'],list):
	           tmp = str(kwargs['wavelength'])
	       else:
	           tmp = '[{0}]'.format(kwargs['wavelength'])
	       payload['ds'] += tmp
            kwargs.pop('wavelength')

        payload.update(kwargs)
        return payload

    def _send_jsoc_request(self, start_time, end_time, series, notify='',
                          protocol='FITS', compression='rice', **kwargs):
        """
        Request that JSOC stages data for download

        This routine puts in a POST request to JSOC
        """

        payload = self._make_query_payload(start_time, end_time, series, notify=notify,
                          protocol=protocol, compression=compression, **kwargs)

        r = requests.post(JSOC_URL, data=payload)

        if r.status_code != 200:
            raise Exception("JSOC POST Request returned code {0}".format(r.status_code))

        return r, r.json()

    def _multi_request(self, **kwargs):
        """
        Make a series of requests to avoid the 100GB limit
        """
        start_time = kwargs.pop('start_time',None)
        end_time = kwargs.pop('end_time',None)
        series = kwargs.pop('series',None)
        if any(x is None for x in (start_time, end_time, series)):
            return []
        start_time = self._process_time(start_time)
        end_time = self._process_time(end_time)
        tr = TimeRange(start_time, end_time)
        returns = []
        response, json_response = self._send_jsoc_request(start_time, end_time, series, **kwargs)

        if json_response['status'] == 3 and json_response['error'] == 'Request exceeds max byte limit of 100000MB':
            returns.append(self._multi_request(tr.start(), tr.center(), series, **kwargs)[0])
            returns.append(self._multi_request(tr.center(), tr.end(), series, **kwargs)[0])
        else:
            returns.append(response)

        return returns

    def _request_status(self, request_id):
        """
        GET the status of a request ID
        """
        payload = {'op':'exp_status', 'requestid':request_id}
        u = requests.get(JSOC_URL, params=payload)

        return u

<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This module implements a mechanism to protect against replay attacks.

The replay protection mechanism is based on a dictionary which caches
previously observed keys.  The dictionaries can be extended and queried.  A
pruning mechanism deletes expired keys from the dictionary.
"""

import time
import pickle

import const

import obfsproxy.common.log as logging

log = logging.get_obfslogger()

class Tracker( object ):

    """
    Implement methods to keep track of replayed keys.

    This class provides methods to add new keys (elements), check whether keys
    have been observed before and to prune the lookup table.
    """

    def __init__( self ):
        """
        Initialise a `Tracker' object.
        """

        self.table = dict()

    def addElement( self, element ):
        """
        Add the given `element' to the lookup table.
        """

        if self.isPresent(element):
            raise LookupError("Element already present in table.")

        self.table[element] = int(time.time())

    def isPresent( self, element ):
        """
        Check if the given `element' is already present in the lookup table.
        """

        log.debug("Looking for existing element in size-%d lookup table." %
                  len(self.table))

        # Prune the replay table before checking for values.
        self.prune()

        return (element in self.table)

    def prune( self ):
        """
        Delete expired elements from the lookup table.
        """

        log.debug("Pruning the replay table.")

        deleteList = []
        now = int(time.time())

        for element in self.table.iterkeys():
            if (now - self.table[element]) > const.EPOCH_GRANULARITY:
                log.debug("Deleting expired element.")
                deleteList.append(element)

        # We can't delete from a dictionary while iterating over it; therefore
        # this construct.
        for elem in deleteList:
            del self.table[elem]

class UniformDHTracker( Tracker ):

    """
    Extends the `Tracker' class with an `addKey()' method for UniformDH.
    """

    def addKey( self, key ):
        """
        Add the given UniformDH public key `key' to the lookup table.
        """

        log.debug("Caching UniformDH public key.")

        self.addElement(key)

class SessionTicketTracker( Tracker ):

    """
    Extends the `Tracker' class with an `addKey()' method for session tickets.
    """

    def addKey( self, key ):
        """
        Add the given session ticket master key `key' to the lookup table.
        """

        log.debug("Caching session ticket master key.")

        self.addElement(key)
<commit_msg>Remove unneeded import of `pickle'.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This module implements a mechanism to protect against replay attacks.

The replay protection mechanism is based on a dictionary which caches
previously observed keys.  The dictionaries can be extended and queried.  A
pruning mechanism deletes expired keys from the dictionary.
"""

import time

import const

import obfsproxy.common.log as logging

log = logging.get_obfslogger()

class Tracker( object ):

    """
    Implement methods to keep track of replayed keys.

    This class provides methods to add new keys (elements), check whether keys
    have been observed before and to prune the lookup table.
    """

    def __init__( self ):
        """
        Initialise a `Tracker' object.
        """

        self.table = dict()

    def addElement( self, element ):
        """
        Add the given `element' to the lookup table.
        """

        if self.isPresent(element):
            raise LookupError("Element already present in table.")

        self.table[element] = int(time.time())

    def isPresent( self, element ):
        """
        Check if the given `element' is already present in the lookup table.
        """

        log.debug("Looking for existing element in size-%d lookup table." %
                  len(self.table))

        # Prune the replay table before checking for values.
        self.prune()

        return (element in self.table)

    def prune( self ):
        """
        Delete expired elements from the lookup table.
        """

        log.debug("Pruning the replay table.")

        deleteList = []
        now = int(time.time())

        for element in self.table.iterkeys():
            if (now - self.table[element]) > const.EPOCH_GRANULARITY:
                log.debug("Deleting expired element.")
                deleteList.append(element)

        # We can't delete from a dictionary while iterating over it; therefore
        # this construct.
        for elem in deleteList:
            del self.table[elem]

class UniformDHTracker( Tracker ):

    """
    Extends the `Tracker' class with an `addKey()' method for UniformDH.
    """

    def addKey( self, key ):
        """
        Add the given UniformDH public key `key' to the lookup table.
        """

        log.debug("Caching UniformDH public key.")

        self.addElement(key)

class SessionTicketTracker( Tracker ):

    """
    Extends the `Tracker' class with an `addKey()' method for session tickets.
    """

    def addKey( self, key ):
        """
        Add the given session ticket master key `key' to the lookup table.
        """

        log.debug("Caching session ticket master key.")

        self.addElement(key)
<|endoftext|>


<commit_before>"""
Support for discovery using GDM (Good Day Mate), multicast protocol by Plex.

Inspired by
  hippojay's plexGDM:
  https://github.com/hippojay/script.plexbmc.helper/resources/lib/plexgdm.py
  iBaa's PlexConnect: https://github.com/iBaa/PlexConnect/PlexAPI.py
"""
import socket

class GDM(object):
    """Base class to discover GDM services."""

    def __init__(self):
        self.entries = []
        self.last_scan = None

    def scan(self):
        """Scan the network."""
        self.update()

    def all(self):
        """Return all found entries.

        Will scan for entries if not scanned recently.
        """
        self.scan()
        return list(self.entries)

    def find_by_content_type(self, value):
        """Return a list of entries that match the content_type."""
        self.scan()
        return [entry for entry in self.entries
                if value in entry['data']['Content_Type']]

    def find_by_data(self, values):
        """Return a list of entries that match the search parameters."""
        self.scan()
        return [entry for entry in self.entries
                if all(item in entry['data'].items()
                       for item in values.items())]

    def update(self):
        """Scan for new GDM services.

        Example of the dict list returned by this function:
        [{'data': 'Content-Type: plex/media-server'
                  'Host: 53f4b5b6023d41182fe88a99b0e714ba.plex.direct'
                  'Name: myfirstplexserver'
                  'Port: 32400'
                  'Resource-Identifier: 646ab0aa8a01c543e94ba975f6fd6efadc36b7'
                  'Updated-At: 1444852697'
                  'Version: 0.9.12.13.1464-4ccd2ca'
          'from': ('10.10.10.100', 32414)}]
        """

        gdm_ip = '239.0.0.250'  # multicast to PMS
        gdm_port = 32414
        gdm_msg = 'M-SEARCH * HTTP/1.0'.encode('ascii')
        gdm_timeout = 1

        self.entries = []

        # setup socket for discovery -> multicast message
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(gdm_timeout)

        # Set the time-to-live for messages for local network
        sock.setsockopt(socket.IPPROTO_IP,
                        socket.IP_MULTICAST_TTL,
                        gdm_timeout)

        try:
            # Send data to the multicast group
            sock.sendto(gdm_msg, (gdm_ip, gdm_port))

            # Look for responses from all recipients
            while True:
                try:
                    data, server = sock.recvfrom(1024)
                    data = data.decode('utf-8')
                    if '200 OK' in data.splitlines()[0]:
                        data = {k: v.strip() for (k, v) in (
                            line.split(':') for line in
                            data.splitlines() if ':' in line)}
                        self.entries.append({'data': data,
                                             'from': server})
                except socket.timeout:
                    break
        finally:
            sock.close()

def main():
    """Test GDM discovery."""
    # pylint: disable=invalid-name
    from pprint import pprint

    gdm = GDM()

    pprint("Scanning GDM...")
    gdm.update()
    pprint(gdm.entries)

if __name__ == "__main__":
    main()
<commit_msg>use the extended signature of the setsockopt (#148)<commit_after>"""
Support for discovery using GDM (Good Day Mate), multicast protocol by Plex.

Inspired by
  hippojay's plexGDM:
  https://github.com/hippojay/script.plexbmc.helper/resources/lib/plexgdm.py
  iBaa's PlexConnect: https://github.com/iBaa/PlexConnect/PlexAPI.py
"""
import socket
import struct

class GDM(object):
    """Base class to discover GDM services."""

    def __init__(self):
        self.entries = []
        self.last_scan = None

    def scan(self):
        """Scan the network."""
        self.update()

    def all(self):
        """Return all found entries.

        Will scan for entries if not scanned recently.
        """
        self.scan()
        return list(self.entries)

    def find_by_content_type(self, value):
        """Return a list of entries that match the content_type."""
        self.scan()
        return [entry for entry in self.entries
                if value in entry['data']['Content_Type']]

    def find_by_data(self, values):
        """Return a list of entries that match the search parameters."""
        self.scan()
        return [entry for entry in self.entries
                if all(item in entry['data'].items()
                       for item in values.items())]

    def update(self):
        """Scan for new GDM services.

        Example of the dict list returned by this function:
        [{'data': 'Content-Type: plex/media-server'
                  'Host: 53f4b5b6023d41182fe88a99b0e714ba.plex.direct'
                  'Name: myfirstplexserver'
                  'Port: 32400'
                  'Resource-Identifier: 646ab0aa8a01c543e94ba975f6fd6efadc36b7'
                  'Updated-At: 1444852697'
                  'Version: 0.9.12.13.1464-4ccd2ca'
          'from': ('10.10.10.100', 32414)}]
        """

        gdm_ip = '239.0.0.250'  # multicast to PMS
        gdm_port = 32414
        gdm_msg = 'M-SEARCH * HTTP/1.0'.encode('ascii')
        gdm_timeout = 1

        self.entries = []

        # setup socket for discovery -> multicast message
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(gdm_timeout)

        # Set the time-to-live for messages for local network
        sock.setsockopt(socket.IPPROTO_IP,
                        socket.IP_MULTICAST_TTL,
                        struct.pack("B", gdm_timeout))

        try:
            # Send data to the multicast group
            sock.sendto(gdm_msg, (gdm_ip, gdm_port))

            # Look for responses from all recipients
            while True:
                try:
                    data, server = sock.recvfrom(1024)
                    data = data.decode('utf-8')
                    if '200 OK' in data.splitlines()[0]:
                        data = {k: v.strip() for (k, v) in (
                            line.split(':') for line in
                            data.splitlines() if ':' in line)}
                        self.entries.append({'data': data,
                                             'from': server})
                except socket.timeout:
                    break
        finally:
            sock.close()

def main():
    """Test GDM discovery."""
    # pylint: disable=invalid-name
    from pprint import pprint

    gdm = GDM()

    pprint("Scanning GDM...")
    gdm.update()
    pprint(gdm.entries)

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before>import datetime
from django.conf import settings
from django import template
from django.core.urlresolvers import reverse
from django.utils.dateformat import format
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from schedule.conf.settings import CHECK_EVENT_PERM_FUNC, CHECK_CALENDAR_PERM_FUNC
from schedule.models import Calendar
from schedule.periods import weekday_names, weekday_abbrs, get_server_timezone

register = template.Library()

@register.inclusion_tag("schedule/_month_table.html", takes_context=True)
def month_table(context, calendar, month, size="regular", shift=None):
    if shift:
        if shift == -1:
            month = month.prev()
        if shift == 1:
            month = month.next()
    if size == "small":
        context['day_names'] = weekday_abbrs
    else:
        context['day_names'] = weekday_names
    context['calendar'] = calendar
    context['month'] = month
    context['size'] = size
    return context

@register.inclusion_tag("schedule/_day_cell.html", takes_context=True)
def day_cell(context, calendar, day, month, size="regular"):
    context.update({
        'calendar': calendar,
        'day': day,
        'month': month,
        'size': size
    })
    return context

@register.inclusion_tag("schedule/_daily_table.html", takes_context=True)
def daily_table(context, day, width, width_slot, height, start=8, end=20, increment=30):
    """
      Display a nice table with occurrences and action buttons.
      Arguments:
      width - width of the table (px)
      width_slot - width of the slot column (px)
      height - height of the table
      start - hour at which the day starts
      end - hour at which the day ends
      increment - size of a time slot (in minutes)
    """
    user = context['request'].user
    addable = CHECK_EVENT_PERM_FUNC(None, user)
    if 'calendar' in context:
        addable &= CHECK_CALENDAR_PERM_FUNC(context['calendar'], user)
    context['addable'] = addable

    width_occ = width - width_slot
    day_part = day.get_time_slot(day.start + datetime.timedelta(hours=start), day.start + datetime.timedelta(hours=end))
    occurrences = day_part.get_occurrences()
    for occ in occurrences:
        occ.start = get_server_timezone(occ.start)
        occ.end = get_server_timezone(occ.end)
    occurrences = _cook_occurrences(day_part, occurrences, width_occ, height)
    # get slots to display on the left
    slots = _cook_slots(day_part, increment, width, height)
    context['occurrences'] = occurrences
    context['slots'] = slots
    context['width'] = width
    context['width_slot'] = width_slot
    context['width_occ'] = width_occ
    context['height'] = height
    return context

@register.inclusion_tag("schedule/_event_title.html", takes_context=True)
def title(context, occurrence):
    context.update({
        'occurrence': occurrence,
    })
    return context

@register.inclusion_tag("schedule/_event_options.html", takes_context=True)
def options(context, occurrence):
    context.update({
        'occurrence': occurrence,
        'MEDIA_URL': getattr(settings, "MEDIA_URL"),
    })
    context['view_occurrence'] = occurrence.get_absolute_url()
    user = context['request'].user
    if CHECK_EVENT_PERM_FUNC(occurrence.event, user) and CHECK_CALENDAR_PERM_FUNC(occurrence.event.calendar, user):
        context['edit_occurrence'] = occurrence.get_edit_url()
        context['cancel_occurrence'] = occurrence.get_cancel_url()
        context['delete_event'] = reverse('delete_event', args=(occurrence.event.id,))
        context['edit_event'] = reverse('edit_event', args=(occurrence.event.calendar.slug, occurrence.event.id,))
    else:
        context['edit_event'] = context['delete_event'] = ''
    return context

@register.inclusion_tag("schedule/_create_event_options.html", takes_context=True)
def create_event_url(context, calendar, slot):
    context.update({
        'calendar': calendar,
        'MEDIA_URL': getattr(settings, "MEDIA_URL"),
    })
    lookup_context = {
        'calendar_slug': calendar.slug,
    }
    context['create_event_url'] = "%s%s" % (
        reverse("calendar_create_event", kwargs=lookup_context),
        querystring_for_date(slot, autoescape=False))
    return context

class CalendarNode(template.Node):
    def __init__(self, content_object, distinction, context_var, create=False):
        self.content_object = template.Variable(content_object)
        self.distinction = distinction
        self.context_var = context_var

    def render(self, context):
        context[self.context_var] = Calendar.objects.get_calendar_for_object(
            self.content_object.resolve(context), self.distinction)
        return ''

def do_get_calendar_for_object(parser, token):
    contents = token.split_contents()
    if len(contents) == 4:
        tag_name, content_object, _, context_var = contents
        distinction = None
    elif len(contents) == 5:
        tag_name, content_object, distinction, _, context_var = token.split_contents()
    else:
        raise template.TemplateSyntaxError, "%r tag follows form %r <content_object> as <context_var>" % (token.contents.split()[0], token.contents.split()[0])
    return CalendarNode(content_object, distinction, context_var)

class CreateCalendarNode(template.Node):
    def __init__(self, content_object, distinction, context_var, name):
        self.content_object = template.Variable(content_object)
        self.distinction = distinction
        self.context_var = context_var
        self.name = name

    def render(self, context):
        context[self.context_var] = Calendar.objects.get_or_create_calendar_for_object(self.content_object.resolve(context), self.distinction, name=self.name)
        return ''

def do_get_or_create_calendar_for_object(parser, token):
    contents = token.split_contents()
    if len(contents) > 2:
        obj = contents[1]
        if 'by' in contents:
            by_index = contents.index('by')
            distinction = contents[by_index + 1]
        else:
            distinction = None
        if 'named' in contents:
            named_index = contents.index('named')
            name = contents[named_index + 1]
            if name[0] == name[-1]:
                name = name[1:-1]
        else:
            name = None
        if 'as' in contents:
            as_index = contents.index('as')
            context_var = contents[as_index + 1]
        else:
            raise template.TemplateSyntaxError, "%r tag requires an a context variable: %r <content_object> [named <calendar name>] [by <distinction>] as <context_var>" % (token.split_contents()[0], token.split_contents()[0])
    else:
        raise template.TemplateSyntaxError, "%r tag follows form %r <content_object> [named <calendar name>] [by <distinction>] as <context_var>" % (token.split_contents()[0], token.split_contents()[0])
    return CreateCalendarNode(obj, distinction, context_var, name)

register.tag('get_calendar', do_get_calendar_for_object)
register.tag('get_or_create_calendar', do_get_or_create_calendar_for_object)

@register.simple_tag
@register.filter(needs_autoescape=True)
def querystring_for_date(date, num=6, autoescape=False):
    if autoescape:
        esc = conditional_escape
    else:
        esc = lambda x: x
    query_string = '?'
    qs_parts = ['year=%d', 'month=%d', 'day=%d', 'hour=%d', 'minute=%d', 'second=%d']
    qs_vars = (date.year, date.month, date.day, date.hour, date.minute, date.second)
    query_string += esc('&'.join(qs_parts[:num]) % qs_vars[:num])
    return mark_safe(query_string)

@register.simple_tag
def prev_url(target, slug, period):
    return '%s%s' % (
        reverse(target, kwargs=dict(calendar_slug=slug)),
        querystring_for_date(period.prev().start, autoescape=True))

@register.simple_tag
def next_url(target, slug, period):
    return '%s%s' % (
        reverse(target, kwargs=dict(calendar_slug=slug)),
        querystring_for_date(period.next().start, autoescape=True))

@register.inclusion_tag("schedule/_prevnext.html")
def prevnext(target, slug, period, fmt=None):
    if fmt is None:
        fmt = settings.DATE_FORMAT
    context = {
        'slug': slug,
        'period': period,
        'period_name': format(period.start, fmt),
        'target': target,
    }
    return context

@register.inclusion_tag("schedule/_detail.html")
def detail(occurrence):
    context = {
        'occurrence': occurrence,
    }
    return context

def _cook_occurrences(period, occs, width, height):
    """ Prepare occurrences to be displayed.
        Calculate dimensions and position (in px) for each occurrence.
        The algorithm tries to fit overlapping occurrences so that they require a minimum
        number of "columns".
        Arguments:
        period - time period for the whole series
        occs - occurrences to be displayed
        increment - slot size in minutes
        width - width of the occurrences column (px)
        height - height of the table (px)
    """
    last = {}
    display_occs = []
    # find out which occurrences overlap
    for o in occs:
        o.data = period.classify_occurrence(o)
        if not o.data:
            continue
        display_occs.append(o)
        o.level = -1
        o.max = 0
        if not last:
            last[0] = o
            o.level = 0
        else:
            for k in sorted(last.keys()):
                if last[k].end <= o.start:
                    o.level = k
                    last[k] = o
                    break
            if o.level == -1:
                k = k + 1
                last[k] = o
                o.level = k
    # calculate position and dimensions
    for o in display_occs:
        # number of overlapping occurrences
        o.max = len([n for n in occs if not(n.end <= o.start or n.start >= o.end)])
    for o in display_occs:
        o.cls = o.data['class']
        o.real_start = max(o.start, period.start)
        o.real_end = min(o.end, period.end)
        # number of "columns" is a minimum number of overlaps for each overlapping group
        o.max = max([n.max for n in display_occs if not(n.end <= o.start or n.start >= o.end)] or [1])
        w = int(width / (o.max))
        o.width = w - 2
        o.left = w * o.level
        range = (period.end - period.start).seconds
        if (range == 0):
            o.top = int(height *
                        (float((o.real_start - period.start).seconds) /
                         (24*60*60)))
            o.height = int(height *
                           (float((o.real_end - o.real_start).seconds) /
                            (24*60*60)))
        else:
            o.top = int(height *
                        (float((o.real_start - period.start).seconds) /
                         (period.end - period.start).seconds))
            o.height = int(height *
                           (float((o.real_end - o.real_start).seconds) /
                            (period.end - period.start).seconds))
        # trim what extends beyond the area
        o.height = min(o.height, height - o.top)
    return display_occs

def _cook_slots(period, increment, width, height):
    """
        Prepare slots to be displayed on the left hand side
        calculate dimensions (in px) for each slot.
        Arguments:
        period - time period for the whole series
        increment - slot size in minutes
        width - width of the slot column (px)
        height - height of the table (px)
    """
    tdiff = datetime.timedelta(minutes=increment)
    if (period.end - period.start).seconds == 0:
        num = 24  # hours in day
    else:
        num = (period.end - period.start).seconds / tdiff.seconds
    s = period.start
    slots = []
    for i in range(num):
        sl = period.get_time_slot(s, s + tdiff)
        sl.top = int(height / float(num)) * i
        sl.height = int(height / float(num))
        slots.append(sl)
        s = s + tdiff
    return slots

@register.simple_tag
def hash_occurrence(occ):
    return '%s_%s' % (occ.start.strftime('%Y%m%d%H%M%S'), occ.event.id)
<commit_msg>Cleanup code, don't need to compare value to zero.<commit_after>import datetime
from django.conf import settings
from django import template
from django.core.urlresolvers import reverse
from django.utils.dateformat import format
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from schedule.conf.settings import CHECK_EVENT_PERM_FUNC, CHECK_CALENDAR_PERM_FUNC
from schedule.models import Calendar
from schedule.periods import weekday_names, weekday_abbrs, get_server_timezone

register = template.Library()

@register.inclusion_tag("schedule/_month_table.html", takes_context=True)
def month_table(context, calendar, month, size="regular", shift=None):
    if shift:
        if shift == -1:
            month = month.prev()
        if shift == 1:
            month = month.next()
    if size == "small":
        context['day_names'] = weekday_abbrs
    else:
        context['day_names'] = weekday_names
    context['calendar'] = calendar
    context['month'] = month
    context['size'] = size
    return context

@register.inclusion_tag("schedule/_day_cell.html", takes_context=True)
def day_cell(context, calendar, day, month, size="regular"):
    context.update({
        'calendar': calendar,
        'day': day,
        'month': month,
        'size': size
    })
    return context

@register.inclusion_tag("schedule/_daily_table.html", takes_context=True)
def daily_table(context, day, width, width_slot, height, start=8, end=20, increment=30):
    """
      Display a nice table with occurrences and action buttons.
      Arguments:
      width - width of the table (px)
      width_slot - width of the slot column (px)
      height - height of the table
      start - hour at which the day starts
      end - hour at which the day ends
      increment - size of a time slot (in minutes)
    """
    user = context['request'].user
    addable = CHECK_EVENT_PERM_FUNC(None, user)
    if 'calendar' in context:
        addable &= CHECK_CALENDAR_PERM_FUNC(context['calendar'], user)
    context['addable'] = addable

    width_occ = width - width_slot
    day_part = day.get_time_slot(day.start + datetime.timedelta(hours=start), day.start + datetime.timedelta(hours=end))
    occurrences = day_part.get_occurrences()
    for occ in occurrences:
        occ.start = get_server_timezone(occ.start)
        occ.end = get_server_timezone(occ.end)
    occurrences = _cook_occurrences(day_part, occurrences, width_occ, height)
    # get slots to display on the left
    slots = _cook_slots(day_part, increment, width, height)
    context['occurrences'] = occurrences
    context['slots'] = slots
    context['width'] = width
    context['width_slot'] = width_slot
    context['width_occ'] = width_occ
    context['height'] = height
    return context

@register.inclusion_tag("schedule/_event_title.html", takes_context=True)
def title(context, occurrence):
    context.update({
        'occurrence': occurrence,
    })
    return context

@register.inclusion_tag("schedule/_event_options.html", takes_context=True)
def options(context, occurrence):
    context.update({
        'occurrence': occurrence,
        'MEDIA_URL': getattr(settings, "MEDIA_URL"),
    })
    context['view_occurrence'] = occurrence.get_absolute_url()
    user = context['request'].user
    if CHECK_EVENT_PERM_FUNC(occurrence.event, user) and CHECK_CALENDAR_PERM_FUNC(occurrence.event.calendar, user):
        context['edit_occurrence'] = occurrence.get_edit_url()
        context['cancel_occurrence'] = occurrence.get_cancel_url()
        context['delete_event'] = reverse('delete_event', args=(occurrence.event.id,))
        context['edit_event'] = reverse('edit_event', args=(occurrence.event.calendar.slug, occurrence.event.id,))
    else:
        context['edit_event'] = context['delete_event'] = ''
    return context

@register.inclusion_tag("schedule/_create_event_options.html", takes_context=True)
def create_event_url(context, calendar, slot):
    context.update({
        'calendar': calendar,
        'MEDIA_URL': getattr(settings, "MEDIA_URL"),
    })
    lookup_context = {
        'calendar_slug': calendar.slug,
    }
    context['create_event_url'] = "%s%s" % (
        reverse("calendar_create_event", kwargs=lookup_context),
        querystring_for_date(slot, autoescape=False))
    return context

class CalendarNode(template.Node):
    def __init__(self, content_object, distinction, context_var, create=False):
        self.content_object = template.Variable(content_object)
        self.distinction = distinction
        self.context_var = context_var

    def render(self, context):
        context[self.context_var] = Calendar.objects.get_calendar_for_object(
            self.content_object.resolve(context), self.distinction)
        return ''

def do_get_calendar_for_object(parser, token):
    contents = token.split_contents()
    if len(contents) == 4:
        tag_name, content_object, _, context_var = contents
        distinction = None
    elif len(contents) == 5:
        tag_name, content_object, distinction, _, context_var = token.split_contents()
    else:
        raise template.TemplateSyntaxError, "%r tag follows form %r <content_object> as <context_var>" % (token.contents.split()[0], token.contents.split()[0])
    return CalendarNode(content_object, distinction, context_var)

class CreateCalendarNode(template.Node):
    def __init__(self, content_object, distinction, context_var, name):
        self.content_object = template.Variable(content_object)
        self.distinction = distinction
        self.context_var = context_var
        self.name = name

    def render(self, context):
        context[self.context_var] = Calendar.objects.get_or_create_calendar_for_object(self.content_object.resolve(context), self.distinction, name=self.name)
        return ''

def do_get_or_create_calendar_for_object(parser, token):
    contents = token.split_contents()
    if len(contents) > 2:
        obj = contents[1]
        if 'by' in contents:
            by_index = contents.index('by')
            distinction = contents[by_index + 1]
        else:
            distinction = None
        if 'named' in contents:
            named_index = contents.index('named')
            name = contents[named_index + 1]
            if name[0] == name[-1]:
                name = name[1:-1]
        else:
            name = None
        if 'as' in contents:
            as_index = contents.index('as')
            context_var = contents[as_index + 1]
        else:
            raise template.TemplateSyntaxError, "%r tag requires an a context variable: %r <content_object> [named <calendar name>] [by <distinction>] as <context_var>" % (token.split_contents()[0], token.split_contents()[0])
    else:
        raise template.TemplateSyntaxError, "%r tag follows form %r <content_object> [named <calendar name>] [by <distinction>] as <context_var>" % (token.split_contents()[0], token.split_contents()[0])
    return CreateCalendarNode(obj, distinction, context_var, name)

register.tag('get_calendar', do_get_calendar_for_object)
register.tag('get_or_create_calendar', do_get_or_create_calendar_for_object)

@register.simple_tag
@register.filter(needs_autoescape=True)
def querystring_for_date(date, num=6, autoescape=False):
    if autoescape:
        esc = conditional_escape
    else:
        esc = lambda x: x
    query_string = '?'
    qs_parts = ['year=%d', 'month=%d', 'day=%d', 'hour=%d', 'minute=%d', 'second=%d']
    qs_vars = (date.year, date.month, date.day, date.hour, date.minute, date.second)
    query_string += esc('&'.join(qs_parts[:num]) % qs_vars[:num])
    return mark_safe(query_string)

@register.simple_tag
def prev_url(target, slug, period):
    return '%s%s' % (
        reverse(target, kwargs=dict(calendar_slug=slug)),
        querystring_for_date(period.prev().start, autoescape=True))

@register.simple_tag
def next_url(target, slug, period):
    return '%s%s' % (
        reverse(target, kwargs=dict(calendar_slug=slug)),
        querystring_for_date(period.next().start, autoescape=True))

@register.inclusion_tag("schedule/_prevnext.html")
def prevnext(target, slug, period, fmt=None):
    if fmt is None:
        fmt = settings.DATE_FORMAT
    context = {
        'slug': slug,
        'period': period,
        'period_name': format(period.start, fmt),
        'target': target,
    }
    return context

@register.inclusion_tag("schedule/_detail.html")
def detail(occurrence):
    context = {
        'occurrence': occurrence,
    }
    return context

def _cook_occurrences(period, occs, width, height):
    """ Prepare occurrences to be displayed.
        Calculate dimensions and position (in px) for each occurrence.
        The algorithm tries to fit overlapping occurrences so that they require a minimum
        number of "columns".
        Arguments:
        period - time period for the whole series
        occs - occurrences to be displayed
        increment - slot size in minutes
        width - width of the occurrences column (px)
        height - height of the table (px)
    """
    last = {}
    display_occs = []
    # find out which occurrences overlap
    for o in occs:
        o.data = period.classify_occurrence(o)
        if not o.data:
            continue
        display_occs.append(o)
        o.level = -1
        o.max = 0
        if not last:
            last[0] = o
            o.level = 0
        else:
            for k in sorted(last.keys()):
                if last[k].end <= o.start:
                    o.level = k
                    last[k] = o
                    break
            if o.level == -1:
                k = k + 1
                last[k] = o
                o.level = k
    # calculate position and dimensions
    for o in display_occs:
        # number of overlapping occurrences
        o.max = len([n for n in occs if not(n.end <= o.start or n.start >= o.end)])
    for o in display_occs:
        o.cls = o.data['class']
        o.real_start = max(o.start, period.start)
        o.real_end = min(o.end, period.end)
        # number of "columns" is a minimum number of overlaps for each overlapping group
        o.max = max([n.max for n in display_occs if not(n.end <= o.start or n.start >= o.end)] or [1])
        w = int(width / (o.max))
        o.width = w - 2
        o.left = w * o.level
        range = (period.end - period.start).seconds
        if range:
            o.top = int(height * ((o.real_start - period.start).seconds / range))
            o.height = int(height * ((o.real_end - o.real_start).seconds / range))
        else:
            o.top = int(height * ((o.real_start - period.start).seconds / (24*60*60)))
            o.height = int(height * ((o.real_end - o.real_start).seconds / (24*60*60)))
        o.height = min(o.height, height - o.top)  # trim what extends beyond the area
    return display_occs

def _cook_slots(period, increment, width, height):
    """
        Prepare slots to be displayed on the left hand side
        calculate dimensions (in px) for each slot.
        Arguments:
        period - time period for the whole series
        increment - slot size in minutes
        width - width of the slot column (px)
        height - height of the table (px)
    """
    tdiff = datetime.timedelta(minutes=increment)
    if (period.end - period.start).seconds:
        num = (period.end - period.start).seconds / tdiff.seconds
    else:
        num = 24  # hours in day
    s = period.start
    slots = []
    for i in range(num):
        sl = period.get_time_slot(s, s + tdiff)
        sl.top = int(height / float(num)) * i
        sl.height = int(height / float(num))
        slots.append(sl)
        s = s + tdiff
    return slots

@register.simple_tag
def hash_occurrence(occ):
    return '%s_%s' % (occ.start.strftime('%Y%m%d%H%M%S'), occ.event.id)
<|endoftext|>