This wiki is in the process of being archived due to lack of usage and the resources necessary to serve it — predominately to bots, crawlers, and LLM companies. Edits are discouraged.
Pages are preserved as they were at the time of archival. For current information, please visit python.org.
If a change to this archive is absolutely needed, requests can be made via the infrastructure@python.org mailing list.

An IOSlave Tutorial

Note

This document is a work in progress. Please feel free to add constructive comments, make additions and edit accordingly.

Introduction

The ADFS IOSlave presents the contents of ADFS floppy disk images to the user by inspecting the ADFS filesystem stored within each image and using the KIOSlave API to return this information to client applications. Although the underlying Python module was written with a command line interface in mind, it provides an interface which can be used by an IOSlave without too much of an overhead.

This document outlines the source code and structure of the ADFS IOSlave and aims to be a useful guide to those wishing to write IOSlaves, either in Python or C++.

Annotated code

It is convenient to examine the source code as it is written in the kio_adfs.py file. However, we can at least group some of the methods in order to provide an overview of the IOSlave and separate out the details of the initialisation.

Initialisation

Various classes and namespaces are required in order to communicate with the KIOSlave framework. These are drawn from the qt, kio and kdecore modules:

from qt import QByteArray
from kio import KIO
from kdecore import KURL

The os and time modules provide functions which are relevant to the operation of the IOSlave; the sys and traceback modules are useful for debugging the IOSlave:

import os, sys, traceback, time

The ADFSlib module is imported. This provides the functionality required to read disk images:

import ADFSlib

We omit the debugging code to keep this tutorial fairly brief. This can be examined in the distributed source code.

The slave class

We define a class which will be used to create instances of this IOSlave. The class must be derived from the KIO.SlaveBase class so that it can communicate with clients via the DCOP mechanism. Various operations are supported if the appropriate method (represented as virtual functions in C++) are reimplemented in our subclass.

Note that the name of the class is also declared in the details.py file so that the library which launches the Python interpreter knows which class to instantiate.

class SlaveClass(KIO.SlaveBase):

    """SlaveClass(KIO.SlaveBase)
    
    See kdelibs/kio/kio/slavebase.h for virtual functions to override.
    """

An initialisation method, or constructor, is written which calls the base class and initialises some useful attributes, or instance variables. Note that the name of the IOSlave is passed to the base class's __init__ method:

    def __init__(self, pool, app):
    
        # We must call the initialisation method of the base class.
        
        KIO.SlaveBase.__init__(self, "adfs", pool, app)
        
        # Initialise various instance variables.
        
        self.host = ""
        self.disc_image = None
        self.adfsdisc = None

We create a method to parse any URLs passed to the IOSlave and return a path into a disk image. This initially extracts the path from the KURL object passed as an argument and converts it to a unicode string:

    def parse_url(self, url):
    
        file_path = unicode(url.path())

The presence of a colon character is determined. If one is present then it will simply be discarded along with any preceding text; the remaining text is assumed to be a path to a local file.

        at = file_path.find(u":")
        
        if at != -1:
        
            file_path = file_path[at+1:]

Since we are implementing a read-only IOSlave, we can implement a simple caching system for operations within a single disk image. If we have cached a URL for a disk image then we check whether the URL passed refers to an item beneath it. This implies that the cached URL is a substring of the one given.

If the disk image has been cached then return the path within the image:

        if self.disc_image and file_path.find(self.disc_image) == 0:
        
            # Return the path within the image.
            return file_path[len(self.disc_image):]

An uncached URL must be examined element by element, as far as possible, comparing the path with the local filesystem. Since a valid path will contain at least one slash character then we can immediately discard any paths which do not contain one, returning None to the caller:

        elements = file_path.split(u"/")
        
        if len(elements) < 2:
        
            return None

Starting from the root directory, we apply each new path element to the constructed path, testing for the presence of the objects it refers to. If no object can be found at the path given then None is returned to the caller to indicate that the URL was invalid. If a file is found then it is assumed that an ADFS disk image has been located; a check could be performed to verify this. Finally, if all the path elements are added to the root directory, and the object referred to is itself a directory, then the URL is treated as invalid; it should have referred to a file.

        path_elements, elements = elements[:1], elements[1:]
        
        while elements != []:
        
            path_elements.append(elements.pop(0))
            
            path = u"/".join(path_elements)
            
            if os.path.isfile(path):
            
                break
            
            elif elements == [] and os.path.isdir(path):
            
                return None
            
            elif not os.path.exists(path):
            
                return None

At this point, it is assumed that a suitable image has been found at the constructed path. The characters following this path correspond to the path within the image file. We record the path to the image and construct the path within the image:

        self.disc_image = path
        
        image_path = u"/".join(elements)

If not already open, it is necessary to open the image file, returning None if the file cannot be found. (Its presence was detected earlier but it is better to catch any exceptions.)

        try:
        
            adf = open(self.disc_image, "rb")
        
        except IOError:
        
            return None

We attempt to open the disk image using a class from the support module. This will read and catalogue the files within the image, storing them in an internal structure. However, if a problem is found with the image, then an exception will be raised. We tidy up and return None to signal failure in such a case, but otherwise return the path within the image:

        try:
        
            self.adfsdisc = ADFSlib.ADFSdisc(adf)
        
        except ADFSlib.ADFS_exception:
        
            adf.close()
            return None
        
        return image_path

The get file operation

Various fundamental operations are required if the IOSlave is going to perform a useful function. The first of these is provided by the get method which reads files in the disk image and sends their contents to the client. The first thing this method does is check the URL supplied using the previously defined parse_url method, reporting an error if the URL is unusable:

    def get(self, url):
    
        path = self.parse_url(url)
        
        if path is None:
        
            self.error(KIO.ERR_DOES_NOT_EXIST, url.path())
            return

Having established that the disk image referred to is valid, we now have a path which is supposed to refer to a file within the image. It is now necessary to attempt to find this file. This is achieved by the use of the as yet undeclared find_file_within_image method which will return None if a suitable file cannot be found:

        adfs_object = self.find_file_within_image(path)
        
        if not adfs_object:
        
            self.error(KIO.ERR_DOES_NOT_EXIST, path)
            return

Since, at this point, an object of some sort was located within the image, we need to check whether it is a directory and return an error if so.

The details of the object returned by the above method is in the form of a tuple which contains the name, the file data and some other metadata.

If the second element in the tuple is a list then the object found is a directory:

        if type(adfs_object[1]) == type([]):
        
            self.error(KIO.ERR_IS_DIRECTORY, path)
            return

For files, the second element of the tuple contains a string. In this method, we are only interested in the file data. Using the base class's data method, which we can access through the current instance, we send a QByteArray to the client:

        self.data(QByteArray(adfs_object[1]))

The end of the data string is indicated by an empty QByteArray before we indicate completion of the operation by calling the base class's finished method:

        self.data(QByteArray())
        
        self.finished()

The stat operation

The stat method returns information about files and directories within the disk image. It is very important that this method works properly as, otherwise, the IOSlave will not work as expected and may appear to be behaving in an unpredictable manner. For example, clients such as Konqueror often use the stat method to find out information about objects before calling get, so failure to read a file may actually be the result of a misbehaving stat operation.

As for the get method, the stat method initially verifies that the URL supplied is referring to a valid disk image, and that there is a path within the image to use. Unlike the get method, it will redirect the client to the path contained within the URL if the parse_url fails to handle it. This allows the user to use URL autocompletion on ordinary filesystems while searching for images to read.