Revision 5 as of 2003-09-15 20:25:45

Clear message

IOSlaves Tutorial

Note: This is a work in progress.

Abstract

A small library was written to allow IOSlaves, the protocol handlers for the [http://www.kde.org/ K Desktop Environment], to be written in Python using the PyQt and ["PyKDE"] modules. An example IOSlave was created for the purpose of examining the user's Konqueror bookmarks using this library and a simple Python class. Since Python works well as a "glue" language, it is hoped that the creation of IOSlaves will be made more accessible to a wider range of users, leading to a richer, more transparent user experience on the desktop.

Note: the installation procedure for IOSlaves in forthcoming versions of PyKDE will probably be different to that described below.

Introduction

In KDE's infrastructure, IOSlaves handle the transfer of data between applications and remote servers using common protocols such as http and ftp, but also for more mundane protocols like the file protocol for local files. Many of the mainstream protocols provided in the standard KDE distribution are implemented in C++, although some, like the finger IOSlave, rely on support scripts to handle various aspects of communication with remote servers. Since ["PyKDE"] provides Python implementations (or wrappers) for the relevant classes in the kio library, it is possible to write IOSlaves almost completely in Python; a simple C++ handling function is only required for dynamic linking purposes and to set up the interpreter.

Much of the documentation describing the creation of IOSlaves is, naturally, written to assist the C++ programmer by providing examples of the appropriate classes in use. When read alongside some of the distributed examples in the kdebase package of the KDE distribution, these tutorials provide most of the information required to write an IOSlave in Python "from scratch". However, some aspects of their operation would benefit from further description so it is useful to take this opportunity, when translating the material for a new audience, to try and provide clear and concise documentation to complement existing material. Note that I am not a implentor of IOSlaves in C++ so there may be scope for future additions and corrections to this document from KDE experts.

We will begin by describing the implementation of the Python module, since the C++ handler should be transparent in use, before discussing potential problems with IOSlaves, methods for debugging them and any known limitations to their use.

Implementation

Modules and the slave class

We begin by importing the necessary modules. Some of these are needed for general interoperability with the KDE IOSlave infrastructure:

from qt import QString, QByteArray, QDataStream, IO_ReadOnly
from kio import KIO
from kdecore import KURL

Other familiar Python modules are used when performing tasks specific to this IOSlave:

import os, time

Additionally, we are going to use the XML module from the Qt libraries:

import qtxml

We define a class which will be instantiated when the bookmarks protocol is used. This is a subclass of KIO.SlaveBase and relies on the facilities of this base class for communication with applications. At this point, it is useful to describe how the bookmark information will be encapsulated in the form of files. We will be representing each bookmark as a Desktop file, so we declare a template to be filled in for each bookmark and the MIME type for these files:

class SlaveClass(KIO.SlaveBase):
    desktop_template = \

u"""[Desktop Entry]
Encoding=%(encoding)s
Icon=%(icon)s
Type=Link
URL=%(href)s
"""

    bookmark_mimetype = "application/x-desktop"

The __init__ method for this class simply calls the corresponding method of the base class and initialises some variables for later use. The __del__ method currently does nothing.

    def __init__(self, pool, app):
    
        KIO.SlaveBase.__init__(self, "bookmarks", pool, app)
        
        self.dcopClient().attach()
        
        self.host = ""
        self.document = None
        self.file = None
    
    def __del__(self):
    
        pass

General operations

Although the actions performed by an IOSlave will typically be closely related to its purpose, we will define the methods required by many IOSlaves and only later define the methods which are specific to the bookmarks protocol.

setHost

The setHost method is called when the bookmarks IOSlave is asked to perform an operation in which a host is specified. Since we will only be looking at the user's local bookmarks, the host name is both not required and not wanted. When a host name is given, we indicate that an error has occurred using the base class's error method:

    def setHost(self, host, port, user, passwd):
    
        if unicode(host) != u"":
        
            self.closeConnection()
            self.error(KIO.ERR_MALFORMED_URL, host)
            return

openConnection and closeConnection

The openConnection method is called before an application tries to perform operations on the file system presented by an IOSlave. For the bookmarks protocol, we take this opportunity to read the user's bookmarks file from the appropriate place in their home directory.

    def openConnection(self):
    
        # Don't call self.finished() in this method.

We find the user's home directory from a shell variable and look for a "bookmarks.xml" file in a subdirectory beneath the ".kde" subdirectory.

        self.home = os.getenv("HOME")
        
        path = os.path.join(
            self.home, ".kde", "share", "apps", "konqueror",
            "bookmarks" + os.extsep + "xml"
            )

For convenience, we record the path used to obtain the bookmarks before trying to open the file specified by that path.

        self.file = path
        
        try:
        
            b = open(self.file, "r").read()

If the bookmarks file was not found at the expected location then an error is returned to the application. This may be passed on to the user.

        except IOError:
        
            self.error(KIO.ERR_CANNOT_OPEN_FOR_READING, self.file)
            return

The XML contained in the file is converted to a DOM representation of the document which is stored in an instance attribute.

        self.document = qtxml.QDomDocument()
        
        result, errorMsg, errorLine, errorColumn = self.document.setContent(b)

If the XML in the bookmarks file could not be interpreted then an error is returned.

        if result == 0:
        
            self.error(KIO.ERR_CANNOT_OPEN_FOR_READING, self.file)
            self.document = None
            return

For this IOSlave, the closeConnection method simply writes any bookmark information to the user's bookmarks file and indicates that the document will need to be read again before subsequent operations can be performed. Other IOSlaves may need to perform more complicated operations at this point.

    def closeConnection(self):
    
        # Don't call self.finished() in this method.
        
        self._flush()
        
        self.document = None

get

The get method is called when an application requests an object, represented by the URL given, using the relevant protocol; in this case the bookmarks protocol. When the calling application requests a bookmark, represented as a file by the IOSlave, the contents of this file are generated using a template and returned to the application.

    def get(self, url):
    
        self.openConnection()

The URL must be examined and the relevant node found from the document describing the user's bookmarks. To do this, we call a method which is specific to this IOSlave. If the URL refers to a valid object then the path to the object, the name of the object and a DOM node are returned to this method; otherwise, the name and object return values are set to None.

        path, name, obj = self.find_object_from_url(url)
        
        if obj is None:
        
            self.error(KIO.ERR_DOES_NOT_EXIST, path)
            return

If, on the other hand, an object was found then we can check whether it is a bookmark or some other object. Objects such as folders cannot be fetched using the get method.

        if unicode(obj.nodeName()) != u"bookmark":
        
            self.error(KIO.ERR_IS_DIRECTORY, path)
            return

With the object representing a bookmark determined, we can return the details to the calling application in the form of a file with a MIME type as defined earlier. We declare the MIME type before we send the file data.

        self.mimeType(self.bookmark_mimetype)

The bookmark's details such as its title, target URL and the icon used to represent it in a menu, are read and converted into a form suitable for presentation.

        details = self.read_bookmark_details(obj)
        
        details[u"title"] = self.decode_name(details[u"title"])

The contents of the file to be returned are created using a template combined with information from the DOM node found earlier.

        text = self.desktop_template % details
        output = text.encode(details[u"encoding"])

The constructed file is sent to the application through the use of the base class's data method. Note the use of the QByteArray class for this purpose.

        self.data(QByteArray(output))

To report the end of the data, we must send the application an empty byte array:

        self.data(QByteArray())

We must also report that we have finished the operation by calling the base class's finished method:

        self.finished()

Synchronisation

This IOSlave defines a _flush method to allow changes to the user's bookmarks to be written to their bookmarks file. This is typically only performed in methods where a request was made to change some aspect of the presented file system.

    def _flush(self):
    
        if self.document is None:
        
            return
        
        try:
        
            b = open(self.file, "w")
        
        except IOError:
        
            self.error(KIO.ERR_CANNOT_OPEN_FOR_WRITING, self.file)
            return
        
        b.write(str(self.document.toString()))
        b.close()

Resetting the document attribute for this instance will cause the bookmarks file to be read from the user's home directory before further operations can be performed on the presented file system:

        self.document = None

Unable to edit the page? See the FrontPage for instructions.