creating clients 1: time service and user

a simple example for creating SVS clients.

introduction
overview of this tutorial

a simple service client
example of a client that manages data on a network, such as the 'game' client in svs_demogame

a simple user client
example of a client that enables users to access and work with data, such as the 'player' client in svs_demogame

running the examples
instructions for running the example applications covered in this tutorial

introduction

This tutorial provides examples of how the network classes of SVS can be extended to create new client-based applications with SVS. The examples are based around a simple network demo in which a 'service' client provides information for 'user' clients. In this example the service client provides two kinds of simple information: the time of day, and a listing of how many times each user client has requested the time.

It is recommended to consult the source code of the examples whilst reading through the tutorial:

The source code for all tutorials can be downloaded with the tutorials-x.x.tar.gz package.

page contents

a simple service client

source: timeservice.py

Service clients provide and manage data for other clients on a network. In most cases they will run as command-line utilities with no GUI. The server client described here is a simple one which returns the current time on request from another client.

The TimeService client extends the GenericClient from svs_core.network.clientuser. It adds two new methods and extends the getDataForLabel method of GenericClient. When creating any client that provides data for other clients on an SVS network, the getDataForLabel method is the main one you will need to extend as this is where requests for specific data are first handled.

Several modules from svs_core are required:

from svs_core.commands.scriptcommands import *
from svs_core.network.clientuser import GenericClient
from svs_core.network.packets import *
from svs_core.utilities.constants import svs_const

Example 1.) svs_core imports for timeservice.

The time module is also imported for the custom functionality of the service:

import time

Example 2.) time imports for timeservice.

The TimeService class extends GenericClient and adds a dictionary for storing the log of client requests:

class TimeService(GenericClient):
    def __init__(self, name, passwd):
        GenericClient.__init__(self, name, passwd)
        self.requests = {}

Example 3.) initialisation of TimeService.

Methods are added to provide the service's custom functionality. One method returns the current time of day, the other a log of all client requests for the time:

def getTime(self, requester):
    """
    Returns current time.

    The time is presented as a tuple in the form:
    (year, month, day, hour, minute, second, weekday,
    yearday, daylightSavingAdjustment)
    """
    if self.requests.has_key(requester):
        self.requests[requester] += 1
    else:self.requests[requester] = 1
    timenow = time.localtime()[:-2]
    return makeDataPacket(self.profile.name, content=timenow, label='time')


def getTimeLog(self):
    """
    Returns log of those who have asked for the time.
    """
    return makeDataPacket(self.profile.name, content=self.requests, label='time_log'))

Example 4.) custom methods for TimeService.

When getTime is called, the method updates the number of times the requesting client has called that function. It checks if the client is already listed in the log, and adds it in if not. In both methods the result is returned inside a data packet using the makeDataPacket function from the svs_core.network.packets module.

In order to respond to network requests, the TimeService class overides the getDataForLabel from GenericClient. It tests all incoming packets for the labels matching the requested data. If these are not matched the method calls back to its parent version so it can handle any packets defined within it:

def getDataForLabel(self, dataRequest):
    """
    Provides custom handling of data requests.

    This should be overidden by extending classes.
    """
    if not dataRequest.label:
        return GenericClient.getDataForLabel(dataRequest)
    # get time
    if dataRequest.label == 'time':
        return self.getTime(dataRequest.sender)
    # get time log
    if dataRequest.label == 'time_log':
        return self.getTimeLog()
    # pass to parent method
    GenericClient.getDataForLabel(dataRequest)

Example 5.) overiding getDataForLabel.

This is all that is required for implementing a client that provides data on an SVS network. The script ends with some code to launch a client from the command-line:

if __name__ == '__main__':
    timeservice = TimeService("time_service", "p@ssw0rd")
    timeservice.connect("time_group", "localhost", 9797)

Example 6.) overiding getDataForLabel.

This can be adapted to handle any arguments from the command-line, as shown in the user client example below.

page contents

a simple user client

source: timeuser.py

A user client provides a tool for users to access and work with data on a network. This client operates with the service client described above. This client is derived from the GraphicalClient found in the svs_core.network.clientuser.py module. Two commands have been added to it: 'time' - to request the current time from the service client, and 'timelog' - to get a listing of how many times each user client has requested the time.

The TimeUser is developed along similar lines as the TimeService but must also handle user input. The basic functionality for this is provided by the GraphicalClient that it extends from.

A series of imported modules are required similar to those of the time service:

from svs_core.commands.scriptcommands import *
from svs_core.network.clientuser import GraphicalClient
from svs_core.network.packets import *
from svs_core.utilities.constants import svs_const

Example 7.) imports for graphical time user.

It extends GraphicalClient and adds an extra property for storing the name of the service it will be interacting with:

class TimeUser(GraphicalClient):
    def __init__(self, name, passwd, guiClass=None):
        GraphicalClient.__init__(self, name, passwd, guiClass=guiClass)
        self.timeService = None

Example 8.) initialising the time user.

Four methods are provided for the custom functionality of the user. The getTime and getTimeLog are called by the user to make requests to the service. The handleTime and handleTimeLog methods deal with the responses sent back by the service:

