# -*- coding: utf-8 -*-

#

# jQuery File Upload Plugin GAE Python Example

# https://github.com/blueimp/jQuery-File-Upload

#

# Copyright 2011, Sebastian Tschan

# https://blueimp.net

#

# Licensed under the MIT license:

# http://www.opensource.org/licenses/MIT

#



from google.appengine.api import memcache, images

import json

import os

import re

import urllib

import webapp2



DEBUG=os.environ.get('SERVER_SOFTWARE', '').startswith('Dev')

WEBSITE = 'https://blueimp.github.io/jQuery-File-Upload/'

MIN_FILE_SIZE = 1  # bytes

# Max file size is memcache limit (1MB) minus key size minus overhead:

MAX_FILE_SIZE = 999000  # bytes

IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)')

ACCEPT_FILE_TYPES = IMAGE_TYPES

THUMB_MAX_WIDTH = 80

THUMB_MAX_HEIGHT = 80

THUMB_SUFFIX = '.'+str(THUMB_MAX_WIDTH)+'x'+str(THUMB_MAX_HEIGHT)+'.png'

EXPIRATION_TIME = 300  # seconds

# If set to None, only allow redirects to the referer protocol+host.

# Set to a regexp for custom pattern matching against the redirect value:

REDIRECT_ALLOW_TARGET = None



class CORSHandler(webapp2.RequestHandler):

    def cors(self):

        headers = self.response.headers

        headers['Access-Control-Allow-Origin'] = '*'

        headers['Access-Control-Allow-Methods'] =\

            'OPTIONS, HEAD, GET, POST, DELETE'

        headers['Access-Control-Allow-Headers'] =\

            'Content-Type, Content-Range, Content-Disposition'



    def initialize(self, request, response):

        super(CORSHandler, self).initialize(request, response)

        self.cors()



    def json_stringify(self, obj):

        return json.dumps(obj, separators=(',', ':'))



    def options(self, *args, **kwargs):

        pass



class UploadHandler(CORSHandler):

    def validate(self, file):

        if file['size'] < MIN_FILE_SIZE:

            file['error'] = 'File is too small'

        elif file['size'] > MAX_FILE_SIZE:

            file['error'] = 'File is too big'

        elif not ACCEPT_FILE_TYPES.match(file['type']):

            file['error'] = 'Filetype not allowed'

        else:

            return True

        return False



    def validate_redirect(self, redirect):

        if redirect:

            if REDIRECT_ALLOW_TARGET:

                return REDIRECT_ALLOW_TARGET.match(redirect)

            referer = self.request.headers['referer']

            if referer:

                from urlparse import urlparse

                parts = urlparse(referer)

                redirect_allow_target = '^' + re.escape(

                    parts.scheme + '://' + parts.netloc + '/'

                )

            return re.match(redirect_allow_target, redirect)

        return False



    def get_file_size(self, file):

        file.seek(0, 2)  # Seek to the end of the file

        size = file.tell()  # Get the position of EOF

        file.seek(0)  # Reset the file position to the beginning

        return size



    def write_blob(self, data, info):

        key = urllib.quote(info['type'].encode('utf-8'), '') +\

            '/' + str(hash(data)) +\

            '/' + urllib.quote(info['name'].encode('utf-8'), '')

        try:

            memcache.set(key, data, time=EXPIRATION_TIME)

        except: #Failed to add to memcache

            return (None, None)

        thumbnail_key = None

        if IMAGE_TYPES.match(info['type']):

            try:

                img = images.Image(image_data=data)

                img.resize(

                    width=THUMB_MAX_WIDTH,

                    height=THUMB_MAX_HEIGHT

                )

                thumbnail_data = img.execute_transforms()

                thumbnail_key = key + THUMB_SUFFIX

                memcache.set(

                    thumbnail_key,

                    thumbnail_data,

                    time=EXPIRATION_TIME

                )

            except: #Failed to resize Image or add to memcache

                thumbnail_key = None

        return (key, thumbnail_key)



    def handle_upload(self):

        results = []

        for name, fieldStorage in self.request.POST.items():

            if type(fieldStorage) is unicode:

                continue

            result = {}

            result['name'] = urllib.unquote(fieldStorage.filename)

            result['type'] = fieldStorage.type

            result['size'] = self.get_file_size(fieldStorage.file)

            if self.validate(result):

                key, thumbnail_key = self.write_blob(

                    fieldStorage.value,

                    result

                )

                if key is not None:

                    result['url'] = self.request.host_url + '/' + key

                    result['deleteUrl'] = result['url']

                    result['deleteType'] = 'DELETE'

                    if thumbnail_key is not None:

                        result['thumbnailUrl'] = self.request.host_url +\

                             '/' + thumbnail_key

                else:

                    result['error'] = 'Failed to store uploaded file.'

            results.append(result)

        return results



    def head(self):

        pass



    def get(self):

        self.redirect(WEBSITE)



    def post(self):

        if (self.request.get('_method') == 'DELETE'):

            return self.delete()

        result = {'files': self.handle_upload()}

        s = self.json_stringify(result)

        redirect = self.request.get('redirect')

        if self.validate_redirect(redirect):

            return self.redirect(str(

                redirect.replace('%s', urllib.quote(s, ''), 1)

            ))

        if 'application/json' in self.request.headers.get('Accept'):

            self.response.headers['Content-Type'] = 'application/json'

        self.response.write(s)



class FileHandler(CORSHandler):

    def normalize(self, str):

        return urllib.quote(urllib.unquote(str), '')



    def get(self, content_type, data_hash, file_name):

        content_type = self.normalize(content_type)

        file_name = self.normalize(file_name)

        key = content_type + '/' + data_hash + '/' + file_name

        data = memcache.get(key)

        if data is None:

            return self.error(404)

        # Prevent browsers from MIME-sniffing the content-type:

        self.response.headers['X-Content-Type-Options'] = 'nosniff'

        content_type = urllib.unquote(content_type)

        if not IMAGE_TYPES.match(content_type):

            # Force a download dialog for non-image types:

            content_type = 'application/octet-stream'

        elif file_name.endswith(THUMB_SUFFIX):

            content_type = 'image/png'

        self.response.headers['Content-Type'] = content_type

        # Cache for the expiration time:

        self.response.headers['Cache-Control'] = 'public,max-age=%d' \

            % EXPIRATION_TIME

        self.response.write(data)



    def delete(self, content_type, data_hash, file_name):

        content_type = self.normalize(content_type)

        file_name = self.normalize(file_name)

        key = content_type + '/' + data_hash + '/' + file_name

        result = {key: memcache.delete(key)}

        content_type = urllib.unquote(content_type)

        if IMAGE_TYPES.match(content_type):

            thumbnail_key = key + THUMB_SUFFIX

            result[thumbnail_key] = memcache.delete(thumbnail_key)

        if 'application/json' in self.request.headers.get('Accept'):

            self.response.headers['Content-Type'] = 'application/json'

        s = self.json_stringify(result)

        self.response.write(s)



app = webapp2.WSGIApplication(

    [

        ('/', UploadHandler),

        ('/(.+)/([^/]+)/([^/]+)', FileHandler)

    ],

    debug=DEBUG

)

