1
# =============================================================================
3
# Remuco - A remote control system for media players.
4
# Copyright (C) 2006-2009 Oben Sonne <obensonne@googlemail.com>
6
# This file is part of Remuco.
8
# Remuco is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# Remuco is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with Remuco. If not, see <http://www.gnu.org/licenses/>.
21
# =============================================================================
28
from remuco import log
45
NET_ENCODING = "UTF-8" # codec for data exchanged with clients
46
NET_ENCODING_ALT = ("UTF-8", "UTF8", "utf-8", "utf8") # synonyms
47
HOST_ENCODING = NET_ENCODING # will be updated with value from config file
49
def __init__(self, buff=None):
51
self.__data = buff or array.array('c')
55
if isinstance(self.__data, basestring):
57
elif isinstance(self.__data, array.array):
58
return self.__data.tostring()
60
log.error("** BUG ** unexpected buffer type")
62
def read_boolean(self):
72
y = struct.unpack_from('b', self.__data, offset=self.__off)[0]
78
n = struct.unpack_from('!h', self.__data, offset=self.__off)[0]
84
i = struct.unpack_from('!i', self.__data, offset=self.__off)[0]
90
l = struct.unpack_from('!q', self.__data, offset=self.__off)[0]
94
def read_string(self):
97
The read raw string will be converted from Bin.NET_ENCODING to
101
s = self.__read_string()
103
if Bin.HOST_ENCODING not in Bin.NET_ENCODING_ALT:
105
s = unicode(s, Bin.NET_ENCODING).encode(Bin.HOST_ENCODING)
106
except UnicodeDecodeError, e:
107
log.warning("could not decode '%s' with codec %s (%s)" %
108
(s, Bin.NET_ENCODING, e))
109
except UnicodeEncodeError, e:
110
log.warning("could not encode '%s' with codec %s (%s)" %
111
(s, Bin.HOST_ENCODING, e))
115
def read_type(self, expected):
117
type = self.read_byte()
120
log.warning("bin data malformed (expected type %d, have %d)" %
126
def read_array_boolean(self):
128
return self.__read_array(self.read_boolean)
130
def read_array_byte(self):
132
return self.__read_array(self.read_byte)
134
def read_array_short(self):
136
return self.__read_array(self.read_short)
138
def read_array_int(self):
140
return self.__read_array(self.read_int)
142
def read_array_long(self):
144
return self.__read_array(self.read_long)
146
def read_array_string(self):
148
return self.__read_array(self.read_string)
150
def __read_string(self):
151
""" Read a string as it is, i.e. without any codec conversion. """
153
l = self.read_short()
154
s = struct.unpack_from('%ds' % l, self.__data, offset=self.__off)[0]
158
def __read_array(self, fn_read_element):
160
num = self.read_int()
164
for i in range(0,num):
166
a.append(fn_read_element())
170
def get_unused_data(self):
172
return len(self.__data) - self.__off
174
def write_type(self, type):
176
self.write_byte(type)
178
def write_boolean(self, b):
185
def write_byte(self, y):
188
self.__data.extend(' ' * 1)
189
struct.pack_into('b', self.__data, self.__off, y)
192
def write_short(self, n):
195
self.__data.extend(' ' * 2)
196
struct.pack_into('!h', self.__data, self.__off, n)
199
def write_int(self, i):
202
self.__data.extend(' ' * 4)
203
struct.pack_into('!i', self.__data, self.__off, i)
206
def write_long(self, l):
209
self.__data.extend(' ' * 8)
210
struct.pack_into('!q', self.__data, self.__off, l)
213
def write_string(self, s):
216
If the string is a unicode string, it will be encoded as a normal string
217
in Bin.NET_ENCODING. If it already is a normal string it will be
218
converted from Bin.HOST_ENCODING to Bin.NET_ENCODING.
222
self.__write_string(s)
225
if isinstance(s, unicode):
228
s = s.encode(Bin.NET_ENCODING)
229
except UnicodeEncodeError, e:
230
log.warning("could not encode '%s' with codec %s (%s)" %
231
(s, Bin.NET_ENCODING, e))
234
elif Bin.HOST_ENCODING not in Bin.NET_ENCODING_ALT:
235
log.debug("convert '%s' from %s to %s" %
236
(s, Bin.HOST_ENCODING, Bin.NET_ENCODING))
238
s = unicode(s, Bin.HOST_ENCODING).encode(Bin.NET_ENCODING)
239
except UnicodeDecodeError, e:
240
log.warning("could not decode '%s' with codec %s (%s)" %
241
(s, Bin.HOST_ENCODING, e))
242
except UnicodeEncodeError, e:
243
log.warning("could not encode '%s' with codec %s (%s)" %
244
(s, Bin.NET_ENCODING, e))
246
self.__write_string(s)
248
def write_array_boolean(self, ba):
250
self.__write_array(ba, self.write_boolean)
252
def write_array_byte(self, ba):
254
if isinstance(ba, str): # byte sequences often come as strings
255
self.__write_string(ba, len_as_int=True)
257
self.__write_array(ba, self.write_byte)
259
def write_array_short(self, na):
261
self.__write_array(na, self.write_short)
263
def write_array_int(self, ia):
265
self.__write_array(ia, self.write_int)
267
def write_array_long(self, ia):
269
self.__write_array(ia, self.write_long)
271
def write_array_string(self, sa):
273
self.__write_array(sa, self.write_string)
275
def __write_string(self, s, len_as_int=False):
278
The string is written as is, i.e. there is no codec conversion.
291
self.__data.extend(' ' * l)
292
struct.pack_into('%ds' % l, self.__data, self.__off, s)
295
def __write_array(self, a, fn_element_write):
306
fn_element_write(a[i])
308
class Serializable(object):
312
raise NotImplementedError
316
raise NotImplementedError
318
def set_data(self, data):
320
raise NotImplementedError
322
def pack(serializable):
324
fmt = serializable.get_fmt()
326
data = serializable.get_data()
328
if len(fmt) != len(data):
329
log.error("** BUG ** format string and data differ in length")
332
#log.debug("data to pack: %s" % str(data))
338
for i in range(0,len(fmt)):
346
bin.write_byte(data[i])
350
bin.write_boolean(data[i])
354
bin.write_short(data[i])
358
bin.write_int(data[i])
362
bin.write_long(data[i])
366
bin.write_string(data[i])
368
elif type == TYPE_AB:
370
bin.write_array_boolean(data[i])
372
elif type == TYPE_AY:
374
bin.write_array_byte(data[i])
376
elif type == TYPE_AN:
378
bin.write_array_short(data[i])
380
elif type == TYPE_AI:
382
bin.write_array_int(data[i])
384
elif type == TYPE_AL:
386
bin.write_array_long(data[i])
388
elif type == TYPE_AS:
390
bin.write_array_string(data[i])
393
log.error("** BUG ** unknown type (%d) in format string" % type)
396
except struct.error, e:
398
log.exception("** BUG ** %s" % e)
402
return bin.get_buff()
404
def unpack(serializable, bytes):
405
""" Deserialize a Serializable.
408
the Serializable to apply the binary data to (may be a class, in which
409
case a new instance of this class is created)
411
binary data (serialized Serializable)
413
@return: 'serializable' itself if it is an instance of Serializable, a new
414
instance of 'serializable' if it is a class or None if an error
418
if inspect.isclass(serializable):
419
serializable = serializable()
421
fmt = serializable.get_fmt()
423
if fmt and not bytes:
424
log.warning("there is no data to unpack")
429
bin = Bin(buff=bytes)
435
if not bin.read_type(type):
440
data.append(bin.read_byte())
444
data.append(bin.read_boolean())
448
data.append(bin.read_short())
452
data.append(bin.read_int())
456
data.append(bin.read_long())
460
data.append(bin.read_string())
462
elif type == TYPE_AB:
464
data.append(bin.read_array_boolean())
466
elif type == TYPE_AY:
468
data.append(bin.read_array_byte())
470
elif type == TYPE_AN:
472
data.append(bin.read_array_short())
474
elif type == TYPE_AI:
476
data.append(bin.read_array_int())
478
elif type == TYPE_AL:
480
data.append(bin.read_array_long())
482
elif type == TYPE_AS:
484
data.append(bin.read_array_string())
488
log.warning("bin data malformed (unknown data type: %d)" % type)
491
except struct.error, e:
493
log.warning("bin data malformed (%s)" % e)
497
unused = bin.get_unused_data()
499
log.warning("there are %d unused bytes" % unused)
502
serializable.set_data(data)
504
#log.debug("unpacked data : %s" % str(data))