#!/usr/bin/python

# json_to_from_browser.py
#       --copyright--                   Copyright 2011 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       October 30, 2011        bar
#       November 2, 2011        bar     do the server side
#       November 13, 2011       bar     allow caller to give the queues
#       November 14, 2011       bar     typo
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       --eodstamps--
##      \file
#       \namespace              tzpython.json_to_from_browser
#
#
#       Logic to support use of json_to_browser.js and json_to_server.js.
#
#

import  Queue
import  random
import  string
import  threading

import  tzlib


try :
    import  json                            # python 2.6 has this?
    if  not hasattr(json, 'read') :
        json.read   = json.loads
    pass
except ImportError :
    try :
        import  jsonlib                     # well, otherwise, I have jsonlib installed
        json    = jsonlib
    except ImportError :
        try     :
            import simplejson as json
            json.read   = json.loads
            json.write  = json.dumps
        except  :
            json        = None
    pass



def make_json(dct) :
    """ Given a dictionary, return a JSON string (appropriate for returning in answer to a hit by json_to_server.js logic. """

    return(json.dumps(dct))


def unmake_json(json_string) :
    """
        Given a string, interpret it as JSON and return the dictionary, array, string, number, or whatever.

        Return None if the string is empty.
        Note: A string can be returned for '"blah"'.
              Etc.
    """

    if  not json_string.strip() :
        return(None)

    return(json.loads(json_string))


def safe_unmake_json(json_string) :
    """
        Given a string, interpret as JSON and return the dictionary.

        Return None if there's nothing there.
        Return "" if the JSON was bad.
        Note: A string can be returned for '"blah"'.
             Etc.
    """

    try :
        return(unmake_json(json_string))
    except ( ValueError, IndexError, TypeError, KeyError, ) :
        pass

    return("")



json_to_browser_wrapper = "zQ"          # 2nd char is always 'Q' to save Firefox 3.6 from itsef. See json_to_browser.js
json_to_browser_wrapchr = list('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')

def json_to_browser_wrap(s) :
    """ Given a JSON string, return a wrapped string that can be pushed to a browser using json_to_browser.js logic. """

    global  json_to_browser_wrapper

    sh      = False
    while   s.find(json_to_browser_wrapper) >= 0 :
        js  = list(json_to_browser_wrapper[2:])
        if  (not sh) and (len(js) >= 4) :
            sh  = True
            random.shuffle(js)
        else    :
            sh  = False
            js.append(random.choice(json_to_browser_wrapchr))
        json_to_browser_wrapper = json_to_browser_wrapper[:2] + string.join(js, '')

    return(json_to_browser_wrapper + " " + s + " " + json_to_browser_wrapper)


def json_to_browser_str(dct) :
    """ Given a dictionary, return a JSON string appropriate for pushing to the browser through its json_to_browser.js logic. """

    return(json_to_browser_wrap(make_json(dct)))




MAX_OUT_IN_ONE_CONNECTION   = 10000             # don't send much more than this number of bytes in one "pull" connection


class   a_json_browser(object) :

    def __init__(me, iq = None, oq = None) :
        me.iq       = iq    or  Queue.Queue()
        me.oq       = oq    or  Queue.Queue()
        me.lock     = threading.RLock()
        me.ocnt     = 0
        me._stop    = False


    #
    #
    #   The following logic allows the web server to call get_json_for_browser() when the browser hits it with a "pull" (json_to_browser.js) hit.
    #       This call returns json to send to the browser as a simple string pass to write_data().
    #       If the call returns None, then the web server should break the connection.
    #   Another thread calls one of the put...() routines to push data to the browser.
    #
    #

    def new_browser_hit(me) :
        """
            The browser has hit us.
            Presumably, caller will be calling get_json_for_browser() "forever" - until is returns None.
        """

        me.lock.acquire()
        me.ocnt     = 0
        me.lock.release()


    def get_json_for_browser(me, timeout = None) :
        """
            Get a string to be sent to the browser (json_to_browser.js, that is).
            Return None if timeout is given and triggered.
            Normally, if return value is None, then caller should break the connection to the browser.
            After call to stop(), will return None (possibly after returning some data from the existing call).
        """

        if  me._stop :
            return(None)

        if  me.ocnt > MAX_OUT_IN_ONE_CONNECTION :
            return(json_to_browser_wrap('me.again();'))

        s           = tzlib.q_get(me.oq, timeout = timeout)
        if  s      == None :
            return(None)

        js          = ""
        while True  :
            js     += json_to_browser_wrap(s)
            s       = tzlib.q_get(0)
            if  s  == None :
                me.put(None)
                break
            pass

        if  not js  :
            return(None)

        me.lock.acquire()
        me.ocnt    += len(js)
        me.lock.release()

        return(js)


    def put(me, s) :
        """
            Put a "True" string or json-able structure to the browser.
            Queues a copy of 's' for later sending out from call to get_json_for_browser().
        """
        if  s   :
            if  not isinstance(s, basestring) :
                s   = make_json(s)
            me.oq.put(s)
        pass


    def put_eval(me, jscript) :
        """
            Put some javescript to the browser to eval().
        """
        if  jscript :
            s   = { 'eval' : jscript }
            me.put(s)
        pass


    def put_func(me, fname, args) :
        """
            Put a call to the given javascript function, with arguments, if any.
        """

        if  fname :
            s   = { 'callback' : fname, 'arg' : args }
            me.put(s)
        else    :
            me.put_eval(args)
        pass


    #
    #
    #   The following logic allows the web server to put json pushed from the browser to a queue and to retrieve the json later from another thread.
    #
    #

    def received(me, json) :
        """
            The browser has sent the given json string (probably from json_to_server.js).
            Queue it for get() calls later.
        """

        dt      = unmake_json(json)
        if  dt  :
            me.iq.put(dt)
        pass


    def safe_received(me, json) :
        """
            The browser has sent the given json string (probably from json_to_server.js).
            Queue it for get() calls later.
            Don't do bad-json exceptions.
        """

        dt      = safe_unmake_json(json)
        if  dt  :
            me.iq.put(dt)
        pass


    def get(me, timeout = None) :
        """
            Optionally blocking, get whatever json structure the browser has sent to us.
            After call to stop(), can return None.
        """

        if  me.stop :
            return(None)

        return(tzlib.q_get(me.iq, timeout = timeout))



    def stop(me) :
        me._stop    = True
        me.oq.put(None)         # release blocking calls
        me.iq.put(None)

    #   a_json_browser



if  __name__ == '__main__' :
    me  = a_json_browser()
    me.stop()


#
#
#
# eof
