[MerlinSkinThemes] - add support for layouts
[enigma2-plugins.git] / webinterface / src / WebSocket / DreamboxServerProtocol.py
1 from enigma import eServiceReference, eServiceCenter, eEPGCache
2 from Components.StreamServerControl import streamServerControl
3 from Plugins.Extensions.WebInterface.auth import check_passwd
4 from Tools.Log import Log
5
6 from autobahn.twisted.websocket import WebSocketServerProtocol
7 from autobahn.twisted.resource import WebSocketResource
8
9 import voluptuous as vol
10 import json
11
12 from base64 import b64decode
13 from binascii import hexlify
14 from os import urandom
15
16 class DreamboxServerProtocol(WebSocketServerProtocol):
17         API_VERSION = 1
18
19         AUTH_FAIL_LIMIT = 3
20
21         TYPE_RESULT = "result"
22         TYPE_PING = "ping"
23         TYPE_AUTH_REQUIRED = "auth_required"
24         TYPE_AUTH_OK = "auth_ok"
25         TYPE_AUTH = "auth"
26         TYPE_GET_SERVICES = "get_services"
27         TYPE_GET_EPG_NOWNEXT = "get_epg_nownext"
28         TYPE_GET_STREAM_SETTINGS = "get_stream_settings"
29
30         KEY_EPG_NOWNEXT = -1
31         KEY_EPG_NOW = 0
32         KEY_EPG_NEXT = 1
33
34         REQUEST_BASE_SCHEMA =  vol.Schema({
35                 vol.Required('id'): int,
36                 vol.Optional('cookie'): unicode,
37         })
38
39         BASE_MESSAGE_SCHEMA = REQUEST_BASE_SCHEMA.extend({
40                 vol.Required('type'): vol.Any(
41                                 TYPE_AUTH,
42                                 TYPE_GET_SERVICES,
43                                 TYPE_GET_EPG_NOWNEXT,
44                                 TYPE_GET_STREAM_SETTINGS,
45                                 TYPE_PING
46                         )
47         }, extra=vol.ALLOW_EXTRA)
48
49         AUTH_MESSAGE_SCHEMA = REQUEST_BASE_SCHEMA.extend({
50                 vol.Required('type') : TYPE_AUTH,
51                 vol.Exclusive('token', 'auth') : unicode,
52                 vol.Exclusive('session', 'auth') : unicode,
53         })
54
55         GET_SERVICES_MESSAGE_SCHEMA = REQUEST_BASE_SCHEMA.extend({
56                 vol.Required('type') : TYPE_GET_SERVICES,
57                 vol.Required('reference') : unicode,
58         })
59
60         GET_EPG_NOWNEXT_MESSAGE_SCHEMA = REQUEST_BASE_SCHEMA.extend({
61                 vol.Required('type') : TYPE_GET_EPG_NOWNEXT,
62                 vol.Required('reference') : unicode,
63                 vol.Required('which'): vol.Any(
64                                 KEY_EPG_NOWNEXT,
65                                 KEY_EPG_NOW,
66                                 KEY_EPG_NEXT
67                         )
68         })
69
70         GET_STREAM_SETTINGS_SCHEMA = REQUEST_BASE_SCHEMA.extend({
71                 vol.Required('type') : TYPE_GET_STREAM_SETTINGS
72         })
73
74         server = None
75         session = None
76
77         def __init__(self, *args, **kwargs):
78                 #we do have http basic auth, so whenever the user manages to load the page he is already authenticated
79                 self._authenticated = True
80                 self._failedAuthCount = 0
81                 self._sessionId = None
82                 self._requestID = 0
83                 Log.i("Protocol instance spawned!")
84
85         def _disconnect(self, code=3401, reason=u'Authentication failed'):
86                 self.sendClose(code=code, reason=reason)
87
88         def _genSID(self):
89                 return hexlify(urandom(32))
90
91         def onConnect(self, request):
92                 Log.d("Client connecting: {0}".format(request.peer))
93                 return None
94
95         def onOpen(self):
96                 self.checkAuth()
97
98         def checkAuth(self):
99                 if self._authenticated:
100                         self.sendAuthOk()
101                         return
102                 else:
103                         self.sendAuthRequest()
104
105         def onMessage(self, payload, isBinary):
106                 if isBinary:
107                         Log.w("Binary message received: {0} bytes".format(len(payload)))
108                 else:
109                         msg = json.loads(payload, 'utf8')
110                         Log.d("> %s" % (msg))
111                         self.onJSONMessage(msg)
112
113         def onJSONMessage(self, msg):
114                 msg = self.validate(msg, self.BASE_MESSAGE_SCHEMA)
115                 if not msg:
116                         return
117                 self._requestID = msg["id"]
118                 do = 'do_{}'.format(msg['type'])
119                 getattr(self, do)(msg)
120
121         def sendAuthOk(self):
122                 if not self._sessionId:
123                         self._sessionId = self._genSID()
124                         self.server.addSession(self._sessionId)
125                 self.sendJSON({"type" : self.TYPE_AUTH_OK, "session" : self._sessionId})
126
127         def sendAuthRequest(self):
128                 self.sendJSON({"type" : self.TYPE_AUTH_REQUIRED})
129
130         def sendResult(self, id, result=None):
131                 msg = {
132                         "id" : id,
133                         "type" : self.TYPE_RESULT,
134                         "success" : True,
135                         "result" : result,
136                 }
137                 self.sendJSON(msg)
138
139         def sendError(self, id, code, message=None):
140                 data = {
141                         "id" : id,
142                         "type" : self.TYPE_RESULT,
143                         "success" : False,
144                         "error" : {
145                                 "code" : code,
146                                 "message" : message,
147                         }
148                 }
149                 self.sendJSON(data)
150
151         def sendJSON(self, msg):
152                 if not msg.has_key("id"):
153                         self._requestID += 1
154                         msg['id'] = self._requestID
155                 msg = json.dumps(msg).encode('utf8')
156                 Log.d("< %s" % (msg))
157                 self.sendMessage(msg)
158
159         def onClose(self, wasClean, code, reason):
160                 print "WebSocket connection closed: {0}".format(code)
161
162         def validate(self, msg, validator):
163                 try:
164                         return validator(msg)
165                 except Exception as e:
166                         self.sendError(msg.get("id", -1), -1, "INVALID CALL! %s" %e)
167
168         def do_auth(self, msg):
169                 msg = self.validate(msg, self.AUTH_MESSAGE_SCHEMA)
170                 if not msg:
171                         return
172                 id = msg['id']
173                 if 'session' in msg:
174                         session = msg['session']
175                         if self.server.checkSession(session):
176                                 Log.w("session: %s" %(session))
177                                 self._sessionId = session
178                                 self.sendAuthOk()
179                                 return
180                         else:
181                                 self.sendAuthRequest()
182                                 return
183                 token = msg['token']
184                 login = b64decode(token)
185                 user, password = login.split(":", 1)
186                 if check_passwd(user, password):
187                         self._authenticated = True
188                         self.sendAuthOk()
189                         return
190
191                 self._authenticated = False
192                 self._failedAuthCount += 1
193                 if self._failedAuthCount >= self.AUTH_FAIL_LIMIT:
194                         self._disconnect()
195                 result = {}
196                 self.sendError(id, 401, "Login failed! Wrong Credentials?")
197
198         def do_get_services(self, msg):
199                 msg = self.validate(msg, self.GET_SERVICES_MESSAGE_SCHEMA)
200                 if not msg:
201                         return
202                 id = msg['id']
203                 ref = msg['reference']
204                 sref =  eServiceReference(str(msg['reference']))
205
206                 svcs = []
207                 serviceHandler = eServiceCenter.getInstance()
208                 info = serviceHandler.info(sref)
209                 sref.setName(info and info.getName(sref) or '')
210
211                 services = serviceHandler.list(sref)
212                 services = ( services and services.getContent('SN', True) ) or []
213                 for service in services:
214                         svcs.append({
215                                 "reference" : service[0],
216                                 "name" : service[1]
217                         })
218
219                 res = {
220                         "reference" : ref,
221                         "name" : sref.getName(),
222                         "data" : svcs
223                 }
224                 self.sendResult(id, res)
225
226         def do_get_epg_nownext(self, msg):
227                 msg = self.validate(msg, self.GET_EPG_NOWNEXT_MESSAGE_SCHEMA)
228                 if not msg:
229                         return
230                 id = msg['id']
231                 ref = str(msg['reference'])
232                 which = msg['which']
233
234                 serviceHandler = eServiceCenter.getInstance()
235                 sref = eServiceReference(ref)
236
237                 info = serviceHandler.info(sref)
238                 sref.setName(info and info.getName(sref) or '')
239
240                 lst = serviceHandler.list(sref)
241                 services = lst and lst.getContent('S', True)
242                 search = ['IBDCTSERNX']
243
244                 if services: # It's a Bouquet
245                         self.isBouquet = True
246                         if which == -1: #Now AND Next at once!
247                                 append = search.append
248                                 for service in services:
249                                         append((service, 0, -1))
250                                         append((service, 1, -1))
251                         else:
252                                 search.extend([(service, which, -1) for service in services])
253                 else:
254                         search.append(ref, which, -1)
255                 events = eEPGCache.getInstance().lookupEvent(search)
256                 def evtToDict(event):
257                         return {
258                                         "id" : event[0],                                        #I
259                                         "begin" : event[1],                                     #B
260                                         "duration" : event[2],                          #D
261                                         "current" : event[3],                           #C
262                                         "title" : event[4],                                     #T
263                                         "short_description" : event[5],         #S
264                                         "extended_description" : event[6],      #E
265                                         "reference" : event[7],                         #R
266                                         "name" : event[8],                                      #N
267                                 }
268
269                 evts = []
270                 if self.isBouquet:
271                         for event in events:
272                                 #IBDCTSERNX
273                                 evts.append(evtToDict(event))
274                 else:
275                         evts.append(evtToDict(events))
276
277                 res = {
278                         "reference" : ref,
279                         "name" : sref.getName(),
280                         "data" : evts or ()
281                 }
282                 self.sendResult(id, res)
283
284         def do_get_stream_settings(self, msg):
285                 msg = self.validate(msg, self.GET_STREAM_SETTINGS_SCHEMA)
286                 if not msg:
287                         return
288                 id = msg['id']
289                 result = {
290                         "source"                        : streamServerControl.config.streamserver.source.value,
291                         "audioBitrate"          : streamServerControl.config.streamserver.audioBitrate.value,
292                         "videoBitrate"          : streamServerControl.config.streamserver.videoBitrate.value,
293                         "resolution"            : streamServerControl.config.streamserver.resolution.value,
294                         "framerate"                     : streamServerControl.config.streamserver.framerate.value,
295                         "gopLength"                     : streamServerControl.config.streamserver.gopLength.value,
296                         "gopOnSceneChange"      : streamServerControl.config.streamserver.gopOnSceneChange.value,
297                         "openGop"                       : streamServerControl.config.streamserver.openGop.value,
298                         "bFrames"                       : streamServerControl.config.streamserver.bFrames.value,
299                         "pFrames"                       : streamServerControl.config.streamserver.pFrames.value,
300                         "slices"                        : streamServerControl.config.streamserver.slices.value,
301                         "level"                         : streamServerControl.config.streamserver.level.value,
302                         "profile"                       : streamServerControl.config.streamserver.profile.value,
303                         "lastService"           : streamServerControl.config.streamserver.lastservice.value,
304                 }
305                 self.sendResult(id, result)
306
307         def do_ping(self, msg):
308                 self.sendJSON({"type" : self.TYPE_PING})