~jconti/recent-notifications/trunk

11 by Jason Conti
Updated Notifications.py to use the new LogWatcher class. Currently still uses the listeners to notify when new messages arrive, should probably update to use glib signals.
1
"""
2
Notifications.py
3
by Jason Conti
4
February 15, 2010
5
6
Reads notifications in the notify-osd.log format specified at:
7
  https://wiki.ubuntu.com/NotifyOSD#Logging notifications
8
"""
9
10
import re
11
import time
12
13
from LogWatcher import LogWatcher
14
15
message_header = re.compile("""
16
    ^
17
    \[                            # [timestamp, process-name, status?]
18
    (
19
    \d{4}-\d{2}-\d{2}             # date YYYY-mm-dd
20
    [Tt]
21
    \d{2}:\d{2}:\d{2}             # time HH-MM-SS
22
    ([Zz]|[+-]\d{2}:\d{2})        # time offset
23
    )
24
    ,
25
    ([^,\]]+)                     # process name
26
    (,([^\]]+))?                  # optional status
27
    \]
28
    (.*)                          # title
29
    $
30
    """, re.VERBOSE)
31
32
class Message(object):
33
  """A notification message"""
34
  def __init__(self, timestamp, process_name, status, title):
35
    self.timestamp = timestamp
36
    self.process_name = process_name
37
    self.status = status
38
    self.title = title
39
    self.body = ""
40
41
  def append_body(self, body):
42
    """Appends a body line to the message."""
43
    self.body += body
44
45
  def formatted_time(self):
46
    """Returned the time in a different format."""
47
    t = time.strptime(self.timestamp.upper(), "%Y-%m-%dT%H:%M:%S-00:00")
16 by Jason Conti
Modified the timestamp format so it would fit better.
48
    return time.strftime("%B %d, %Y at %I:%M:%S %p", t)
11 by Jason Conti
Updated Notifications.py to use the new LogWatcher class. Currently still uses the listeners to notify when new messages arrive, should probably update to use glib signals.
49
50
class Notifications(object):
51
  """Reads notifications from a log file and sends them to the listeners."""
52
  def __init__(self, path):
53
    self._path = path
54
    self._listeners = set()
55
56
  def add_listener(self, f):
57
    """A listener is a function of a single argument, the messages 
58
    received."""
59
    self._listeners.add(f)
60
61
  def remove_listener(self, f):
62
    """Removes f from the set of listeners."""
63
    self._listeners.remove(f)
64
65
  def start(self):
66
    """Reads the previous messages, sends them to the listeners and starts
67
    watching for more messages."""
68
    # Get the previous messages
69
    f = open(self._path, "r")
70
    messages = f.read()
71
    f.close()
72
    self._message_received(None, messages)
73
74
    self._watcher = LogWatcher(self._path)
75
    self._watcher.connect("changed", self._message_received)
76
77
    self._watcher.start()
78
79
  def stop(self):
80
    """Stops watching for more messages."""
81
    self._watcher.stop()
82
83
  def _notify_listeners(self, messages):
84
    """Notifies the listeners of new messages."""
85
    for f in self._listeners:
86
      f(messages)
87
88
  def _message_received(self, watcher, messages):
89
    """Callback that parses messages and adds them to the queue."""
90
    queue = self._parse_messages(messages)
91
    if len(queue) > 0:
92
      self._notify_listeners(queue)
93
94
  def _parse_messages(self, messages):
95
    """Returns a list of parsed messages."""
96
    lines = messages.splitlines()
97
    queue = []
98
    message = None
99
100
    for line in lines:
101
      # Try to parse a new message, discard the line if the header
102
      # doesn't match
103
      if message == None:
104
        m = message_header.match(line)
105
        if m != None:
106
          header = m.groups()
107
          timestamp = header[0].strip()
108
          process_name = header[2].strip()
109
          status = header[4]
110
          # Status may not exist
111
          if status == None:
112
            status = ""
113
          else:
114
            status = status.strip()
115
          title = header[5].strip()
116
          message = Message(timestamp, process_name, status, title)
117
      # Messages are delimited by a blank line
118
      else:
119
        if line.strip() == "":
120
          queue.append(message)
121
          message = None
122
        else:
123
          message.append_body(line)
124
125
    # Don't forget the last message (should probably never happen)
126
    if message != None:
127
      queue.append(message)
128
129
    return queue
130