Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to manage multiple session whiteboard using tokens ? #1

Closed
dhanababu-nyros opened this issue Aug 16, 2013 · 5 comments
Closed

How to manage multiple session whiteboard using tokens ? #1

dhanababu-nyros opened this issue Aug 16, 2013 · 5 comments

Comments

@dhanababu-nyros
Copy link

I am using your collabdraw application. It is very nice app. I have successfully created the session urls. As shown below,
import logging
import json
import os
import threading
import subprocess
import uuid
from zlib import compress
from urllib.parse import quote
from base64 import b64encode
import tornado.websocket
import tornado.web
import redis
from pystacia import read

from tools import hexColorToRGB, createCairoContext
import config

class RealtimeHandler(tornado.websocket.WebSocketHandler):
room_name = ''
token = ''
paths = []
redis_client = None
page_no = 1
num_pages = 1

def construct_key(self, namespace, key, *keys):
    publish_key = ""
    if len(keys) == 0:
        publish_key = "%s:%s" % (namespace, key)
    else:
        publish_key = "%s:%s:%s" % (namespace, key, ":".join(keys))
    return publish_key

def redis_listener(self, room_name, page_no):
    self.logger.info("Starting listener thread for room %s" % room_name)
    rr = redis.Redis(host=config.REDIS_IP_ADDRESS, port=config.REDIS_PORT, db=1)
    r = rr.pubsub()
    r.subscribe(self.construct_key(room_name, page_no))
    for message in r.listen():
        for listener in self.application.LISTENERS.get(room_name, {}).get(page_no, []):
            self.logger.debug("Sending message to room %s" % room_name)
            listener.send_message(message['data'])

def open(self):
    self.logger = logging.getLogger('websocket')
    self.logger.info("Open connection")
    self.send_message(self.construct_message("ready"))
    self.redis_client = redis.Redis(host=config.REDIS_IP_ADDRESS, db=2)

def on_message(self, message):
    m = json.loads(message)
    event = m.get('event', '').strip()
    data = m.get('data', {})
    self.logger.debug("Processing event %s" % event)
    if not event:
        self.logger.error("No event specified")
        return

    if event == "init":
        self.logger.info("Initializing with room name %s" % self.room_name)
        room_name = data.get('room', '')
        if not room_name:
            self.logger.error("Room name not provided. Can't initialize")
            return
        page_no = data.get('page', '1')
        token = data.get('token', '')
        find_token = self.redis_client.exists(token)
        if find_token:
            token = ''
        self.init(room_name, page_no, token)

    elif event == "draw-click":
        singlePath = data['singlePath']
        if not self.paths:
            self.logger.debug("None")
            self.paths = []
        self.paths.extend(singlePath)
        self.broadcast_message(self.construct_message("draw", {'singlePath': singlePath}))
        if not self.token:
            self.redis_client.set(self.construct_key(self.room_name, self.page_no), self.paths)
        else:
            self.redis_client.set(self.construct_key('token', self.token), self.paths)

    elif event == "save":
        self.redis_client.set(self.construct_key(self.room_name, self.page_no), self.paths)
        self.init(self.room_name, self.page_no)

    elif event == "savesession":
        token = data['token']
        self.paths = []
        self.redis_client.set(self.construct_key('token', token), self.paths)
        self.send_message(self.construct_message("token", {'token': token}))
        #self.init(self.room_name, self.page_no)

    elif event == "clear":
        self.broadcast_message(self.construct_message("clear"))
        self.redis_client.delete(self.construct_key(self.room_name, self.page_no))

    elif event == "get-image":
        if self.room_name != data['room'] or self.page_no != data['page']:
            self.logger.warning("Room name %s and/or page no. %s doesn't match with current room name %s and/or page no. %s. Ignoring" % (data['room'],
                            data['page'], self.room_name, self.page_no))
        image_url, width, height = self.get_image_data(self.room_name, self.page_no)
        self.send_message(self.construct_message("image", {'url': image_url,
                                                            'width': width, 'height': height}))

    elif event == "video":
        self.make_video(self.room_name, self.page_no)

    elif event == "new-page":
        self.logger.info("num_pages was %d" % self.num_pages)
        self.redis_client.set(self.construct_key("info", self.room_name, "npages"),
                              self.num_pages + 1)
        self.num_pages += 1
        self.logger.info("num_pages is now %d" % self.num_pages)
        self.init(self.room_name, self.num_pages)

