2
# This file is part of Checkbox.
4
# Copyright 2011 Canonical Ltd.
6
# Checkbox is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
11
# Checkbox is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
24
from datetime import (
28
from cStringIO import StringIO
30
from checkbox.parsers.device import DeviceResult
31
from checkbox.parsers.udev import UdevParser
32
from checkbox.parsers.utils import implement_from_dict
35
import xml.etree.cElementTree as etree
37
import cElementTree as etree
40
_time_regex = re.compile(r"""
41
^(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)
42
T(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)
43
(?:\.(?P<second_fraction>\d{0,6}))?
45
(?:(?P<tz_sign>[-+])(?P<tz_hour>\d\d):(?P<tz_minute>\d\d))
51
class HALDevice(object):
53
def __init__(self, id, udi, properties):
56
self.properties = properties
59
class SubmissionParser(object):
61
default_name = "unknown"
63
def __init__(self, stream, name=None):
65
self.name = name or self.default_name
67
def _getClient(self, node):
68
return "_".join([node.get('name'), node.get('version')])
70
def _getProperty(self, node):
71
"""Parse a <property> node.
73
:return: (name, (value, type)) of a property.
75
return (node.get('name'), self._getValueAttribute(node))
77
def _getProperties(self, node):
78
"""Parse <property> sub-nodes of node.
80
:return: A dictionary, where each key is the name of a property;
81
the values are the tuples (value, type) of a property.
84
for child in node.getchildren():
85
name, value = self._getProperty(child)
86
if name in properties:
88
'<property name="%s"> found more than once in <%s>'
90
properties[name] = value
94
def _getValueAttribute(self, node):
95
"""Return (value, type) of a <property> or <value> node."""
96
type_ = node.get('type')
97
if type_ in ('dbus.Boolean', 'bool'):
98
value = node.text.strip() == 'True'
100
elif type_ in ('str', 'dbus.String', 'dbus.UTF8String'):
101
value = node.text.strip()
103
elif type_ in ('dbus.Byte', 'dbus.Int16', 'dbus.Int32', 'dbus.Int64',
104
'dbus.UInt16', 'dbus.UInt32', 'dbus.UInt64', 'int',
106
value = int(node.text.strip())
108
elif type_ in ('dbus.Double', 'float'):
109
value = float(node.text.strip())
111
elif type_ in ('dbus.Array', 'list'):
112
value = [self._getValueAttribute(child)
113
for child in node.getchildren()]
115
elif type_ in ('dbus.Dictionary', 'dict'):
116
value = dict((child.get('name'), self._getValueAttribute(child))
117
for child in node.getchildren())
120
# This should not happen.
121
raise AssertionError(
122
'Unexpected <property> or <value> type: %s' % type_)
126
def _getValueAttributeAsBoolean(self, node):
127
"""Return the value of the attribute "value" as a boolean."""
128
return node.attrib['value'] == "True"
130
def _getValueAttributeAsString(self, node):
131
"""Return the value of the attribute "value"."""
132
return node.attrib['value']
134
def _getValueAttributeAsDateTime(self, node):
135
"""Convert a "value" attribute into a datetime object."""
136
time_text = node.get('value')
138
# we cannot use time.strptime: this function accepts neither fractions
139
# of a second nor a time zone given e.g. as '+02:30'.
140
match = _time_regex.search(time_text)
144
'Timestamp with unreasonable value: %s' % time_text)
146
time_parts = match.groupdict()
148
year = int(time_parts['year'])
149
month = int(time_parts['month'])
150
day = int(time_parts['day'])
151
hour = int(time_parts['hour'])
152
minute = int(time_parts['minute'])
153
second = int(time_parts['second'])
154
second_fraction = time_parts['second_fraction']
155
if second_fraction is not None:
156
milliseconds = second_fraction + '0' * (6 - len(second_fraction))
157
milliseconds = int(milliseconds)
163
milliseconds = 999999
165
timestamp = datetime(year, month, day, hour, minute, second,
166
milliseconds, tzinfo=pytz.timezone('utc'))
168
tz_sign = time_parts['tz_sign']
169
tz_hour = time_parts['tz_hour']
170
tz_minute = time_parts['tz_minute']
171
if tz_sign in ('-', '+'):
172
delta = timedelta(hours=int(tz_hour), minutes=int(tz_minute))
174
timestamp = timestamp + delta
176
timestamp = timestamp - delta
180
def _parseHAL(self, result, node):
181
result.startDevices()
182
for child in node.getchildren():
183
id = int(child.get('id'))
184
udi = child.get('udi')
185
properties = self._getProperties(child)
186
device = HALDevice(id, udi, properties)
187
result.addDevice(device)
191
def _parseInfo(self, result, node):
192
command = node.attrib['command']
193
if command == "udevadm info --export-db":
194
self._parseUdev(result, node)
196
result.addInfo(command, node.text)
198
def _parseUdev(self, result, node):
199
result.startDevices()
201
stream = StringIO(node.text)
202
udev = UdevParser(stream)
207
def _parseProcessors(self, result, node):
208
result.startProcessors()
210
for child in node.getchildren():
211
id = int(child.get('id'))
212
name = child.get('name')
213
properties = self._getProperties(child)
214
result.addProcessor(id, name, properties)
216
result.endProcessors()
218
def _parseRoot(self, result, node):
220
"summary": self._parseSummary,
221
"hardware": self._parseHardware,
222
"software": self._parseSoftware,
223
"questions": self._parseQuestions,
224
"context": self._parseContext,
227
for child in node.getchildren():
228
parser = parsers.get(child.tag, self._parseNone)
229
parser(result, child)
231
def _parseSummary(self, result, node):
233
'live_cd': self._getValueAttributeAsBoolean,
234
'system_id': self._getValueAttributeAsString,
235
'distribution': self._getValueAttributeAsString,
236
'distroseries': self._getValueAttributeAsString,
237
'architecture': self._getValueAttributeAsString,
238
'private': self._getValueAttributeAsBoolean,
239
'contactable': self._getValueAttributeAsBoolean,
240
'date_created': self._getValueAttributeAsDateTime,
241
'client': self._getClient,
242
'kernel-release': self._getValueAttributeAsString,
245
for child in node.getchildren():
246
parser = parsers.get(child.tag, self._parseNone)
247
value = parser(child)
248
result.addSummary(child.tag, value)
250
def _parseHardware(self, result, node):
252
'hal': self._parseHAL,
253
'processors': self._parseProcessors,
254
'udev': self._parseUdev,
257
for child in node.getchildren():
258
parser = parsers.get(child.tag, self._parseNone)
259
parser(result, child)
261
def _parseLSBRelease(self, result, node):
262
properties = self._getProperties(node)
263
result.setDistribution(**properties)
265
def _parsePackages(self, result, node):
266
result.startPackages()
268
for child in node.getchildren():
269
id = int(child.get('id'))
270
name = child.get('name')
271
properties = self._getProperties(child)
273
result.addPackage(id, name, properties)
277
def _parseXOrg(self, result, node):
279
for child in node.getchildren():
280
info = dict(child.attrib)
282
info['device'] = int(info['device'])
287
'<driver name="%s"> appears more than once in <xorg>'
292
version = node.get('version')
293
result.addXorg(version, drivers)
295
def _parseSoftware(self, result, node):
297
'lsbrelease': self._parseLSBRelease,
298
'packages': self._parsePackages,
299
'xorg': self._parseXOrg,
302
for child in node.getchildren():
303
parser = parsers.get(child.tag, self._parseNone)
304
parser(result, child)
306
def _parseQuestions(self, result, node):
307
result.startQuestions()
309
for child in node.getchildren():
310
question = {'name': child.get('name')}
311
plugin = child.get('plugin', None)
312
if plugin is not None:
313
question['plugin'] = plugin
314
question['targets'] = targets = []
317
for sub_node in child.getchildren():
318
sub_tag = sub_node.tag
319
if sub_tag == 'answer':
320
question['answer'] = answer = {}
321
answer['type'] = sub_node.get('type')
322
if answer['type'] == 'multiple_choice':
323
question['answer_choices'] = answer_choices
324
unit = sub_node.get('unit', None)
326
answer['unit'] = unit
327
answer['value'] = sub_node.text.strip()
328
elif sub_tag == 'answer_choices':
329
for value_node in sub_node.getchildren():
330
answer_choices.append(
331
self._getValueAttribute(value_node))
332
elif sub_tag == 'target':
333
target = {'id': int(sub_node.get('id'))}
334
target['drivers'] = drivers = []
335
for driver_node in sub_node.getchildren():
336
drivers.append(driver_node.text.strip())
337
targets.append(target)
338
elif sub_tag in('comment', 'command'):
341
question[sub_tag] = data.strip()
343
result.addQuestion(question)
345
result.endQuestions()
347
def _parseContext(self, result, node):
349
'info': self._parseInfo,
352
for child in node.getchildren():
353
parser = parsers.get(child.tag, self._parseNone)
354
parser(result, child)
356
def _parseNone(self, result, node):
359
def run(self, result):
360
parser = etree.XMLParser()
363
tree = etree.parse(self.stream, parser=parser)
364
except SyntaxError, error:
368
root = tree.getroot()
369
if root.tag != "system":
370
logging.error("Root node is not '<system>'")
373
self._parseRoot(result, root)
376
SubmissionResult = implement_from_dict("SubmissionResult", [
377
"startDevices", "endDevices", "addDevice", "startPackages",
378
"endPackages", "addPackage", "startProcessors", "endProcessors",
379
"addProcessor", "startQuestions", "endQuestions", "addQuestion",
380
"addInfo", "addSummary", "addXorg", "setDistribution"], DeviceResult)