# The Evolution of Finger: cleaning up the finger code

## Introduction

This is the third part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger.

In this section of the tutorial, we'll clean up our code so that it is closer to a readable and extensible style.

## Write Readable Code

The last version of the application had a lot of hacks. We avoided sub-classing, didn't support things like user listings over the web, and removed all blank lines -- all in the interest of code which is shorter. Here we take a step back, subclass what is more naturally a subclass, make things which should take multiple lines take them, etc. This shows a much better style of developing Twisted applications, though the hacks in the previous stages are sometimes used in throw-away prototypes.

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    # Do everything properly
    from twisted.application import internet, service
    from twisted.internet import protocol, reactor, defer
    from twisted.words.protocols import irc
    from twisted.protocols import basic
    from twisted.web import resource, server, static, xmlrpc
    import cgi

    def catchError(err):
        return "Internal error in server"

    class FingerProtocol(basic.LineReceiver):

        def lineReceived(self, user):
            d = self.factory.getUser(user)
            d.addErrback(catchError)
            def writeValue(value):
                self.transport.write(value+'\r\n')
                self.transport.loseConnection()
            d.addCallback(writeValue)

    class IRCReplyBot(irc.IRCClient):

        def connectionMade(self):
            self.nickname = self.factory.nickname
            irc.IRCClient.connectionMade(self)

        def privmsg(self, user, channel, msg):
            user = user.split('!')[0]
            if self.nickname.lower() == channel.lower():
                d = self.factory.getUser(msg)
                d.addErrback(catchError)
                d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
                d.addCallback(lambda m: self.msg(user, m))

    class UserStatusTree(resource.Resource):
        def __init__(self, service):
            resource.Resource.__init__(self)
            self.service = service

        def render_GET(self, request):
            d = self.service.getUsers()
            def formatUsers(users):
                l = ['<li><a href="%s">%s</a></li>' % (user, user)
                     for user in users]
                return '<ul>'+''.join(l)+'</ul>'
            d.addCallback(formatUsers)
            d.addCallback(request.write)
            d.addCallback(lambda _: request.finish())
            return server.NOT_DONE_YET

        def getChild(self, path, request):
            if path=="":
                return UserStatusTree(self.service)
            else:
                return UserStatus(path, self.service)

    class UserStatus(resource.Resource):

        def __init__(self, user, service):
            resource.Resource.__init__(self)
            self.user = user
            self.service = service

        def render_GET(self, request):
            d = self.service.getUser(self.user)
            d.addCallback(cgi.escape)
            d.addCallback(lambda m:
                          '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
            d.addCallback(request.write)
            d.addCallback(lambda _: request.finish())
            return server.NOT_DONE_YET

    class UserStatusXR(xmlrpc.XMLRPC):

        def __init__(self, service):
            xmlrpc.XMLRPC.__init__(self)
            self.service = service

        def xmlrpc_getUser(self, user):
            return self.service.getUser(user)

    class FingerService(service.Service):

        def __init__(self, filename):
            self.filename = filename
            self.users = {}

        def _read(self):
            self.users.clear()
            for line in file(self.filename):
                user, status = line.split(':', 1)
                user = user.strip()
                status = status.strip()
                self.users[user] = status
            self.call = reactor.callLater(30, self._read)

        def getUser(self, user):
            return defer.succeed(self.users.get(user, "No such user"))

        def getUsers(self):
            return defer.succeed(self.users.keys())

        def getFingerFactory(self):
            f = protocol.ServerFactory()
            f.protocol = FingerProtocol
            f.getUser = self.getUser
            return f

        def getResource(self):
            r = UserStatusTree(self)
            x = UserStatusXR(self)
            r.putChild('RPC2', x)
            return r

        def getIRCBot(self, nickname):
            f = protocol.ReconnectingClientFactory()
            f.protocol = IRCReplyBot
            f.nickname = nickname
            f.getUser = self.getUser
            return f

        def startService(self):
            self._read()
            service.Service.startService(self)

        def stopService(self):
            service.Service.stopService(self)
            self.call.cancel()

    application = service.Application('finger', uid=1, gid=1)
    f = FingerService('/etc/users')
    serviceCollection = service.IServiceCollection(application)
    internet.TCPServer(79, f.getFingerFactory()
                       ).setServiceParent(serviceCollection)
    internet.TCPServer(8000, server.Site(f.getResource())
                       ).setServiceParent(serviceCollection)
    internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
                       ).setServiceParent(serviceCollection)

Source listing - listings/finger/finger18.tac

Index

Version: 11.0.0