Table of Contents Previous Next
Logo
Developing a File System Server in Python : 21.2 Implementing a File System Server
Copyright © 2003-2008 ZeroC, Inc.

21.2 Implementing a File System Server

We have now seen enough of the server-side Python mapping to implement a server for the file system we developed in Chapter 5. (You may find it useful to review the Slice definition for our file system in Section 5 before studying the source code.)
Our server is implemented in a single source file, Server.py, containing our server’s main program as well as the definitions of our Directory and File servant subclasses.

21.2.1 The Server Main Program

Our server main program uses the Ice.Application class we discussed in Section 20.3.1. The run method installs a signal handler, creates an object adapter, instantiates a few servants for the directories and files in the file system, and then activates the adapter. This leads to a main program as follows:
import sys, threading, Ice, Filesystem

# DirectoryI servant class ...
# FileI servant class ...

class Server(Ice.Application):
    def run(self, args):
        # Terminate cleanly on receipt of a signal
        #
        self.shutdownOnInterrupt()

        # Create an object adapter (stored in the _adapter
        # static members)
        #
        adapter = self.communicator().\
                    createObjectAdapterWithEndpoints(
                        "SimpleFilesystem", "default p 10000")
        DirectoryI._adapter = adapter
        FileI._adapter = adapter

        # Create the root directory (with name "/" and no parent)
        #
        root = DirectoryI("/", None)

        # Create a file called "README" in the root directory
        #
        file = FileI("README", root)
        text = [ "This file system contains a collection of " +
                 "poetry." ]
        try:
            file.write(text)
        except Filesystem.GenericError, e:
            print e.reason

        # Create a directory called "Coleridge"
        # in the root directory
        #
        coleridge = DirectoryI("Coleridge", root)

        # Create a file called "Kubla_Khan"
        # in the Coleridge directory
        #
        file = FileI("Kubla_Khan", coleridge)
        text = [ "In Xanadu did Kubla Khan",
                 "A stately pleasuredome decree:",
                 "Where Alph, the sacred river, ran",
                 "Through caverns measureless to man",
                 "Down to a sunless sea." ]
        try:
            file.write(text)
        except Filesystem.GenericError, e:
            print e.reason

        # All objects are created, allow client requests now
        #
        adapter.activate()

        # Wait until we are done
        #
        self.communicator().waitForShutdown()

        if self.interrupted():
            print self.appName() + ": terminating"

        return 0

app = Server()
sys.exit(app.main(sys.argv))
The code defines the Server class, which derives from Ice.Application and contains the main application logic in its run method. Much of this code is boiler plate that we saw previously: we create an object adapter, and, towards the end, activate the object adapter and call waitForShutdown.
The interesting part of the code follows the adapter creation: here, the server instantiates a few nodes for our file system to create the structure shown in Figure 21.1.
Figure 21.1. A small file system.
As we will see shortly, the servants for our directories and files are of type DirectoryI and FileI, respectively. The constructor for either type of servant accepts two parameters, the name of the directory or file to be created and a reference to the servant for the parent directory. (For the root directory, which has no parent, we pass None.) Thus, the statement
        root = DirectoryI("/", None)
creates the root directory, with the name "/" and no parent directory.
Here is the code that establishes the structure in Figure 21.1:
        # Create the root directory (with name "/" and no parent)
        #
        root = DirectoryI("/", None)

        # Create a file called "README" in the root directory
        #
        file = FileI("README", root)
        text = [ "This file system contains a collection of " +
                 "poetry." ]
        try:
            file.write(text)
        except Filesystem.GenericError, e:
            print e.reason

        # Create a directory called "Coleridge"
        # in the root directory
        #
        coleridge = DirectoryI("Coleridge", root)

        # Create a file called "Kubla_Khan"
        # in the Coleridge directory
        #
        file = FileI("Kubla_Khan", coleridge)
        text = [ "In Xanadu did Kubla Khan",
                 "A stately pleasuredome decree:",
                 "Where Alph, the sacred river, ran",
                 "Through caverns measureless to man",
                 "Down to a sunless sea." ]
        try:
            file.write(text)
        except Filesystem.GenericError, e:
            print e.reason
We first create the root directory and a file README within the root directory. (Note that we pass a reference to the root directory as the parent when we create the new node of type FileI.)
The next step is to fill the file with text:
        text = [ "This file system contains a collection of " +
                 "poetry." ]
        try:
            file.write(text)
        except Filesystem.GenericError, e:
            print e.reason
