RemoteControl.py :  » Network » Grail-Internet-Browser » grail-0.6 » ancillary » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Network » Grail Internet Browser 
Grail Internet Browser » grail 0.6 » ancillary » RemoteControl.py
"""Remote control of Grail.

NOTE NOTE NOTE: See <http://monty.cnri.reston.va.us/grail-0.3/help/rc.html>
for details on this interface.

On Unix systems with the DISPLAY environment variable set, the socket
file name will typically be /tmp/.grail-unix/$USER-$DISPLAY, and the
directory will be protected with mode 0700.  The DISPLAY value will be
normalized to `socket.gethostname():<DISPNUM>.<SCREENNUM>'.

You can also define what file to use by setting the environment
variable GRAIL_REMOTE.  TBD: this should also be made a preference.

This module essentially opens the socket and registers it with Tk so
when data is readable on it, registered callbacks are executed.  This
is a really simple minded string based protocol, with a synchronous
server model.  Commands are limited to 1024 bytes.

TBD: Port this to non-Unix systems, use CCI and ILU.

Exported functions:

start()
        starts the remote controller

stop()
        stops the remote controller

register(cmdstr, callback)
        registers a callback function for handling cmdstr commands.
        The callback has the following form:

        callback(cmdstr, cmdargs)

        where cmdargs is the remainder of the string read from the
        socket.  Note that you can register more than one callback for
        a particular command string; they are invoked in the order
        they were registered.

unregister(cmdstr, callback=None)
        unregisters the callback for the given command string.  If
        callback is not specified, all callbacks for the cmdstr are
        unregistered.

register_loads()
        registers a callback for the command string 'LOAD' which loads
        a given URL into the latest browser window.  The URL to load
        is the cmdargs for the command.  This also registers the
        'LOADNEW' command string which pops up a new browser and loads
        the URL into that browser window.

unregister_loads()
        unregisters the built-in LOAD and LOADNEW callbacks.


Exported exceptions:

InitError       - the socket couldn't be initialized
ClashError      - another Grail is already being remote controlled
BadCommandError - a badly formatted command was received
NoHandlerError  - no handler for the received command has been registered


Example (put this in your grailrc.py file):

# Turn on remote control.  Ignore error that get's raised if some
# other Grail is being remote controlled.
import RemoteControl
RemoteControl.register_loads()
try:
    RemoteControl.start()
except RemoteControl.ClashError:
    pass

"""


# errors
InitError = 'RemoteControl.InitError'
ClashError = 'RemoteControl.ClashError'
BadCommandError = 'RemoteControl.BadCommandError'
NoHandlerError = 'RemoteControl.NoHandlerError'

_controller = None
_filename = None
_loads_registered = None

def _create():
    global _controller
    if not _controller:
        _controller = Controller()

def start():
    _create()
    _controller.start()

def stop():
    if _controller:
        _controller.stop()

def register(cmdstr, callback):
    _create()
    _controller.register(cmdstr, callback)

def unregister(cmdstr, callback):
    _create()
    _controller.unregister(cmdstr, callback)

def register_loads():
    _create()
    global _loads_registered
    if not _loads_registered:
        _controller.register('LOAD', _controller.load_cmd)
        _controller.register('LOADNEW', _controller.load_new_cmd)
        _loads_registered = 1

def unregister_loads():
    global _loads_registered
    if _loads_registered:
        _controller.unregister('LOAD', _controller.load_cmd)
        _controller.unregister('LOADNEW', _controller.load_new_cmd)
        _loads_registered = None



import tempfile
import os
import socket
import regex
import string
from Tkinter import tkinter
from grailutil import *

# The file structure.  Modeled after X11
_filename = getenv('GRAIL_REMOTE')
if not _filename:
    TMPDIR = tempfile.gettempdir()
    USER = getenv('USER') or getenv('LOGNAME')
    XDISPLAY = getenv('DISPLAY') or ':0'
    # normalize the display name
    cre = regex.compile('\([^:]+\)?:\([0-9]+\)\(\.\([0-9]+\)\)?')
    if cre.match(XDISPLAY):
        host, display, screen = cre.group(1, 2, 4)
        if not host:
            host = socket.gethostname()
        if not screen:
            screen = '0'
        XDISPLAY = '%s:%s.%s' % (host, display, screen)
    _filename = os.path.join(TMPDIR,
                             os.path.join('.grail-unix',
                                          '%s-%s' % (USER, XDISPLAY)))


