2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2011 Sebastien Helleu <flashcode@flashtux.org>
6 # This file is part of QWeeChat, a Qt remote GUI for WeeChat.
8 # QWeeChat 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 # QWeeChat 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 QWeeChat. If not, see <http://www.gnu.org/licenses/>.
23 # Decode binary messages received from WeeChat/relay.
25 # For info about protocol and format of messages, please read document "WeeChat Relay Protocol",
26 # available at: http://www.weechat.org/doc/
30 # 2011-11-23, Sebastien Helleu <flashcode@flashtux.org>:
37 def __init__(self
, objtype
, value
):
38 self
.objtype
= objtype
;
41 def _str_value(self
, v
):
42 if type(v
) is str and not v
is None:
46 def _str_value_hdata(self
):
48 ' keys: %s' % str(self
.value
['keys']),
49 ' path: %s' % str(self
.value
['path'])]
50 for i
, item
in enumerate(self
.value
['items']):
51 lines
.append(' item %d:' % (i
+ 1))
52 lines
.append('\n'.join([' %s: %s' % (key
, self
._str
_value
(value
)) for key
, value
in sorted(item
.items())]))
53 return '\n'.join(lines
)
55 def _str_value_infolist(self
):
56 lines
= ['', ' name: %s' % self
.value
['name']]
57 for i
, item
in enumerate(self
.value
['items']):
58 lines
.append(' item %d:' % (i
+ 1))
59 lines
.append('\n'.join([' %s: %s' % (key
, self
._str
_value
(value
)) for key
, value
in sorted(item
.items())]))
60 return '\n'.join(lines
)
62 def _str_value_other(self
):
63 return self
._str
_value
(self
.value
)
66 self
._obj
_cb
= {'hda': self
._str
_value
_hdata
,
67 'inl': self
._str
_value
_infolist
,
69 return '%s: %s' % (self
.objtype
, self
._obj
_cb
.get(self
.objtype
, self
._str
_value
_other
)())
72 class WeechatObjects(list):
74 return '\n'.join([str(obj
) for obj
in self
])
78 def __init__(self
, size
, size_uncompressed
, compression
, uncompressed
, msgid
, objects
):
80 self
.size_uncompressed
= size_uncompressed
81 self
.compression
= compression
82 self
.uncompressed
= uncompressed
84 self
.objects
= objects
87 if self
.compression
!= 0:
88 return 'size: %d/%d (%d%%), id=\'%s\', objects:\n%s' % (
89 self
.size
, self
.size_uncompressed
,
90 100 - ((self
.size
* 100) // self
.size_uncompressed
),
91 self
.msgid
, self
.objects
)
93 return 'size: %d, id=\'%s\', objects:\n%s' % (self
.size
, self
.msgid
, self
.objects
)
97 """Decode binary message received from WeeChat/relay."""
100 self
._obj
_cb
= {'chr': self
._obj
_char
,
101 'int': self
._obj
_int
,
102 'lon': self
._obj
_long
,
103 'str': self
._obj
_str
,
104 'buf': self
._obj
_buffer
,
105 'ptr': self
._obj
_ptr
,
106 'tim': self
._obj
_time
,
107 'htb': self
._obj
_hashtable
,
108 'hda': self
._obj
_hdata
,
109 'inf': self
._obj
_info
,
110 'inl': self
._obj
_infolist
,
114 """Read type in data (3 chars)."""
115 if len(self
.data
) < 3:
118 objtype
= str(self
.data
[0:3])
119 self
.data
= self
.data
[3:]
122 def _obj_len_data(self
, length_size
):
123 """Read length (1 or 4 bytes), then value with this length."""
124 if len(self
.data
) < length_size
:
128 length
= struct
.unpack('B', self
.data
[0:1])[0]
129 self
.data
= self
.data
[1:]
131 length
= self
._obj
_int
()
135 value
= self
.data
[0:length
]
136 self
.data
= self
.data
[length
:]
142 """Read a char in data."""
143 if len(self
.data
) < 1:
145 value
= struct
.unpack('b', self
.data
[0:1])[0]
146 self
.data
= self
.data
[1:]
150 """Read an integer in data (4 bytes)."""
151 if len(self
.data
) < 4:
154 value
= struct
.unpack('>i', self
.data
[0:4])[0]
155 self
.data
= self
.data
[4:]
159 """Read a long integer in data (length on 1 byte + value as string)."""
160 value
= self
._obj
_len
_data
(1)
163 return int(str(value
))
166 """Read a string in data (length on 4 bytes + content)."""
167 value
= self
._obj
_len
_data
(4)
172 def _obj_buffer(self
):
173 """Read a buffer in data (length on 4 bytes + data)."""
174 return self
._obj
_len
_data
(4)
177 """Read a pointer in data (length on 1 byte + value as string)."""
178 value
= self
._obj
_len
_data
(1)
181 return '0x%s' % str(value
)
184 """Read a time in data (length on 1 byte + value as string)."""
185 value
= self
._obj
_len
_data
(1)
190 def _obj_hashtable(self
):
191 """Read a hashtable in data (type for keys + type for values + count + items)."""
192 type_keys
= self
._obj
_type
()
193 type_values
= self
._obj
_type
()
194 count
= self
._obj
_int
()
196 for i
in range(0, count
):
197 key
= self
._obj
_cb
[type_keys
]()
198 value
= self
._obj
_cb
[type_values
]()
199 hashtable
[key
] = value
202 def _obj_hdata(self
):
203 """Read a hdata in data."""
204 path
= self
._obj
_str
()
205 keys
= self
._obj
_str
()
206 count
= self
._obj
_int
()
207 list_path
= path
.split('/')
208 list_keys
= keys
.split(',')
211 for key
in list_keys
:
212 items
= key
.split(':')
213 keys_types
.append(items
)
214 dict_keys
[items
[0]] = items
[1]
216 for i
in range(0, count
):
219 for p
in range(0, len(list_path
)):
220 pointers
.append(self
._obj
_ptr
())
221 for key
, objtype
in keys_types
:
222 item
[key
] = self
._obj
_cb
[objtype
]()
223 item
['__path'] = pointers
225 return {'path': list_path
,
232 """Read an info in data."""
233 name
= self
._obj
_str
()
234 value
= self
._obj
_str
()
237 def _obj_infolist(self
):
238 """Read an infolist in data."""
239 name
= self
._obj
_str
()
240 count_items
= self
._obj
_int
()
242 for i
in range(0, count_items
):
243 count_vars
= self
._obj
_int
()
245 for v
in range(0, count_vars
):
246 var_name
= self
._obj
_str
()
247 var_type
= self
._obj
_type
()
248 var_value
= self
._obj
_cb
[var_type
]()
249 variables
[var_name
] = var_value
250 items
.append(variables
)
251 return {'name': name, 'items': items}
253 def decode(self
, data
):
254 """Decode binary data and return list of objects."""
256 size
= len(self
.data
)
257 size_uncompressed
= size
259 # uncompress data (if it is compressed)
260 compression
= struct
.unpack('b', self
.data
[4:5])[0]
262 uncompressed
= zlib
.decompress(self
.data
[5:])
263 size_uncompressed
= len(uncompressed
) + 5
264 uncompressed
= '%s%s%s' % (struct
.pack('>i', size_uncompressed
), struct
.pack('b', 0), uncompressed
)
265 self
.data
= uncompressed
266 # skip length and compression flag
267 self
.data
= self
.data
[5:]
269 msgid
= self
._obj
_str
()
273 objects
= WeechatObjects()
274 while len(self
.data
) > 0:
275 objtype
= self
._obj
_type
()
276 value
= self
._obj
_cb
[objtype
]()
277 objects
.append(WeechatObject(objtype
, value
))
278 return WeechatMessage(size
, size_uncompressed
, compression
, uncompressed
, msgid
, objects
)
281 def hex_and_ascii(data
, bytes_per_line
=10):
282 """Convert a QByteArray to hex + ascii output."""
283 num_lines
= ((len(data
) - 1) // bytes_per_line
) + 1
287 for i
in range(0, num_lines
):
290 for char
in data
[i
*bytes_per_line
:(i
*bytes_per_line
)+bytes_per_line
]:
291 byte
= struct
.unpack('B', char
)[0]
292 str_hex
.append('%02X' % int(byte
))
293 if byte
>= 32 and byte
<= 127:
294 str_ascii
.append(char
)
296 str_ascii
.append('.')
297 fmt
= '%%-%ds %%s' % ((bytes_per_line
* 3) - 1)
298 lines
.append(fmt
% (' '.join(str_hex
), ''.join(str_ascii
)))
299 return '\n'.join(lines
)