def getTime(self):
    """
    Requests time from tiem service.
    """
    dataRequest = makeDataRequest(self, recipient=self.timeService, label='time')
    self.getData(dataRequest)


def handleTime(self, timeData):
    """
    Displays time returned from service.
    """
    dateStr = "date: %s, %d/%d/%d" % (daysOfWeek[timeData[6]], timeData[2], timeData[1], timeData[0])
    self.statusMessage(dateStr)
    timeStr = "time: %d:%d:%d" % (timeData[3], timeData[4], timeData[5])
    self.statusMessage(timeStr)


def getTimeLog(self):
    """
    Requests time log from time service.
    """
    dataRequest = makeDataRequest(self, recipient=self.timeService, label='time_log')
    self.getData(dataRequest)


def handleTimeLog(self, logData):
    """
    Displays log returned from service.
    """
    lStr = "time log:\n"
    clients = logData.keys()
    clients.sort()
    for clientname in clients:
        lStr += " %s: %d\n" % (clientname, logData[clientname])
    self.statusMessage(lStr)

Example 9.) custom methods for time user.

The requests to the service are made by creating and sending data request packets. The responses are received by the handleDataPacket method. This overides the method originally defined in GenericClient. As with the getDataForLabel method of the time service, this matches for labels on the incoming data packets and then triggers handler methods as appropriate:

def handleDataPacket(self, dataPacket):
    """
    Handles data packet received from network.

    This should be overidden by extending classes.
    """
    if not dataPacket:return
    dataLabel = dataPacket.label
    if not dataLabel:return
    # time
    if dataLabel == 'time':
        self.handleTime(dataPacket.content)
    # timelog
    if dataLabel == 'time_log':
        self.handleTimeLog(dataPacket.content)
    # client joined network
    elif dataLabel == 'client_joined':
        self.handleClientJoined(dataPacket.content)
    # client departed network
    elif dataLabel == 'client_departed':
        self.handleClientDeparted(dataPacket.content)
    else:
        try:self.logMessage("data packet received: <%s>" % dataPacket.content)
        except:pass

Example 10.) implementation of the handleDataPacket method.

Packets with the 'time' and 'time_log' labels trigger the corresponding handleTime and handleTimeLog methods. Two other packet labels are handled: 'client_joined' and 'client_departed'. These handle packets that are automatically sent out to all clients by the server, notifying when a client has newly joined the network or when an existing client has left it. Clients can determine how they handle these, or can just ignore them. The time user handles them by printing out messages in its console:

def handleClientJoined(self, data):
    """
    Responds to new client joining the network.
    """
    self.statusMessage("'%s' has joined '%s'." % (data['client_name'], data['client_group']))


def handleClientDeparted(self, data):
    """
    Responds to new client leaving the network.
    """
    self.statusMessage("'%s' has left '%s'." % (data['client_name'], data['client_group']))

Example 11.) methods handling messages about clients joining and leaving the network.

The remaining methods of the time user provide an interface to the scripting capabilities of the client. The method signatures follow those outlined in the client shell commands tutorial. These respond to shell commands 'time' and 'timelog' entered by a user and trigger the corresponding methods outlined above:

def cmdprivate_time(self, command):
    """
    Gets current time from service.
    """
    self.getTime()
    return makeCommandResult(command, message="requesting time ...", status=svs_const.OK)


def cmdprivate_timelog(self, command):
    """
    Gets log of those who have requested time from service.
    """
    self.getTimeLog()
    return makeCommandResult(command, message="requesting time log ...", status=svs_const.OK)

Example 12.) shell command methods.

The time user can be launched from the command line. It accepts two arguments defining the user name and password so that multiple clients can be launched from the same script:

if __name__ == '__main__':
    import sys
    timeuser = TimeUser(sys.argv[1], sys.argv[2])
    timeuser.timeService = 'time_service'
    timeuser.connect("time_group", "localhost", 9797)

Example 13.) handling command line arguments for time user.

page contents

running the examples

Working versions of the examples are in the 'creating_clients' directory of the 'tutorials' package.

Step 1.) start the server:

./server.py

This will run as a command line application with no user interface.

Step 2.) in a new terminal, start the service client:

./timeservice.py

This will run as a command line application with no user interface.

Step 3.) in a new terminal, start a user client:

./timeuser.py time_user_1 p@ssw0rd

To start a second user, again in a new terminal:

./timeuser.py time_user_2 p@ssw0rd

Additional clients can be started in the same way, just by increasing the number in the name ('time_user_3', 'time_user_4', etc). The server file is configured to accept up to 6 such clients.

Step 4.) when the user client has launched, enter the 'time' command. The result from the service will be displayed in the console:


fig. 1.) using the 'time' command.

Do the same from the other user client. Make several requests.

Step 5.) to see a listing of how many times each client has requested the time, enter the 'timelog' command:


fig. 1.) using the 'timelog' command.

Step 6.) enter 'quit' to close each client and disconnect from the network. Enter 'control-c' in the terminal to shutdown the time service and the server.

page contents

creating SVS clients
part 1: time service and user
part 2: interface components
part 3: notification system