pvl/syslog/tail.py
author Tero Marttila <terom@fixme.fi>
Fri, 04 Jan 2013 21:26:39 +0200
changeset 59 caed0ed82709
parent 53 685fd90bc610
child 73 ef01c4639689
permissions -rw-r--r--
pvl.syslog.tail: re-implement seek=True
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
     1
"""
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
     2
    Iterate over input lines in files. 
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
     3
    
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
     4
    Can be read up to eof, on blocking inputs, or polled with a timeout (see: pvl.syslog.syslog.SyslogSource)
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
     5
"""
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
     6
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
     7
import os
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
     8
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
     9
import logging; log = logging.getLogger('pvl.syslog.tail')
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    10
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    11
class Tail (object) :
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    12
    """
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    13
        Follow a file-like object, reading lines until EOF.
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    14
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    15
        Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`.
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    16
    """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    17
59
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    18
    def __init__ (self, file, skip=None) :
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    19
        self.file = file
59
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    20
        
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    21
        if skip :
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    22
            self.skip()
46
0bdbbda4cdea pvl.syslog: have --syslog-tail skip to end of file
Tero Marttila <terom@fixme.fi>
parents: 31
diff changeset
    23
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    24
    def readline (self) :
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    25
        """
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    26
            Reads a line from the file, without trailing \n
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    27
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    28
            Raises EOF on end-of-file.
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    29
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    30
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    31
        line = self.file.readline()
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    32
53
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    33
        log.debug("%s", line)
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    34
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    35
        if not line :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    36
            raise EOFError()
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    37
        else :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    38
            return line.rstrip('\n')
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    39
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    40
    def readlines (self) :
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    41
        """
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    42
            Reads any available lines from the file.
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    43
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    44
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    45
        while True :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    46
            try :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    47
                line = self.readline()
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    48
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    49
            except EOFError :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    50
                log.debug("EOF")
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    51
                break
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    52
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    53
            else :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    54
                yield line
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    55
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    56
    def skip (self) :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    57
        """
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    58
            Skip any available lines.
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    59
        """
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    60
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    61
        for line in self.readlines() :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    62
            pass
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    63
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    64
    __iter__ = readlines
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    65
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    66
class TailFile (Tail) :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    67
    """
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    68
        Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced.
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    69
    """
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    70
59
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    71
    def __init__ (self, path, **opts) :
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    72
        log.debug("%s", path)
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    73
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    74
        self.path = path
59
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    75
        self._stat = self.stat()
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    76
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    77
        file = self.open()
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    78
        
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    79
        # may call skip -> readlines -> changed() -> _stat
caed0ed82709 pvl.syslog.tail: re-implement seek=True
Tero Marttila <terom@fixme.fi>
parents: 53
diff changeset
    80
        Tail.__init__(self, file, **opts)
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    81
    
53
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    82
    def stat (self) :
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    83
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    84
            Return a key identifying the file at our path.
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    85
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    86
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    87
        st = os.stat(self.path)
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    88
53
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    89
        stat = st.st_dev, st.st_ino
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    90
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    91
        log.debug("%s: %s", self, stat)
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    92
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    93
        return stat
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    94
    
53
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    95
    def open (self) :
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    96
        log.debug("%s", self)
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
    97
        return open(self.path, 'r')
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
    98
53
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
    99
    def reopen (self) :
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   100
        self.file = self.open()
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   101
        self._stat = self.stat()
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   102
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   103
    def changed (self) :
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   104
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   105
            Has the underlying file changed?
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   106
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   107
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   108
        return self.stat() != self._stat
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   109
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   110
    def readlines (self, eof_mark=False) :
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   111
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   112
            Reads any available lines from the file.
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   113
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   114
            Reopens the file on EOF if the underlying file changed.
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   115
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   116
                eof_mark:   yields a special None line when this happens.
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   117
        """
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   118
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   119
        while True :
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   120
            try :
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   121
                line = self.readline()
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   122
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   123
            except EOFError :
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   124
                if self.changed() :
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   125
                    log.debug("EOF: reopen")
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   126
                    self.reopen()
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   127
                    
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   128
                    if eof_mark :
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   129
                        yield None # special token
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   130
                    
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   131
                    # keep going
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   132
                    continue
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   133
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   134
                else :
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   135
                    log.debug("EOF")
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   136
                    break # wait
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   137
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   138
            else :
49
30c615bf7751 pvl.syslog.tail: split Tail vs TailFile
Tero Marttila <terom@fixme.fi>
parents: 46
diff changeset
   139
                yield line
31
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   140
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   141
    __iter__ = readlines
3e6d0feb115c pvl.syslog: import from pvl-collectd
Tero Marttila <terom@paivola.fi>
parents:
diff changeset
   142
53
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
   143
    def __str__ (self) :
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
   144
        return self.path
685fd90bc610 pvl.syslog.tail: bugfix, log.debug readline
Tero Marttila <terom@fixme.fi>
parents: 49
diff changeset
   145