~jconti/recent-notifications/trunk

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
"""
Notifications.py
by Jason Conti
February 15, 2010

Reads notifications in the notify-osd.log format specified at:
  https://wiki.ubuntu.com/NotifyOSD#Logging notifications
"""

import re
import time

from LogWatcher import LogWatcher

message_header = re.compile("""
    ^
    \[                            # [timestamp, process-name, status?]
    (
    \d{4}-\d{2}-\d{2}             # date YYYY-mm-dd
    [Tt]
    \d{2}:\d{2}:\d{2}             # time HH-MM-SS
    ([Zz]|[+-]\d{2}:\d{2})        # time offset
    )
    ,
    ([^,\]]+)                     # process name
    (,([^\]]+))?                  # optional status
    \]
    (.*)                          # title
    $
    """, re.VERBOSE)

class Message(object):
  """A notification message"""
  def __init__(self, timestamp, process_name, status, title):
    self.timestamp = timestamp
    self.process_name = process_name
    self.status = status
    self.title = title
    self.body = ""

  def append_body(self, body):
    """Appends a body line to the message."""
    self.body += body

  def formatted_time(self):
    """Returned the time in a different format."""
    t = time.strptime(self.timestamp.upper(), "%Y-%m-%dT%H:%M:%S-00:00")
    return time.strftime("%B %d, %Y at %I:%M:%S %p", t)

class Notifications(object):
  """Reads notifications from a log file and sends them to the listeners."""
  def __init__(self, path):
    self._path = path
    self._listeners = set()

  def add_listener(self, f):
    """A listener is a function of a single argument, the messages 
    received."""
    self._listeners.add(f)

  def remove_listener(self, f):
    """Removes f from the set of listeners."""
    self._listeners.remove(f)

  def start(self):
    """Reads the previous messages, sends them to the listeners and starts
    watching for more messages."""
    # Get the previous messages
    f = open(self._path, "r")
    messages = f.read()
    f.close()
    self._message_received(None, messages)

    self._watcher = LogWatcher(self._path)
    self._watcher.connect("changed", self._message_received)

    self._watcher.start()

  def stop(self):
    """Stops watching for more messages."""
    self._watcher.stop()

  def _notify_listeners(self, messages):
    """Notifies the listeners of new messages."""
    for f in self._listeners:
      f(messages)

  def _message_received(self, watcher, messages):
    """Callback that parses messages and adds them to the queue."""
    queue = self._parse_messages(messages)
    if len(queue) > 0:
      self._notify_listeners(queue)

  def _parse_messages(self, messages):
    """Returns a list of parsed messages."""
    lines = messages.splitlines()
    queue = []
    message = None

    for line in lines:
      # Try to parse a new message, discard the line if the header
      # doesn't match
      if message == None:
        m = message_header.match(line)
        if m != None:
          header = m.groups()
          timestamp = header[0].strip()
          process_name = header[2].strip()
          status = header[4]
          # Status may not exist
          if status == None:
            status = ""
          else:
            status = status.strip()
          title = header[5].strip()
          message = Message(timestamp, process_name, status, title)
      # Messages are delimited by a blank line
      else:
        if line.strip() == "":
          queue.append(message)
          message = None
        else:
          message.append_body(line)

    # Don't forget the last message (should probably never happen)
    if message != None:
      queue.append(message)

    return queue