class Controller:
    def __init__(self, path=_filename):
        # register a destruction handler with the Grail Application object
        app = get_grailapp()
        self._app = app
        app.register_on_exit(self._close)
        # calculate the socket's filename
        self._path = path
        self._fileno = None
        self._socket = None
        self._enabled = None
        # Don't create the socket now, because we want to allow
        # clients of this class to register callbacks for commands
        # first.
        self._cbdict = {}
        self._cmdre = regex.compile('\([^ \t]+\)\(.*\)')

    def start(self):
        """Begin listening for remote control commands."""
        # initialize the socket
        if self._fileno is None:
            # for security, create the file structure
            head, self._filename = os.path.split(self._path)
            dirhier = []
            while head and not os.path.isdir(head):
                head, t = os.path.split(head)
                dirhier.insert(0, t)
            for dir in dirhier:
                head = os.path.join(head, dir)
                os.mkdir(head, 0700)
            self._filename = self._path
            # TBD: What do we do with multiple Grail processes?  Which
            # one do we remote control?
            if os.path.exists(self._filename):
                # first make sure that the socket is connected to a
                # live Grail process.  E.g. if Python core dumped, the
                # exit handler won't run so you'd be dead in the
                # water.
                s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                try:
                    s.connect(self._filename)
                    s.send('PING NOACK')
                    s.close()
                    raise ClashError
                except socket.error, (errno, msg):
                    os.unlink(self._filename)
                    s.close()
            # create the FIFO object
            s = self._socket = socket.socket(socket.AF_UNIX,
                                             socket.SOCK_STREAM)
            s.bind(self._filename)
            s.listen(1)
            # register with Tk
            self._fileno = s.fileno()
            if self._fileno < 0:
                self._fileno = None
                raise InitError
        if not self._enabled:
            self._enabled = 1
            tkinter.createfilehandler(
                self._fileno, tkinter.READABLE, self._dispatch)
            self.register('PING', self.ping_cmd)

    def stop(self):
        """Stop listening for remote control commands."""
        if self._enabled:
            self._enabled = None
            tkinter.deletefilehandler(self._fileno)

    def register(self, cmdstr, callback):
        """Register command string, callback function pairs.

        Command format is 'CMDSTR ARGS' where CMDSTR is some command
        string as defined by the client of this class.  ARGS is
        anything that follows, and is command specific.

        Format of the callback method is: callback(cmdstr, argstr).
        More than one callback can be defined for a command, and they
        are called in the order they are registered in.  Note that any
        exceptions raised in the callback are passed straight up
        through to Grail.
        """
        if self._cbdict.has_key(cmdstr):
            cblist = self._cbdict[cmdstr]
            cblist.append(callback)
        else:
            self._cbdict[cmdstr] = [callback]

    def unregister(self, cmdstr, callback=None):
        """Unregister a command string, callback mapping.

        If callback is None (the default), this unregisters all
        callbacks associated with a command.
        """
        if self._cbdict.has_key(cmdstr):
            cblist = self._cbdict[cmdstr]
            if callback and callback in cblist:
                cblist.remove(callback)
            else:
                del self._cbdict[cmdstr]

    # private methods

    def _close(self):
        self.stop()
        if self._filename:
            try:
                os.unlink(self._filename)
            except os.error:
                pass

    def _dispatch(self, *args):
        conn, addr = self._socket.accept()
        rawdata = conn.recv(1024)
        # strip off the command string
        string.strip(rawdata)
        if self._cmdre.match(rawdata) < 0:
            print 'Remote Control: Ignoring badly formatted command:', rawdata
            return
        # extract the command and args strings
        command = string.strip(self._cmdre.group(1))
        argstr = string.strip(self._cmdre.group(2))
        # look up the command string
        if not self._cbdict.has_key(command):
            print 'Remote Control: Ignoring unrecognized command:', command
            return
        cblist = self._cbdict[command]
        # call all callbacks in list
        for cb in cblist:
            cb(command, argstr, conn)

    # convenience methods

    def _do_load(self, uri, in_new_window=None):
        target = ""
        def _new_browser(b):
            from Browser import Browser
            return Browser(b.master)

        if " " in uri:
            [uri, target] = string.split(uri)

        browsers = self._app.browsers[:]
        if not len(browsers):
            return

        b = None
        if not in_new_window:
            browsers.reverse()
            # find the last browser who's context is not showing
            # source
            for b in browsers:
                if not b.context.show_source:
                    break
            else:
                b = None
        if not b:
            b = _new_browser(browsers[-1])
        b.context.load(uri, target=target)

    def load_cmd(self, cmdstr, argstr, conn):
        self._do_load(argstr)

    def load_new_cmd(self, cmdstr, argstr, conn):
        self._do_load(argstr, in_new_window=1)

    def ping_cmd(self, cmdstr, argstr, conn):
        try:
            if argstr <> 'NOACK':
                conn.send('ACK')
        except socket.error:
            print 'RemoteControl: unable to acknowledge PING'
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.