1#!/usr/bin/env python
2#
3# Copyright (C) 2012 Adam Sutton <dev@adamsutton.me.uk>
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16#
17"""
18This is a very simple HTSP client library written in python mainly just
19for demonstration purposes.
20
21Much of the code is pretty rough, but might help people get started
22with communicating with HTSP server
23"""
24
25import tvh.log as log
26import tvh.htsmsg as htsmsg
27
28# ###########################################################################
29# HTSP Client
30# ###########################################################################
31
32HTSP_PROTO_VERSION = 25
33
34
35# Create passwd digest
36def htsp_digest(user, passwd, chal):
37    import hashlib
38    salted = passwd.encode('utf-8') + chal
39    return hashlib.sha1(salted).digest()
40
41
42# Client object
43class HTSPClient(object):
44    # Setup connection
45    def __init__(self, addr, name='HTSP PyClient'):
46        import socket
47
48        # Setup
49        self._sock = socket.create_connection(addr)
50        self._name = name
51        self._auth = None
52        self._user = None
53        self._auser = None
54        self._apass = None
55
56    # Send
57    def send(self, func, args={}):
58        args['method'] = func
59        if self._auser: args['username'] = self._auser
60        if self._apass: args['digest'] = htsmsg.HMFBin(self._apass)
61        log.debug('htsp tx:')
62        log.debug(args, pretty=True)
63        self._sock.send(htsmsg.serialize(args))
64
65    # Receive
66    def recv(self):
67        ret = htsmsg.deserialize(self._sock, False)
68        log.debug('htsp rx:')
69        log.debug(ret, pretty=True)
70        return ret
71
72    # Setup
73    def hello(self):
74        args = {
75            'htspversion': HTSP_PROTO_VERSION,
76            'clientname': self._name
77        }
78        self.send('hello', args)
79        resp = self.recv()
80
81        # Store
82        self._version = min(HTSP_PROTO_VERSION, resp['htspversion'])
83        self._auth = resp['challenge']
84
85        # Return response
86        return resp
87
88    # Authenticate
89    def authenticate(self, user, passwd=None):
90        self._auser = user
91        if passwd:
92            self._apass = htsp_digest(user, passwd, self._auth)
93        self.send('authenticate')
94        self._auser = None
95        self._apass = None
96        resp = self.recv()
97        if 'noaccess' in resp:
98            raise Exception('Authentication failed')
99        self._user = user
100
101    # Enable async receive
102    def enableAsyncMetadata(self, args={}):
103        self.send('enableAsyncMetadata', args)
104
105    def disconnect(self):
106        self._sock.close()
107
108