Recall from Section 14.7.3 that Slice sequences map to Python lists. The Slice type Lines is simply a list of strings; we add a line of text to our README file by initializing the text list to contain one element.
Finally, we call the Slice write operation on our FileI servant by simply writing:
            file.write(text)
This statement is interesting: the server code invokes an operation on one of its own servants. Because the call happens via a reference to the servant (of type FileI) and not via a proxy (of type FilePrx), the Ice run time does not know that this call is even taking place—such a direct call into a servant is not mediated by the Ice run time in any way and is dispatched as an ordinary Python method call.
In similar fashion, the remainder of the code creates a subdirectory called Coleridge and, within that directory, a file called Kubla_Khan to complete the structure in Figure 21.1.

21.2.2 The FileI Servant Class

Our FileI servant class has the following basic structure:
class FileI(Filesystem.File):
    # Constructor and operations here...

    _adapter = None
The class has a number of data members:
• _adapter
This class member stores a reference to the single object adapter we use in our server.
• _name
This instance member stores the name of the file incarnated by the servant.
• _parent
This instance member stores the reference to the servant for the file’s parent directory.
• _lines
This instance member holds the contents of the file.
The _name, _parent, and _lines data members are initialized by the constructor:
    def __init__(self, name, parent):
        self._name = name
        self._parent = parent
        self._lines = []

        assert(self._parent != None)

        # Create an identity
        #
        myID = self._adapter.getCommunicator().
                    stringToIdentity(Ice.generateUUID())

        # Add the identity to the object adapter
        #
        self._adapter.add(self, myID)

        # Create a proxy for the new node and
        # add it as a child to the parent
        #
        thisNode = Filesystem.NodePrx.uncheckedCast(
                        self._adapter.createProxy(myID))
        self._parent.addChild(thisNode)
After initializing the instance members, the code verifies that the reference to the parent is not None because every file must have a parent directory. The constructor then generates an identity for the file by calling Ice.generateUUID and adds itself to the servant map by calling ObjectAdapter.add. Finally, the constructor creates a proxy for this file and calls the addChild method on its parent directory. addChild is a helper function that a child directory or file calls to add itself to the list of descendant nodes of its parent directory. We will see the implementation of this function on page 572.
The remaining methods of the FileI class implement the Slice operations we defined in the Node and File Slice interfaces:
    # Slice Node::name() operation

    def name(self, current=None):
        return self._name

    # Slice File::read() operation

    def read(self, current=None):
        return self._lines

    # Slice File::write() operation

    def write(self, text, current=None):
        self._lines = text
The name method is inherited from the generated Node class. It simply returns the value of the _name instance member.
The read and write methods are inherited from the generated File class and simply return and set the _lines instance member.

21.2.3 The DirectoryI Servant Class

The DirectoryI class has the following basic structure:
class DirectoryI(Filesystem.Directory):
    # Constructor and operations here...

    _adapter = None
As for the FileI class, we have data members to store the object adapter, the name, and the parent directory. (For the root directory, the _parent member holds None.) In addition, we have a _contents data member that stores the list of child directories. These data members are initialized by the constructor:
    def __init__(self, name, parent):
        self._name = name
        self._parent = parent
        self._contents = []

        # Create an identity. The
        # parent has the fixed identity "RootDir"
        #
        if(self._parent):
            myID = self._adapter.getCommunicator().
                        stringToIdentity(Ice.generateUUID())
        else:
            myID = self._adapter.getCommunicator().
                        stringToIdentity("RootDir")

        # Add the identity to the object adapter
        #
        self._adapter.add(self, myID)

        # Create a proxy for the new node and
        # add it as a child to the parent
        #
        thisNode = Filesystem.NodePrx.uncheckedCast(
                        self._adapter.createProxy(myID))
        if self._parent:
            self._parent.addChild(thisNode)
The constructor creates an identity for the new directory by calling Ice.generateUUID. (For the root directory, we use the fixed identity "RootDir".) The servant adds itself to the servant map by calling ObjectAdapter.add and then creates a proxy to itself and passes it to the addChild helper function.
addChild simply adds the passed reference to the _contents list:
    def addChild(self, child):
        self._contents.append(child)
The remainder of the operations, name and list, are trivial:
    def name(self, current=None):
        return self._name

    def list(self, current=None):
        return self._contents
Table of Contents Previous Next
Logo