psa: Adding notifications to even feed screen
[feedingit] / psa_harmattan / feedingit / pysrc / eventfeed.py
1 # -*- coding: utf-8 -*-
2
3 """
4 A library to post events to the MeeGo 1.2 Harmattan Event Feed
5
6 This library is intended to be used by N950, N9 application or
7 service developers who want to post their own content to the
8 MeeGo 1.2 Harmattan UX Event Feed screen.
9 """
10
11 __license__ = """
12 Copyright (c) 2011, Thomas Perl <m@thp.io>
13
14 Permission to use, copy, modify, and/or distribute this software for any
15 purpose with or without fee is hereby granted, provided that the above
16 copyright notice and this permission notice appear in all copies.
17
18 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
20 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
21 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
23 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
24 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 """
26
27 __version__ = '1.0'
28 __author__ = 'Thomas Perl <thp.io/about>'
29 __url__ = 'http://thp.io/2011/eventfeed/'
30
31 __version_info__ = tuple(int(x) for x in __version__.split('.'))
32
33 # Dependency on PySide for encoding/decoding like MRemoteAction
34 from PySide.QtCore import QBuffer, QIODevice, QDataStream, QByteArray
35
36 # Python D-Bus Library dependency for communcating with the service
37 import dbus
38 import dbus.service
39 import dbus.mainloop
40 import dbus.glib
41
42 import datetime
43 import logging
44
45
46 logger = logging.getLogger(__name__)
47
48 # When the user clicks on "Refresh", this signal gets sent via D-Bus:
49 # signal sender=:1.8 -> dest=(null destination) serial=855 path=/eventfeed; interface=com.nokia.home.EventFeed; member=refreshRequested
50 # TODO: Implement support for receiving this signal
51
52 # MRemoteAction::toString()
53 # http://apidocs.meego.com/1.0/mtf/mremoteaction_8cpp_source.html
54 def qvariant_encode(value):
55     buffer = QBuffer()
56     buffer.open(QIODevice.ReadWrite)
57     stream = QDataStream(buffer)
58     stream.writeQVariant(value)
59     buffer.close()
60     return buffer.buffer().toBase64().data().strip()
61
62 # MRemoteAction::fromString()
63 # http://apidocs.meego.com/1.0/mtf/mremoteaction_8cpp_source.html
64 def qvariant_decode(data):
65     byteArray = QByteArray.fromBase64(data)
66     buffer = QBuffer(byteArray)
67     buffer.open(QIODevice.ReadOnly)
68     stream = QDataStream(buffer)
69     result = stream.readQVariant()
70     buffer.close()
71     return result
72
73
74 class EventFeedItem(object):
75     """One item that can be posted to the event feed"""
76
77     def __init__(self, icon, title, timestamp=None):
78         """Create a new event feed item
79
80         :param icon: Icon name or path to icon file (can be a URL)
81         :param title: The title text describing this item
82         :param timestamp: datetime.datetime object when the item happened (optional)
83         """
84         if timestamp is None:
85             timestamp = datetime.datetime.now()
86
87         timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S')
88
89         self.args = {
90             'icon': icon,
91             'title': title,
92             'timestamp': timestamp,
93         }
94
95         # ID assigned when showing item
96         self.id = -1
97
98         # Callback for when the action is clicked
99         self.callback = None
100
101         # Action data (custom list of stuff for callback)
102         self.action_data = None
103
104     def set_body(self, body):
105         """Body text of the item (string)"""
106         self.args['body'] = body
107
108     def set_image_list(self, image_list):
109         """List of image filenames/URLs (list of strings)"""
110         self.args['imageList'] = image_list
111
112     def set_footer(self, footer):
113         """Footer text, displayed near the time (string)"""
114         self.args['footer'] = footer
115
116     def set_video(self, video):
117         """Flag to overlay a play button on the thumbnail (bool)"""
118         self.args['video'] = video
119
120     def set_url(self, url):
121         """The URL to be opened when the item is clicked (string)"""
122         self.args['url'] = url
123
124     def set_action_data(self, *args):
125         """The data to be sent when clicked (list of str, int, bool)"""
126         self.action_data = args
127
128     def set_custom_action(self, callback):
129         """The action to be executed when clicked (callable)"""
130         self.callback = callback
131
132 class EventFeedSender:
133     EVENT_FEED_NAME = 'com.nokia.home.EventFeed'
134     EVENT_FEED_PATH = '/eventfeed'
135     EVENT_FEED_INTF = 'com.nokia.home.EventFeed'
136     EVENT_FEED_CALL = 'addItem'
137     
138     DEFAULT_NAME = 'org.maemo.feedingit'
139     DEFAULT_PATH = '/org/maemo/feedingit'
140     DEFAULT_INTF = 'org.maemo.feedingit'
141     
142     def __init__(self, source_name, source_display_name, on_data_received=None):
143         self.next_action_id = 1
144         self.actions = {}
145         self.source_name = source_name
146         self.source_display_name = source_display_name
147         self.on_data_received = on_data_received
148
149         dbus_main_loop = dbus.glib.DBusGMainLoop(set_as_default=True)
150         session_bus = dbus.SessionBus(dbus_main_loop)
151
152         o = session_bus.get_object(self.EVENT_FEED_NAME, self.EVENT_FEED_PATH)
153         self.event_feed = dbus.Interface(o, self.EVENT_FEED_INTF)
154         
155     def add_item(self, item):
156         """Send a EventFeedItem to the service to be displayed
157
158         :param item: EventFeedItem to be displayed
159         """
160         if item.id != -1:
161             logger.debug('Message %d already shown - updating footer.', item.id)
162             self.update_item(item)
163             return item.id
164
165         action = item.callback
166         action_data = item.action_data
167         data = item.args.copy()
168
169         data['sourceName'] = self.source_name
170         data['sourceDisplayName'] = self.source_display_name
171
172         remote_action = [
173                 self.DEFAULT_NAME,
174                 self.DEFAULT_PATH,
175                 self.DEFAULT_INTF,
176         ]
177
178         remote_action.append('OpenFeed')
179         remote_action.extend([qvariant_encode(x) for x in action_data])
180
181         data['action'] = ' '.join(remote_action)
182
183         item.id = self.event_feed.addItem(data)
184
185         return item.id
186     
187     def remove_items(self):
188         """Remove all items """
189         self.event_feed.removeItemsBySourceName(self.source_name)
190         # No need to remember action IDs, because all items were removed
191         self.actions = {}