from trac.core import *
from trac.web import IRequestHandler, IRequestFilter
from trac.web.session import DetachedSession
from trac.ticket.web_ui import TicketModule
from base64 import b64decode, urlsafe_b64decode
import time, zlib, sys
from xml.sax import saxutils

class AnonymousDetachedSession(DetachedSession):
    """Allows access to non-authenticated session storage.""" 

    def __init__(self, env, sid):
        super(AnonymousDetachedSession, self).__init__(env, None)
        self.get_session(sid) 

class JosmTicket(Component):
    """ This class handles calls to '/josmticket' to create a josm specific ticket.
        This can also be used to create a ticket from inside JOSM.

        It allows you to send in debug data as text (base64 or gzipped) or to store them for later retrival with a retrival key.

        Author of pdata-storage: Michael Zangl with help of stoecker
    """

    handlers = ExtensionPoint(IRequestHandler)

    implements(IRequestHandler)

    def match_request(self, req):
        return req.path_info == '/josmticket'

    def process_request(self, req):
        self.cleanup_pdata()

	# can only be /josmticket to store pdata
        pdata = req.args.get('pdata')
        try:
            pdata = b64decode(pdata);
        except TypeError:
            self.send_josm_error(req, "Could not decode base64")
            return

        if len(pdata) > 100000:
            self.send_josm_error(req, "Cannot store that much text")
            return

        req.session['preparedticket'] = pdata;
        req.session['preparedticket_time'] = int(time.time());
        self.send_josm_ticket(req, {'preparedid': req.session.sid})

    def send_josm_error(self, req, message):
        self.send_josm_ticket(req, {'error': message}, 'error')

    def send_josm_ticket(self, req, data, status = 'ok'):
        items = ["  <%(k)s>%(v)s</%(k)s>\n" % {'k': saxutils.escape(key), 'v' : saxutils.escape(value)} for key, value in data.items()]
	req.send('<?xml version="1.0" encoding="UTF-8"?>\n'
               + '<josmticket status="ok">\n'
               + ''.join(items)
               + '</josmticket>', 'text/xml', 200 if status == 'ok' else 400)
        

    def process_base64_data(self, data, urlsafe = True):
        lens = len(data)
        lenx = int(lens) / 4 * 4 # round down
        try:
            func = urlsafe_b64decode if urlsafe else b64decode
            return func(unicode(data[:lenx]).encode('ascii'))
        except:
            return "(Error decoding base64)"

    def process_tdata(self, data):
        data = "==== What steps will reproduce the problem?\n" \
               + "1. \n" \
               + "2. \n" \
               + "3. \n" \
               + "\n" \
               + "==== What is the expected result?\n\n" \
               + "==== What happens instead?\n\n" \
               + "==== Please provide any additional information below. Attach a screenshot if possible.\n\n" \
               + "{{{\n" + str(data) + "\n}}}\n"
        return data

    implements(IRequestFilter)
    def pre_process_request(self, req, handler):
        if(req.path_info == '/josmticket'):
            if req.method == 'POST' :
                if req.args.getfirst('pdata') != None:
                    #pdata store request.
                    # Let us handle this, convince trac that this is not a CSRF attack
                    req.form_token = 'x'
                    req.args['__FORM_TOKEN'] = 'x'
                    return handler;
                req.environ['REQUEST_METHOD'] = 'GET'

            description = self.get_description_from_args(req)

            # This also seems to fix the unicode issues.
            req.args['description'] = self.process_tdata(description).decode('utf-8', errors='ignore')
            req.args['keywords'] = 'template_report'
            req.environ['PATH_INFO'] = '/newticket'
            for newhandler in self.handlers:
                if isinstance(newhandler, TicketModule):
                    handler = newhandler
        return (handler)

    def get_description_from_args(self, req):
        gdata = req.args.getfirst('gdata')
        if gdata != None:
            gdata = self.process_base64_data(gdata)
            try:
                return zlib.decompressobj(15+32).decompress(gdata)
            except:
                return "Error decompressing compressed data."

        tdata = req.args.getfirst('tdata')
        if tdata != None:
            tdata = self.process_base64_data(tdata)
            return tdata

        data = req.args.getfirst('data')
        if data != None:
            return self.process_base64_data(data, False)

        sid = req.args.getfirst('pdata_stored')
        if sid != None:
             return self.get_pdata(sid);
        return ""

    def get_pdata(self, sessionId):
        session = AnonymousDetachedSession(self.env, sessionId)
        return session.get('preparedticket', 'Could not retrieve text.')

    def post_process_request(self, req, *args):
        return args

    # Clean pdata older than a given time.
    def cleanup_pdata(self):
        # This code ignores possible race condition
        CLEAN_PERIOD = 60 * 60 #1h
        t = int(time.time())
        try:
            last_clean = self.env.db_query("SELECT value FROM system "
                      "WHERE name='pdata_lastclean' AND value >= %s", (t - CLEAN_PERIOD,))
            if len(last_clean) > 0:
                return;
        except:
            pass

        self.do_cleanup_pdata()
        with self.env.db_transaction as db:
            db("INSERT OR REPLACE INTO system VALUES ('pdata_lastclean', %s)", (t,))

    def do_cleanup_pdata(self):
        DELETE_AFTER = 6 * 60 * 60 #6h
        etime = int(time.time() - DELETE_AFTER)
        with self.env.db_transaction as db:
            to_clean = db("SELECT sid FROM session_attribute WHERE name = 'preparedticket_time' AND " + db.cast('value', 'int') + " < %s", (etime,));
            to_clean = list(to_clean) # fetch them, just to be sure.
            for sid, in to_clean:
                db("""
                    DELETE FROM session_attribute 
                    WHERE (name = 'preparedticket' OR name = 'preparedticket_time') AND sid = %s""",
                    (str(sid),))

