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.
