-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtsp.lua
More file actions
146 lines (128 loc) · 3.44 KB
/
htsp.lua
File metadata and controls
146 lines (128 loc) · 3.44 KB
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
local ls = require "lsocket"
local crypto = require "crypto"
local htsmsg = require "htsmsg"
local struct = require "struct"
local HTSP_PROTO_VERSION = 6
local htsp = {}
htsp.__index = htsp
-- This adds a metatable to the htsp table which makes it a callable table.
-- This is purely a style choice, it allows us to create htsp instances in
-- a manner similar to C++/Java constructor calls.
setmetatable(htsp,{
__call = function(cls,...)
return cls.new(...)
end,
})
-- support function, turn hex string rep in to actual binary data
function fromhex(s)
return (s:gsub('..',function(cc) return string.char(tonumber(cc,16)) end))
end
function htsp:connect()
self._socket,err = ls.connect(self.opts.host,self.opts.port)
if not self._socket
then
error(err)
end
ls.select(nil,{self._socket},2000)
end
function htsp:authenticate()
if self.challenge ~= nil and self.opts.pass ~= nil
then
self.digest = function() return fromhex(crypto.digest("sha1",self.opts.pass..self.challenge)) end
end
self:send({method="authenticate"})
end
function htsp:recv(option)
local ignoreQueue = option or false
-- return any previously received asynchronous messages first unless told not to
if ignoreQueue == false and #self.queue > 0
then
return table.remove(self.queue,1)
end
local sel=ls.select({self._socket},2000)
if type(sel)=="table"
then
local respsize=self._socket:recv(4)
if respsize:len() ~= 4
then
error("error reading from socket, could not read initial length bytes")
end
local respint = struct.unpack('>I4',respsize)
if respint > 0
then
-- read the response in as many chunks as required. string concat in lua can be
-- slow, so store the individual chunks in a table (quicker) for concatenation when
-- finished reading
local respbody = ""
local resptable = {}
local bytesread = 0
while bytesread < respint
do
local sel=ls.select({self._socket},2000)
if type(sel) == "table"
then
local temp = self._socket:recv(respint-bytesread)
bytesread = bytesread + temp:len()
table.insert(resptable,temp)
else
error("Comms problem - timed out waiting for data chunk from TVHeadend")
end
end
respbody = table.concat(resptable)
local response = htsmsg.deserialize(respsize..respbody)
return response
else
return {}
end
else
error("Timed out waiting for response")
end
end
function htsp:send(t)
if self.opts.user ~= nil then t.username=self.opts.user end
if self.digest ~= nil then t.digest=self.digest end
self.seq = self.seq + 1
t.seq = self.seq
local msg = htsmsg.serialize(t)
if self._socket
then
local sent,err = self._socket:send(msg)
if not sent or sent < msg:len()
then
error("error sending, bytes sent["..tostring(sent).."],err="..err)
end
local resp = {}
while resp.seq == nil or resp.seq ~= t.seq
do
resp = self:recv(true)
if resp.seq ~= t.seq
then
table.insert(self.queue,resp)
end
end
return resp
else
error("Not connected to TVHeadend")
end
end
function htsp:init(opts)
self.opts=opts
self.seq=0
self.queue={}
end
function htsp:hello()
self:send({method="hello",htspversion=HTSP_PROTO_VERSION,clientname="LuaClient"})
end
function htsp:enableAsyncMetadata()
self:send({method="enableAsyncMetadata"})
end
function htsp.new(_opts)
local o = {}
local opts = _opts or {}
if not opts.host then opts.host = "127.0.0.1" end
if not opts.port then opts.port = 9982 end
setmetatable(o,htsp)
o:init(opts)
return o
end
return htsp