def on_close(self):
    self.leave_room(self.room_name)

def construct_message(self, event, data = {}):
    m = json.dumps({"event": event, "data": data})
    return m

def broadcast_message(self, message):
    if not self.token:
        self.leave_room(self.room_name, False)
        self.redis_client.publish(self.construct_key(self.room_name, self.page_no), message)
        self.join_room(self.room_name)
    else:
        print('fe................##########')
        self.leave_room('token', False)
        self.redis_client.publish(self.construct_key('token', self.token), message)
        self.join_room('token')
        self.application.LISTENERS.setdefault('token', {}).setdefault(self.token, []).append(self)

def send_message(self, message):
    if type(message) == type(b''):
        self.logger.info("Decoding binary string")
        message = message.decode('utf-8')
    elif type(message) != type(''):
        self.logger.info("Converting message from %s to %s" % (type(message),
                                                        type('')))
        message = str(message)
    message = b64encode(compress(bytes(quote(message), 'utf-8'), 9))
    self.write_message(message)

def leave_room(self, room_name, clear_paths = True):
    if not self.token:
        self.logger.info("Leaving room %s" % room_name)
        if self in self.application.LISTENERS.get(room_name, {}).get(self.page_no, []):
            self.application.LISTENERS[room_name][self.page_no].remove(self)
        if clear_paths:
            self.paths = []
    else:
        self.logger.info("Leaving room %s" % room_name)
        if self in self.application.LISTENERS.get(room_name, {}).get(self.token, []):
            self.application.LISTENERS[room_name][self.token].remove(self)
        if clear_paths:
            self.paths = []

def join_room(self, room_name):
    if not self.token:
        self.logger.info("Joining room %s" % room_name)
        self.application.LISTENERS.setdefault(room_name, {}).setdefault(self.page_no, []).append(self)
    else:
        self.logger.info("Joining room %s" % room_name)
        self.application.LISTENERS.setdefault(room_name, {}).setdefault(self.token, []).append(self)

def init(self, room_name, page_no, token):
    self.token = token
    if not token:
        self.logger.info("Initializing %s and %s" % (room_name, page_no))
        if room_name not in self.application.LISTENERS or page_no not in self.application.LISTENERS[room_name]:
            t = threading.Thread(target=self.redis_listener, args=(room_name, page_no))
            t.start()
            self.application.LISTENER_THREADS.setdefault(room_name, {}).setdefault(page_no, []).append(t)

        self.leave_room(self.room_name)
        self.room_name = room_name
        self.page_no = page_no
        self.join_room(self.room_name)

        n_pages = self.redis_client.get(self.construct_key("info", self.room_name, "npages"))
        if n_pages:
            self.num_pages = int(n_pages.decode('utf-8'))
        # First send the image if it exists
        image_url, width, height = self.get_image_data(self.room_name, self.page_no)
        self.send_message(self.construct_message("image", {'url': image_url,
                                                           'width': width, 'height': height}))
        # Then send the paths
        p = self.redis_client.get(self.construct_key(self.room_name, self.page_no))
        if p:
            self.paths = json.loads(p.decode('utf-8').replace("'", '"'))
        else:
            self.paths = []
            self.logger.info("No data in database")
        self.send_message(self.construct_message("draw-many",
                                                 {'datas': self.paths, 'npages': self.num_pages}))
    else:
        #if 'token' not in self.application.LISTENERS or token not in self.application.LISTENERS['token']:
        #    t = threading.Thread(target=self.redis_listener, args=('token', token))
        #    t.start()
        #    self.application.LISTENER_THREADS.setdefault('token', {}).setdefault(token, []).append(t)
        self.token = token
        self.leave_room('token', False)
        self.join_room('token')
        p = self.redis_client.get(self.construct_key('token', token))
        if p:
            self.paths = json.loads(p.decode('utf-8').replace("'", '"'))
        else:
            self.paths = []
            self.logger.info("No data in database")
        self.send_message(self.construct_message("draw-many",
                                                 {'datas': self.paths, 'npages': 0}))

