# ScopeConnection.py : Class that handles the microscope connection
#
# Distributed under the terms described in the LICENCE file.
#
# Developed by akuchlin@mems-exchange.org
#
# Class that implements the microscope protocol and handles the
# connection.  If we ever seriously modify the protocol, or switch to
# CORBA, HTTP, or some other thing, this should be the only class that 
# needs to be modified or rewritten.
#
# XXX this code should really broadcast its events in some fashion,
# instead of registering a bunch of handler objects.  While the
# current implementation is easy in Python, it's less natural in Java.
#
# XXX this code doesn't really guard against a malicious *server*;
# should it?

__revision__ = "$Id: ScopeConnection.py,v 1.21.2.7 2002/03/13 17:18:27 akuchlin Exp $"

import socket, string, sys, time

from java.lang import Thread
from java.io import IOException
from java.net import SocketException

# XXX Imported locally; you'll have to create a symlink from the local
# directory to ~/src/mems/proto3/instrument/protocols.py.
import protocols

DEBUG = 0                           # Display debugging information
TIMING = 0                             # Display timing information

class ScopeConnection:
    def __init__ (self, JavaSocket, image_JavaSocket, client,
                 authtoken, try_reconnect = 1):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock._setup(JavaSocket)
        self.image_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.image_sock._setup(image_JavaSocket)
        self.client = client
        self.authtoken = authtoken
        self.try_reconnect = try_reconnect
        self.handlers = []

        self._control_thread = None
        self._image_thread = None

        # Send authorization token to both sockets
        self.send_message('auth value=' + self.authtoken)
        self.send_message('auth value=' + self.authtoken, self.image_sock)

        # Inform the server of the port for images
        local_ip, port = self.image_sock.getsockname()
        self.send_message('image_socket port=%i' % port)

        # Set the number of frames to 1, which is about all that Java
        # can handle.
        self.send_message('frames num_frames=1')


    def close (self):
        # Stop the threads
        if self._control_thread:
            self._control_thread.stop()
            self._control_thread = None
        if self._image_thread:
            self._image_thread.stop()
            self._image_thread = None

        # Close the sockets
        self.sock.close()
        self.sock = None
        self.image_sock.close()
        self.image_sock = None
        
    def send_message (self, message, sock=None):
        if sock is None: sock = self.sock
        
        if DEBUG:
            print 'ScopeConnection: Sending:', message
        try:
            sock.send(string.strip(message) + '\n')
        except (IOException, SocketException):
            if self.try_reconnect:
                self.client.message("Connection broken; re-connnecting")
                time.sleep(2)
                self.client.make_connection()
                self.sock = self.image_sock = None

                # This ScopeConnection object should have been replaced by
                # a fresh one, so simply return; the new object will deal with its
                # own problems.
                return
            else:
                return

    def start (self):
        self._control_thread = ControlThread(self)
        self._control_thread.start()
        self._image_thread = ImageThread(self)
        self._image_thread.start()

    def handle_message (self, message):
        cmd, args = protocols.parse_message_line(message)
        method_name = "cmd_" + string.lower(cmd)
        for obj in self.handlers:
            if hasattr(obj, method_name):
                meth = getattr(obj, method_name)
                if TIMING:
                    now = time.time()
                apply(meth, (self.sock,), args)
                if TIMING:
                    print 'Time for command', method_name, time.time()-now
                break
        else:
            sys.stderr.write('Unknown command: '+repr(cmd)+'\n')

    def update_image (self, image_data):
        self.client.image_disp.image_data = image_data


class ControlThread(Thread):
    def __init__ (self, conn):
        self.conn = conn
        
    def run (self):
        conn = self.conn
        while (1):

            line = ""
            if TIMING:
                linestart = time.time()

            while (1):
                c = conn.sock.recv(1)
                # If the socket object is None, it must have been closed
                # (probably by the Client.quit() method).  Silently exit
                # when that happens.
                if conn.sock is None: return

                if c == "":
                    if conn.try_reconnect:
                        conn.client.reconnect()
                        conn.sock = None
                    
                    # This ScopeConnection object should have been
                    # replaced by a fresh one, so simply return; the
                    # new object will deal with its own problems.
                    return
                
                elif c in '\n\r':
                    if line != "": break
                else:
                    line = line + c

            if DEBUG:
                print "ScopeConnection", id(conn), ": line received:", line ; i=i+1
            if TIMING:
                now = time.time()
                print 'Exiting line-read loop:', now - linestart

            conn.handle_message(line)

class ImageThread(Thread):
    def __init__ (self, conn):
        self.conn = conn
        
    def run (self):
        conn = self.conn
        codec = None
        while 1:
            # Disassemble the packet
            try:
                fmt, data, keywords = protocols.read_packet(conn.image_sock)
            except (IOException, SocketException):
                # If the server has crashed, then the ScopeConnection object
                # will handle re-connecting, so this code will just
                # exit quietly. 
                return
            
            if conn.image_sock is None:
                return

            assert fmt == 'null'
            conn.update_image(data)
            
        