def get_image_data(self, room_name, page_no):
    image_url = "files/" + room_name + "/" + str(page_no) + "_image.png";
    image_path = os.path.realpath(__file__).replace(__file__, '') + image_url
    try:
        image = read(image_path)
    except IOError as e:
        self.logger.error("Error %s while reading image at location %s" % (e, image_path))
        return '', -1, -1
    width, height = image.size
    return image_url, width, height

def make_video(self, room_name, page_no):
    p = self.redis_client.get(self.construct_key(room_name, page_no))
    os.makedirs('tmp', exist_ok=True)
    prefix = 'tmp/'+str(uuid.uuid4())
    if p:
        points = json.loads(p.decode('utf-8').replace("'",'"'))
        i = 0
        c = createCairoContext(920, 550)
        for point in points:
            c.set_line_width(float(point['lineWidth'].replace('px','')))
            c.set_source_rgb(*hexColorToRGB(point['lineColor']))
            if point['type'] == 'dragstart' or point['type'] == 'touchstart':
                c.move_to(point['oldx'], point['oldy'])
            elif point['type'] == 'drag' or point['type'] == 'touchmove':
                c.move_to(point['oldx'], point['oldy'])
                c.line_to(point['x'], point['y'])
            c.stroke()
            f = open(prefix+"_img_"+str(i)+".png", "wb")
            c.get_target().write_to_png(f)
            f.close()
            i += 1
        video_file_name = prefix+'_video.mp4'
        retval = subprocess.call(['ffmpeg', '-f', 'image2', '-i', prefix+'_img_%d.png', video_file_name])
        self.logger.info("Image for room %s and page %s successfully created. File name is %s" % (room_name, page_no, video_file_name))
        if retval == 0:
            # Clean up if successfull
            cleanup_files = prefix+'_img_*'
            self.logger.info("Cleaning up %s" % cleanup_files)
            subprocess.call(['rm', cleanup_files])

But I got the error when draw the whiteboard app. I am using url this way http://localhost:4000/test/ggxeu and error is,

Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.2/threading.py", line 740, in _bootstrap_inner
self.run()
File "/usr/lib/python3.2/threading.py", line 693, in run
self._target(_self._args, *_self._kwargs)
File "/home/nyros/Desktop/python3/collabdraw/websockethandler.py", line 43, in redis_listener
listener.send_message(message['data'])
File "/home/nyros/Desktop/python3/collabdraw/websockethandler.py", line 147, in send_message
self.write_message(message)
File "/home/nyros/Desktop/python3/venv3/lib/python3.2/site-packages/tornado/websocket.py", line 165, in write_message
self.ws_connection.write_message(message, binary=binary)
AttributeError: 'NoneType' object has no attribute 'write_message'

Please solve my problem.

@anandtrex
Copy link
Owner

What version of tornado do you have running?
Also can you attach your main.py if you've made any changes to it? It'll help me debug this issue.

@dhanababu-nyros
Copy link
Author

Hi Thanks for reply,
I am using Torndao-3.1. I have changed the two files one is websockethandler.py and another one is main.py.

Please find code here for main.py:

import logging
import uuid

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.template as template

from websockethandler import RealtimeHandler
from uploadhandler import UploadHandler
from loginhandler import LoginHandler
from logouthandler import LogoutHandler
from registerhandler import RegisterHandler
import config

logger = logging.getLogger('websocket')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)

class IndexHandler(tornado.web.RequestHandler):
def get_current_user(self):
if not config.DEMO_MODE:
return self.get_secure_cookie("loginId")
else:
return True

@tornado.web.authenticated
def get(self):
    #print('...........authenticated.......')
    loader = template.Loader(config.ROOT_DIR)
    return_str = loader.load("index.html").generate(app_ip_address=config.APP_IP_ADDRESS,
                                                    app_port=config.PUBLIC_LISTEN_PORT, token='')
    self.finish(return_str)

class RedirectHandler(tornado.web.RequestHandler):
def get(self, token_id):
loader = template.Loader(config.ROOT_DIR)
return_str = loader.load("index.html").generate(app_ip_address=config.APP_IP_ADDRESS,
app_port=config.PUBLIC_LISTEN_PORT, token=token_id)
self.finish(return_str)

class Application(tornado.web.Application):
def init(self):
handlers = [
(r'/realtime/', RealtimeHandler),
(r'/resource/(.)', tornado.web.StaticFileHandler,
dict(path=config.RESOURCE_DIR)),
(r'/test/resource/(.
)', tornado.web.StaticFileHandler,
dict(path=config.RESOURCE_DIR)),
(r'/upload', UploadHandler),
(r'/login.html', LoginHandler),
(r'/logout.html', LogoutHandler),
(r'/register.html', RegisterHandler),
(r'/index.html', IndexHandler),
(r'/test/(?P<token_id>\w+)', RedirectHandler),
(r'/(.*)', tornado.web.StaticFileHandler,
dict(path=config.ROOT_DIR)),
]

    self.LISTENERS = {}
    self.LISTENER_THREADS = {}

    settings = dict(
        auto_reload = True,
        gzip = True,
        login_url = "login.html",
        cookie_secret = str(uuid.uuid4()),
    )

    tornado.web.Application.__init__(self, handlers, **settings)

if name == "main":
if not config.ENABLE_SSL:
http_server = tornado.httpserver.HTTPServer(Application())
else:
http_server = tornado.httpserver.HTTPServer(Application(), ssl_options={
"certfile": config.SERVER_CERT,
"keyfile": config.SERVER_KEY,
})
logger.info("Listening on port %s" % config.APP_PORT)
http_server.listen(config.APP_PORT)
tornado.ioloop.IOLoop.instance().start()

Here for websockethandler.py:

import logging
import json
import os
import threading
import subprocess
import uuid
from zlib import compress
from urllib.parse import quote
from base64 import b64encode
import tornado.websocket
import tornado.web
import redis
from pystacia import read

from tools import hexColorToRGB, createCairoContext
import config

class RealtimeHandler(tornado.websocket.WebSocketHandler):
room_name = ''
token = ''
paths = []
redis_client = None
page_no = 1
num_pages = 1

def construct_key(self, namespace, key, *keys):
    publish_key = ""
    if len(keys) == 0:
        publish_key = "%s:%s" % (namespace, key)
    else:
        publish_key = "%s:%s:%s" % (namespace, key, ":".join(keys))
    return publish_key

def redis_listener(self, room_name, page_no):
    self.logger.info("Starting listener thread for room %s" % room_name)
    rr = redis.Redis(host=config.REDIS_IP_ADDRESS, port=config.REDIS_PORT, db=1)
    r = rr.pubsub()
    r.subscribe(self.construct_key(room_name, page_no))
    for message in r.listen():
        for listener in self.application.LISTENERS.get(room_name, {}).get(page_no, []):
            self.logger.debug("Sending message to room %s" % room_name)
            listener.send_message(message['data'])

def open(self):
    self.logger = logging.getLogger('websocket')
    self.logger.info("Open connection")
    self.send_message(self.construct_message("ready"))
    self.redis_client = redis.Redis(host=config.REDIS_IP_ADDRESS, db=2)

def on_message(self, message):
    m = json.loads(message)
    event = m.get('event', '').strip()
    data = m.get('data', {})
    self.logger.debug("Processing event %s" % event)
    if not event:
        self.logger.error("No event specified")
        return

    if event == "init":
        token = data.get('token', '')
        find_token = self.redis_client.exists(self.construct_key('token', token))
        print(token, find_token)
        room_name = data.get('room', '')
        page_no = data.get('page', '1')
        if token:
            if not find_token:
                self.send_message(self.construct_message("forword", {}))
            else:
                room_name = 'token'
                self.token = token
                page_no = token
        else:
            self.token = ''
            print('.........................')
            self.logger.info("Initializing with room name %s" % self.room_name)
            room_name = data.get('room', '')
            if not room_name:
                self.logger.error("Room name not provided. Can't initialize")
                return
        self.init(room_name, page_no)

    elif event == "draw-click":
        singlePath = data['singlePath']
        if not self.paths:
            self.logger.debug("None")
            self.paths = []
        self.paths.extend(singlePath)
        self.broadcast_message(self.construct_message("draw", {'singlePath': singlePath}))
        if not self.token:
            self.redis_client.set(self.construct_key(self.room_name, self.page_no), self.paths)
        else:
            self.redis_client.set(self.construct_key('token', self.token), self.paths)

    elif event == "save":
        self.redis_client.set(self.construct_key(self.room_name, self.page_no), self.paths)
        self.init(self.room_name, self.page_no)

    elif event == "savesession":
        token = data['token']
        self.paths = []
        self.redis_client.set(self.construct_key('token', token), self.paths)
        self.send_message(self.construct_message("token", {'token': token}))
        #self.init(self.room_name, self.page_no)

    elif event == "clear":
        self.broadcast_message(self.construct_message("clear"))
        self.redis_client.delete(self.construct_key(self.room_name, self.page_no))

    elif event == "get-image":
        if self.room_name != data['room'] or self.page_no != data['page']:
            self.logger.warning("Room name %s and/or page no. %s doesn't match with current room name %s and/or page no. %s. Ignoring" % (data['room'],
                            data['page'], self.room_name, self.page_no))
        image_url, width, height = self.get_image_data(self.room_name, self.page_no)
        self.send_message(self.construct_message("image", {'url': image_url,
                                                            'width': width, 'height': height}))

    elif event == "video":
        self.make_video(self.room_name, self.page_no)

    elif event == "new-page":
        self.logger.info("num_pages was %d" % self.num_pages)
        self.redis_client.set(self.construct_key("info", self.room_name, "npages"),
                              self.num_pages + 1)
        self.num_pages += 1
        self.logger.info("num_pages is now %d" % self.num_pages)
        self.init(self.room_name, self.num_pages)

def on_close(self):
    self.leave_room(self.room_name)

def construct_message(self, event, data = {}):
    m = json.dumps({"event": event, "data": data})
    return m

def broadcast_message(self, message):
    self.leave_room(self.room_name, False)
    self.redis_client.publish(self.construct_key(self.room_name, self.page_no), message)
    self.join_room(self.room_name)

def send_message(self, message):
    if type(message) == type(b''):
        self.logger.info("Decoding binary string")
        message = message.decode('utf-8')
    elif type(message) != type(''):
        self.logger.info("Converting message from %s to %s" % (type(message),
                                                        type('')))
        message = str(message)
    message = b64encode(compress(bytes(quote(message), 'utf-8'), 9))
    self.write_message(message)

def leave_room(self, room_name, clear_paths = True):

    self.logger.info("Leaving room %s" % room_name)
    if self in self.application.LISTENERS.get(room_name, {}).get(self.page_no, []):
        self.application.LISTENERS[room_name][self.page_no].remove(self)
    if clear_paths:
        self.paths = []

def join_room(self, room_name):
    self.logger.info("Joining room %s" % room_name)
    self.application.LISTENERS.setdefault(room_name, {}).setdefault(self.page_no, []).append(self)

def init(self, room_name, page_no):
    self.logger.info("Initializing %s and %s" % (room_name, page_no))
    if room_name not in self.application.LISTENERS or page_no not in self.application.LISTENERS[room_name]:
        t = threading.Thread(target=self.redis_listener, args=(room_name, page_no))
        t.start()
        print("find ....", t)
        self.application.LISTENER_THREADS.setdefault(room_name, {}).setdefault(page_no, []).append(t)

    self.leave_room(self.room_name)
    self.room_name = room_name
    self.page_no = page_no
    self.join_room(self.room_name)

    n_pages = self.redis_client.get(self.construct_key("info", self.room_name, "npages"))
    if n_pages:
        self.num_pages = int(n_pages.decode('utf-8'))
    # First send the image if it exists
    image_url, width, height = self.get_image_data(self.room_name, self.page_no)
    self.send_message(self.construct_message("image", {'url': image_url,
                                                       'width': width, 'height': height}))
    # Then send the paths
    p = self.redis_client.get(self.construct_key(self.room_name, self.page_no))
    if p:
        self.paths = json.loads(p.decode('utf-8').replace("'", '"'))
    else:
        self.paths = []
        self.logger.info("No data in database")
    self.send_message(self.construct_message("draw-many",
                                             {'datas': self.paths, 'npages': self.num_pages}))

def get_image_data(self, room_name, page_no):
    image_url = "files/" + room_name + "/" + str(page_no) + "_image.png";
    image_path = os.path.realpath(__file__).replace(__file__, '') + image_url
    try:
        image = read(image_path)
    except IOError as e:
        self.logger.error("Error %s while reading image at location %s" % (e, image_path))
        return '', -1, -1
    width, height = image.size
    return image_url, width, height

def make_video(self, room_name, page_no):
    p = self.redis_client.get(self.construct_key(room_name, page_no))
    os.makedirs('tmp', exist_ok=True)
    prefix = 'tmp/'+str(uuid.uuid4())
    if p:
        points = json.loads(p.decode('utf-8').replace("'",'"'))
        i = 0
        c = createCairoContext(920, 550)
        for point in points:
            c.set_line_width(float(point['lineWidth'].replace('px','')))
            c.set_source_rgb(*hexColorToRGB(point['lineColor']))
            if point['type'] == 'dragstart' or point['type'] == 'touchstart':
                c.move_to(point['oldx'], point['oldy'])
            elif point['type'] == 'drag' or point['type'] == 'touchmove':
                c.move_to(point['oldx'], point['oldy'])
                c.line_to(point['x'], point['y'])
            c.stroke()
            f = open(prefix+"_img_"+str(i)+".png", "wb")
            c.get_target().write_to_png(f)
            f.close()
            i += 1
        video_file_name = prefix+'_video.mp4'
        retval = subprocess.call(['ffmpeg', '-f', 'image2', '-i', prefix+'_img_%d.png', video_file_name])
        self.logger.info("Image for room %s and page %s successfully created. File name is %s" % (room_name, page_no, video_file_name))
        if retval == 0:
            # Clean up if successfull
            cleanup_files = prefix+'_img_*'
            self.logger.info("Cleaning up %s" % cleanup_files)
            subprocess.call(['rm', cleanup_files])

May be I think I solved this issue. But few issues are there. When the user first time draws on whiteboard it is not saved into database. I am expecting feedback from you. Thanks.

@anandtrex
Copy link
Owner

I'm looking into it.

@dhanababu-nyros
Copy link
Author

Ok thanks for your supporting. I am looking forward for your reply.

@anandtrex
Copy link
Owner

I can't seem to reproduce the issue of the first time drawing not being saved in database. Please open a new issue with more details if you're still seeing the problem.

For the record, can you post your solution to your first problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants