1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtSystems module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #include "servicemetadata_p.h" |
35 | #include <QFile> |
36 | #include <QDataStream> |
37 | #include <QDebug> |
38 | #include "qserviceinterfacedescriptor_p.h" |
39 | |
40 | //XML tags and attributes |
41 | //General |
42 | #define NAME_TAG "name" |
43 | #define DESCRIPTION_TAG "description" |
44 | #define SERVICEFW_TAG "SFW" |
45 | #define XML_MAX "1.1" |
46 | |
47 | //Service related |
48 | #define SERVICE_TAG "service" |
49 | #define SERVICE_FILEPATH "filepath" |
50 | #define SERVICE_IPCADDRESS "ipcaddress" |
51 | |
52 | //Interface related |
53 | #define INTERFACE_TAG "interface" |
54 | #define INTERFACE_VERSION "version" |
55 | #define INTERFACE_CAPABILITY "capabilities" |
56 | #define INTERFACE_CUSTOM_PROPERTY "customproperty" |
57 | |
58 | //Service type prefix |
59 | #define SERVICE_IPC_PREFIX "_q_ipc_addr:" |
60 | |
61 | QT_BEGIN_NAMESPACE |
62 | |
63 | #ifndef QT_NO_DATASTREAM |
64 | SERVICEMETADATA_EXPORT QDataStream &operator<<(QDataStream &out, const ServiceMetaDataResults &r) |
65 | { |
66 | out << r.type << r.name << r.location; |
67 | out << r.description << r.interfaces << r.latestInterfaces; |
68 | |
69 | return out; |
70 | } |
71 | |
72 | SERVICEMETADATA_EXPORT QDataStream &operator>>(QDataStream &in, ServiceMetaDataResults &r) |
73 | { |
74 | in >> r.type >> r.name >> r.location; |
75 | in >> r.description >> r.interfaces >> r.latestInterfaces; |
76 | |
77 | return in; |
78 | } |
79 | #endif |
80 | |
81 | /* |
82 | \class ServiceMetaData |
83 | |
84 | |
85 | Utility class (used by service database) that offers support for |
86 | parsing metadata service xml registry file during service registration. \n |
87 | |
88 | It uses QXMLStreamReader class for parsing. Supproted Operations are: |
89 | - Parse the service and interfaces defined in XML file |
90 | - name, version, capabilitiesList, description and filePath of service can be retrieved |
91 | - each interface can be retrieved |
92 | */ |
93 | |
94 | /* |
95 | * Class constructor |
96 | * |
97 | * @param aXmlFilePath path to the xml file that describes the service. |
98 | */ |
99 | ServiceMetaData::ServiceMetaData(const QString &aXmlFilePath) |
100 | : xmlDevice(new QFile(aXmlFilePath)), |
101 | ownsXmlDevice(true), |
102 | serviceType(QService::Plugin), |
103 | latestError(0) |
104 | {} |
105 | |
106 | /* |
107 | * Class constructor |
108 | * |
109 | * @param device QIODevice that contains the XML data that describes the service. |
110 | */ |
111 | ServiceMetaData::ServiceMetaData(QIODevice *device) |
112 | : xmlDevice(device), |
113 | ownsXmlDevice(false), |
114 | serviceType(QService::Plugin), |
115 | latestError(0) |
116 | {} |
117 | |
118 | /* |
119 | * Class destructor |
120 | * |
121 | */ |
122 | ServiceMetaData::~ServiceMetaData() |
123 | { |
124 | if (ownsXmlDevice) |
125 | delete xmlDevice; |
126 | } |
127 | |
128 | /* |
129 | Sets the device containing the XML data that describes the service to \a device. |
130 | */ |
131 | void ServiceMetaData::setDevice(QIODevice *device) |
132 | { |
133 | clearMetadata(); |
134 | xmlDevice = device; |
135 | ownsXmlDevice = false; |
136 | } |
137 | |
138 | /* |
139 | Returns the device containing the XML data that describes the service. |
140 | */ |
141 | QIODevice *ServiceMetaData::device() const |
142 | { |
143 | return xmlDevice; |
144 | } |
145 | |
146 | /* |
147 | * Gets the service name |
148 | * |
149 | * @return service name or default value (empty string) if it is not available |
150 | */ |
151 | /*QString ServiceMetaData::name() const |
152 | { |
153 | return serviceName; |
154 | }*/ |
155 | |
156 | /* |
157 | * Gets the path of the service implementation file |
158 | * |
159 | * @return service implementation filepath |
160 | */ |
161 | /*QString ServiceMetaData::location() const |
162 | { |
163 | return serviceLocation; |
164 | }*/ |
165 | |
166 | /* |
167 | * Gets the service description |
168 | * |
169 | * @return service description or default value (empty string) if it is not available |
170 | */ |
171 | /*QString ServiceMetaData::description() const |
172 | { |
173 | return serviceDescription; |
174 | }*/ |
175 | |
176 | /* |
177 | Returns the metadata of the interace at \a index; otherwise |
178 | returns 0. |
179 | */ |
180 | /*QList<QServiceInterfaceDescriptor> ServiceMetaData::getInterfaces() const |
181 | { |
182 | return serviceInterfaces; |
183 | } */ |
184 | |
185 | /*! |
186 | \internal |
187 | |
188 | Returns a streamable object containing the results of the parsing. |
189 | */ |
190 | ServiceMetaDataResults ServiceMetaData::parseResults() const |
191 | { |
192 | ServiceMetaDataResults results; |
193 | results.type = serviceType; |
194 | results.location = serviceLocation; |
195 | results.name = serviceName; |
196 | results.description = serviceDescription; |
197 | results.interfaces = serviceInterfaces; |
198 | results.latestInterfaces = latestInterfaces(); |
199 | |
200 | return results; |
201 | } |
202 | |
203 | /* |
204 | Parses the file and extracts the service metadata \n |
205 | Custom error codes: \n |
206 | SFW_ERROR_UNABLE_TO_OPEN_FILE in case can not open the XML file \n |
207 | SFW_ERROR_INVALID_XML_FILE in case service registry is not a valid XML file \n |
208 | SFW_ERROR_NO_SERVICE in case XML file has no service tag\n |
209 | @return true if the metadata was read properly, false if there is an error |
210 | */ |
211 | bool ServiceMetaData::() |
212 | { |
213 | Q_ASSERT(checkVersion(QLatin1String(XML_MAX))); |
214 | |
215 | latestError = 0; |
216 | clearMetadata(); |
217 | QXmlStreamReader xmlReader; |
218 | bool parseError = false; |
219 | //Open xml file |
220 | if (!xmlDevice->isOpen() && !xmlDevice->open(mode: QIODevice::ReadOnly)) { |
221 | qWarning() << xmlDevice->errorString(); |
222 | latestError = ServiceMetaData::SFW_ERROR_UNABLE_TO_OPEN_FILE; |
223 | parseError = true; |
224 | } else { |
225 | //Load xml content |
226 | xmlReader.setDevice(xmlDevice); |
227 | // Read XML doc |
228 | while (!xmlReader.atEnd() && !parseError) { |
229 | xmlReader.readNext(); |
230 | //Found <SFW> xml versioning tag introduced in 1.1 |
231 | //If this tag is not found the XML parser version will be 1.0 |
232 | if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String(SERVICEFW_TAG)) { |
233 | if (!processVersionElement(aXMLReader&: xmlReader)) { |
234 | parseError = true; |
235 | } |
236 | } |
237 | //Found a <service> node, read service related metadata |
238 | else if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String(SERVICE_TAG)) { |
239 | if (!processServiceElement(aXMLReader&: xmlReader)) { |
240 | parseError = true; |
241 | } |
242 | } |
243 | else if (xmlReader.isStartElement() && xmlReader.name() != QLatin1String(SERVICE_TAG) |
244 | && xmlReader.name() != QLatin1String(SERVICEFW_TAG)) { |
245 | latestError = ServiceMetaData::SFW_ERROR_NO_SERVICE; |
246 | parseError = true; |
247 | } |
248 | else if (xmlReader.tokenType() == QXmlStreamReader::Invalid) { |
249 | latestError = ServiceMetaData::SFW_ERROR_INVALID_XML_FILE; |
250 | parseError = true; |
251 | } |
252 | } |
253 | if (ownsXmlDevice) |
254 | xmlDevice->close(); |
255 | } |
256 | |
257 | if (parseError) { |
258 | //provide better error reports |
259 | switch (latestError) { |
260 | case SFW_ERROR_NO_SERVICE: /* Can not find service root node in XML file*/ |
261 | qDebug() << "Missing <service> tag" ; |
262 | break; |
263 | case SFW_ERROR_NO_SERVICE_NAME: /* Can not find service name in XML file */ |
264 | qDebug() << "Missing or empty <name> tag within <service>" ; |
265 | break; |
266 | case SFW_ERROR_NO_SERVICE_PATH: /* Can not find service filepath or ipcaddress in XML file */ |
267 | if (greaterThan(v1: xmlVersion, v2: QLatin1String("1.0" ))) |
268 | qDebug() << "Missing or empty <filepath> or <ipcaddress> tag within <service>" ; |
269 | else |
270 | qDebug() << "Missing or empty <filepath> tag within <service>" ; |
271 | break; |
272 | case SFW_ERROR_NO_SERVICE_INTERFACE: /* No interface for the service in XML file*/ |
273 | qDebug() << "Missing <interface> tag" ; |
274 | break; |
275 | case SFW_ERROR_NO_INTERFACE_VERSION: /* Can not find interface version in XML file */ |
276 | qDebug() << "Missing or empty <version> tag within <interface>" ; |
277 | break; |
278 | case SFW_ERROR_NO_INTERFACE_NAME: /* Can not find interface name in XML file*/ |
279 | qDebug() << "Missing or empty <name> tag within <interface>" ; |
280 | break; |
281 | case SFW_ERROR_UNABLE_TO_OPEN_FILE: /* Error opening XML file*/ |
282 | qDebug() << "Unable to open service xml file" ; |
283 | break; |
284 | case SFW_ERROR_INVALID_XML_FILE: /* Not a valid XML file*/ |
285 | qDebug() << "Not a valid service xml" ; |
286 | break; |
287 | case SFW_ERROR_PARSE_SERVICE: /* Error parsing service node */ |
288 | qDebug().nospace() << "Invalid tag within <service> with the supplied version(" |
289 | << xmlVersion << ")" ; |
290 | break; |
291 | case SFW_ERROR_PARSE_INTERFACE: /* Error parsing interface node */ |
292 | qDebug() << "Invalid tag within <interface> tags" ; |
293 | break; |
294 | case SFW_ERROR_DUPLICATED_INTERFACE: /* The same interface is defined twice */ |
295 | qDebug() << "The same interface has been defined more than once" ; |
296 | break; |
297 | case SFW_ERROR_INVALID_VERSION: |
298 | qDebug() << "Invalid version string, expected: x.y" ; |
299 | break; |
300 | case SFW_ERROR_DUPLICATED_TAG: /* The tag appears twice */ |
301 | qDebug() << "XML tag appears twice" ; |
302 | break; |
303 | case SFW_ERROR_INVALID_CUSTOM_TAG: /* The customproperty tag is not correctly formatted or otherwise incorrect*/ |
304 | qDebug() << "Invalid custom property tag" ; |
305 | break; |
306 | case SFW_ERROR_DUPLICATED_CUSTOM_KEY: /* The customproperty appears twice*/ |
307 | qDebug() << "Same custom property appears multiple times" ; |
308 | break; |
309 | case SFW_ERROR_MULTIPLE_SERVICE_TYPES: /* Both filepath and ipcaddress found in the XML file */ |
310 | qDebug() << "Cannot specify both <filepath> and <ipcaddress> tags within <service>" ; |
311 | break; |
312 | case SFW_ERROR_INVALID_FILEPATH: /* Service path cannot contain IPC prefix */ |
313 | qDebug() << "Invalid service location, avoid private prefixes" ; |
314 | break; |
315 | case SFW_ERROR_INVALID_XML_VERSION: /* Error parsing servicefw version node */ |
316 | qDebug() << "Invalid or missing version attribute in <SFW> tag" ; |
317 | break; |
318 | case SFW_ERROR_UNSUPPORTED_IPC: /* Servicefw version doesn't support IPC */ |
319 | qDebug().nospace() << "Supplied service framework version(" << xmlVersion |
320 | << ") doesn't support the <ipcaddress> tag" ; |
321 | break; |
322 | case SFW_ERROR_UNSUPPORTED_XML_VERSION: /* Unsupported servicefw version supplied */ |
323 | qDebug().nospace() << "Service framework version(" << xmlVersion |
324 | << ") is higher than available support(" << QLatin1String(XML_MAX) << ")" ; |
325 | break; |
326 | } |
327 | clearMetadata(); |
328 | } |
329 | return !parseError; |
330 | } |
331 | |
332 | /* |
333 | Gets the latest parsing error \n |
334 | @return parsing error(negative value) or 0 in case there is none |
335 | */ |
336 | int ServiceMetaData::getLatestError() const |
337 | { |
338 | return latestError; |
339 | } |
340 | |
341 | /* |
342 | Parses the service framework xml version tag and continues to parse the service |
343 | */ |
344 | bool ServiceMetaData::processVersionElement(QXmlStreamReader &aXMLReader) |
345 | { |
346 | Q_ASSERT(aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(SERVICEFW_TAG)); |
347 | bool parseError = false; |
348 | |
349 | if (aXMLReader.attributes().hasAttribute(qualifiedName: QLatin1String("version" ))) { |
350 | xmlVersion = aXMLReader.attributes().value(qualifiedName: QLatin1String("version" )).toString(); |
351 | bool success = checkVersion(version: xmlVersion); |
352 | |
353 | if (xmlVersion.isEmpty() || !success) { |
354 | latestError = ServiceMetaData::SFW_ERROR_INVALID_XML_VERSION; |
355 | parseError = true; |
356 | } else { |
357 | if (greaterThan(v1: xmlVersion, v2: QLatin1String(XML_MAX))) { |
358 | latestError = ServiceMetaData::SFW_ERROR_UNSUPPORTED_XML_VERSION; |
359 | parseError = true; |
360 | } |
361 | } |
362 | } else { |
363 | latestError = ServiceMetaData::SFW_ERROR_INVALID_XML_VERSION; |
364 | parseError = true; |
365 | } |
366 | |
367 | while (!parseError && !aXMLReader.atEnd()) { |
368 | aXMLReader.readNext(); |
369 | //Found a <service> node, read service related metadata |
370 | if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(SERVICE_TAG)) { |
371 | if (!processServiceElement(aXMLReader)) { |
372 | parseError = true; |
373 | } |
374 | } |
375 | else if (aXMLReader.isEndElement() && aXMLReader.name() == QLatin1String(SERVICEFW_TAG)) { |
376 | //Found </SFW>, leave the loop |
377 | break; |
378 | } |
379 | else if (aXMLReader.isStartElement() && aXMLReader.name() != QLatin1String(SERVICE_TAG)) { |
380 | latestError = ServiceMetaData::SFW_ERROR_NO_SERVICE; |
381 | parseError = true; |
382 | } |
383 | else if (aXMLReader.tokenType() == QXmlStreamReader::Invalid) { |
384 | latestError = ServiceMetaData::SFW_ERROR_INVALID_XML_FILE; |
385 | parseError = true; |
386 | } |
387 | } |
388 | |
389 | return !parseError; |
390 | } |
391 | |
392 | /* |
393 | Parses and extracts the service metadata from the current xml <service> node \n |
394 | */ |
395 | bool ServiceMetaData::processServiceElement(QXmlStreamReader &aXMLReader) |
396 | { |
397 | Q_ASSERT(aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(SERVICE_TAG)); |
398 | bool parseError = false; |
399 | |
400 | int dupSTags[4] = {0 //->tag name |
401 | ,0 //-> service description |
402 | ,0 //-> filepath |
403 | ,0 //-> ipcaddress |
404 | }; |
405 | while (!parseError && !aXMLReader.atEnd()) { |
406 | aXMLReader.readNext(); |
407 | if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(NAME_TAG)) { |
408 | //Found <name> tag |
409 | serviceName = aXMLReader.readElementText(); |
410 | dupSTags[0]++; |
411 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(DESCRIPTION_TAG)) { |
412 | //Found <description> tag |
413 | serviceDescription = aXMLReader.readElementText(); |
414 | dupSTags[1]++; |
415 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(SERVICE_FILEPATH) ) { |
416 | //Found <filepath> tag for plugin service |
417 | dupSTags[2]++; |
418 | serviceLocation = aXMLReader.readElementText(); |
419 | //Check if IPC prefix was used incorrectly here |
420 | if (serviceLocation.startsWith(s: QLatin1String(SERVICE_IPC_PREFIX))) { |
421 | latestError = ServiceMetaData::SFW_ERROR_INVALID_FILEPATH; |
422 | parseError = true; |
423 | } |
424 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(SERVICE_IPCADDRESS) ) { |
425 | //Found <ipcaddress> tag for IPC service |
426 | //Check if servicefw XML version supports IPC |
427 | if (greaterThan(v1: xmlVersion, v2: QLatin1String("1.0" ))) { |
428 | dupSTags[3]++; |
429 | serviceLocation = aXMLReader.readElementText(); |
430 | //Check if IPC prefix was used incorrectly here |
431 | if (serviceLocation.startsWith(s: QLatin1String(SERVICE_IPC_PREFIX))) { |
432 | latestError = ServiceMetaData::SFW_ERROR_INVALID_FILEPATH; |
433 | parseError = true; |
434 | } |
435 | } else { |
436 | latestError = ServiceMetaData::SFW_ERROR_UNSUPPORTED_IPC; |
437 | parseError = true; |
438 | } |
439 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(INTERFACE_TAG)) { |
440 | //Found interface> node, read module related metadata |
441 | if (!processInterfaceElement(aXMLReader)) |
442 | parseError = true; |
443 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String("version" )) { |
444 | //Found <version> tag on service level. We ignore this for now |
445 | aXMLReader.readElementText(); |
446 | } else if (aXMLReader.isEndElement() && aXMLReader.name() == QLatin1String(SERVICE_TAG)) { |
447 | //Found </service>, leave the loop |
448 | break; |
449 | } else if (aXMLReader.isEndElement() || aXMLReader.isStartElement()) { |
450 | latestError = ServiceMetaData::SFW_ERROR_PARSE_SERVICE; |
451 | parseError = true; |
452 | } else if (aXMLReader.tokenType() == QXmlStreamReader::Invalid) { |
453 | latestError = ServiceMetaData::SFW_ERROR_INVALID_XML_FILE; |
454 | parseError = true; |
455 | } |
456 | } |
457 | |
458 | if ( !parseError ) { |
459 | if (serviceName.isEmpty()) { |
460 | latestError = ServiceMetaData::SFW_ERROR_NO_SERVICE_NAME; |
461 | parseError = true; |
462 | } else if (serviceLocation.isEmpty()) { |
463 | latestError = ServiceMetaData::SFW_ERROR_NO_SERVICE_PATH; |
464 | parseError = true; |
465 | } |
466 | } |
467 | |
468 | if (dupSTags[3] > 0) |
469 | serviceType = QService::InterProcess; |
470 | |
471 | if ((dupSTags[2] > 0) && (dupSTags[3] > 0)) { |
472 | latestError = SFW_ERROR_MULTIPLE_SERVICE_TYPES; |
473 | parseError = true; |
474 | } |
475 | |
476 | for (int i=0; !parseError && i<4; i++) { |
477 | if (dupSTags[i] > 1) { |
478 | latestError = SFW_ERROR_DUPLICATED_TAG; |
479 | parseError = true; |
480 | break; |
481 | } |
482 | } |
483 | |
484 | //update all interfaces with service data |
485 | const int icount = serviceInterfaces.count(); |
486 | if (icount == 0 && latestError == 0) { |
487 | latestError = ServiceMetaData::SFW_ERROR_NO_SERVICE_INTERFACE; |
488 | parseError = true; |
489 | } |
490 | for (int i = 0; i<icount; i++) { |
491 | serviceInterfaces.at(i).d->serviceName = serviceName; |
492 | serviceInterfaces.at(i).d->attributes[QServiceInterfaceDescriptor::Location] = serviceLocation; |
493 | serviceInterfaces.at(i).d->attributes[QServiceInterfaceDescriptor::ServiceDescription] = serviceDescription; |
494 | serviceInterfaces.at(i).d->attributes[QServiceInterfaceDescriptor::ServiceType] = serviceType; |
495 | } |
496 | |
497 | return !parseError; |
498 | } |
499 | |
500 | /* |
501 | Parses and extracts the interface metadata from the current xml <interface> node \n |
502 | */ |
503 | bool ServiceMetaData::processInterfaceElement(QXmlStreamReader &aXMLReader) |
504 | { |
505 | Q_ASSERT(aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(INTERFACE_TAG)); |
506 | bool parseError = false; |
507 | |
508 | //Read interface parameter |
509 | QString tmp; |
510 | QServiceInterfaceDescriptor aInterface; |
511 | int dupITags[4] = { |
512 | 0, //->iface name tag |
513 | 0, //->version |
514 | 0, //->capabilities |
515 | 0 //->description |
516 | }; |
517 | aInterface.d = new QServiceInterfaceDescriptorPrivate; |
518 | |
519 | while (!parseError && !aXMLReader.atEnd()) { |
520 | aXMLReader.readNext(); |
521 | //Read interface description |
522 | if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(NAME_TAG)) { |
523 | aInterface.d->interfaceName = aXMLReader.readElementText(); |
524 | dupITags[0]++; |
525 | //Found <name> tag for interface |
526 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(DESCRIPTION_TAG)) { |
527 | //Found <description> tag |
528 | aInterface.d->attributes[QServiceInterfaceDescriptor::InterfaceDescription] = aXMLReader.readElementText(); |
529 | dupITags[3]++; |
530 | //Found </interface>, leave the loop |
531 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(INTERFACE_VERSION)) { |
532 | tmp.clear(); |
533 | tmp = aXMLReader.readElementText(); |
534 | if (tmp.isEmpty()) |
535 | continue; //creates NO_INTERFACE_VERSION error further below |
536 | bool success = checkVersion(version: tmp); |
537 | if ( success ) { |
538 | int majorVer = -1; |
539 | int minorVer = -1; |
540 | transformVersion(version: tmp, major: &majorVer, minor: &minorVer); |
541 | aInterface.d->major = majorVer; |
542 | aInterface.d->minor = minorVer; |
543 | dupITags[1]++; |
544 | } else { |
545 | latestError = ServiceMetaData::SFW_ERROR_INVALID_VERSION; |
546 | parseError = true; |
547 | } |
548 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(INTERFACE_CAPABILITY)) { |
549 | tmp.clear(); |
550 | tmp= aXMLReader.readElementText(); |
551 | aInterface.d->attributes[QServiceInterfaceDescriptor::Capabilities] = tmp.split(sep: QLatin1String("," ), behavior: QString::SkipEmptyParts); |
552 | dupITags[2]++; |
553 | } else if (aXMLReader.isStartElement() && aXMLReader.name() == QLatin1String(INTERFACE_CUSTOM_PROPERTY)) { |
554 | parseError = true; |
555 | if (aXMLReader.attributes().hasAttribute(qualifiedName: QLatin1String("key" ))) { |
556 | const QString ref = aXMLReader.attributes().value(qualifiedName: QLatin1String("key" )).toString(); |
557 | if (!ref.isEmpty()) { |
558 | if (aInterface.d->customAttributes.contains(akey: ref)) { |
559 | latestError = SFW_ERROR_DUPLICATED_CUSTOM_KEY; |
560 | continue; |
561 | } else { |
562 | QString value = aXMLReader.readElementText(); |
563 | if (value.isNull()) |
564 | value = QLatin1String("" ); |
565 | aInterface.d->customAttributes[ref] = value; |
566 | parseError = false; |
567 | } |
568 | } |
569 | } |
570 | if (parseError) |
571 | latestError = SFW_ERROR_INVALID_CUSTOM_TAG; |
572 | } else if (aXMLReader.isEndElement() && aXMLReader.name() == QLatin1String(INTERFACE_TAG)) { |
573 | break; |
574 | } else if (aXMLReader.isStartElement() || aXMLReader.isEndElement()) { |
575 | latestError = ServiceMetaData::SFW_ERROR_PARSE_INTERFACE; |
576 | parseError = true; |
577 | } else if (aXMLReader.tokenType() == QXmlStreamReader::Invalid) { |
578 | latestError = ServiceMetaData::SFW_ERROR_INVALID_XML_FILE; |
579 | parseError = true; |
580 | } |
581 | } |
582 | |
583 | if (!parseError) { |
584 | if (dupITags[1] == 0) { //no version tag found |
585 | latestError = ServiceMetaData::SFW_ERROR_NO_INTERFACE_VERSION; |
586 | parseError = true; |
587 | } else if (aInterface.d->interfaceName.isEmpty()) { |
588 | latestError = ServiceMetaData::SFW_ERROR_NO_INTERFACE_NAME; |
589 | parseError = true; |
590 | } |
591 | } |
592 | |
593 | for (int i=0;!parseError && i<4;i++) { |
594 | if (dupITags[i] > 1) { |
595 | parseError = true; |
596 | latestError = SFW_ERROR_DUPLICATED_TAG; |
597 | break; |
598 | } |
599 | } |
600 | |
601 | if (!parseError) { |
602 | const QString ident = aInterface.d->interfaceName |
603 | + QString::number(aInterface.majorVersion()) |
604 | + QLatin1String("." ) |
605 | + QString::number(aInterface.minorVersion()); |
606 | if (duplicates.contains(value: ident.toLower())) { |
607 | latestError = ServiceMetaData::SFW_ERROR_DUPLICATED_INTERFACE; |
608 | parseError = true; |
609 | } else { |
610 | duplicates.insert(value: ident.toLower()); |
611 | serviceInterfaces.append(t: aInterface); |
612 | if (!m_latestIndex.contains(akey: aInterface.d->interfaceName.toLower()) |
613 | || lessThan(d1: latestInterfaceVersion(interfaceName: aInterface.d->interfaceName), d2: aInterface)) |
614 | |
615 | { |
616 | m_latestIndex[aInterface.d->interfaceName.toLower()] = serviceInterfaces.count() - 1; |
617 | } |
618 | } |
619 | } |
620 | return !parseError; |
621 | } |
622 | |
623 | QServiceInterfaceDescriptor ServiceMetaData::latestInterfaceVersion(const QString &interfaceName) |
624 | { |
625 | QServiceInterfaceDescriptor ret; |
626 | if (m_latestIndex.contains(akey: interfaceName.toLower())) |
627 | return serviceInterfaces[m_latestIndex[interfaceName.toLower()]]; |
628 | else |
629 | return ret; |
630 | } |
631 | |
632 | QList<QServiceInterfaceDescriptor> ServiceMetaData::latestInterfaces() const |
633 | { |
634 | QList<QServiceInterfaceDescriptor> interfaces; |
635 | QHash<QString,int>::const_iterator i = m_latestIndex.constBegin(); |
636 | while (i != m_latestIndex.constEnd()) |
637 | { |
638 | interfaces.append(t: serviceInterfaces[i.value()]); |
639 | ++i; |
640 | } |
641 | return interfaces; |
642 | } |
643 | |
644 | bool ServiceMetaData::lessThan(const QServiceInterfaceDescriptor &d1, |
645 | const QServiceInterfaceDescriptor &d2) const |
646 | { |
647 | return (d1.majorVersion() < d2.majorVersion()) |
648 | || ( d1.majorVersion() == d2.majorVersion() |
649 | && d1.minorVersion() < d2.minorVersion()); |
650 | |
651 | } |
652 | |
653 | bool ServiceMetaData::greaterThan(const QString &v1, const QString &v2) const |
654 | { |
655 | int majorV1 = -1; |
656 | int minorV1 = -1; |
657 | transformVersion(version: v1, major: &majorV1, minor: &minorV1); |
658 | |
659 | int majorV2 = -1; |
660 | int minorV2 = -1; |
661 | transformVersion(version: v2, major: &majorV2, minor: &minorV2); |
662 | |
663 | return (majorV1 > majorV2 |
664 | || (majorV1 == majorV2 && minorV1 > minorV2)); |
665 | } |
666 | |
667 | bool ServiceMetaData::checkVersion(const QString &version) const |
668 | { |
669 | //match x.y as version format |
670 | QRegExp rx(QLatin1String("^([1-9][0-9]*)\\.(0+|[1-9][0-9]*)$" )); |
671 | int pos = rx.indexIn(str: version); |
672 | QStringList list = rx.capturedTexts(); |
673 | bool success = false; |
674 | if (pos == 0 && list.count() == 3 |
675 | && rx.matchedLength() == version.length() ) |
676 | { |
677 | list[1].toInt(ok: &success); |
678 | if ( success ) { |
679 | list[2].toInt(ok: &success); |
680 | } |
681 | } |
682 | return success; |
683 | } |
684 | |
685 | void ServiceMetaData::transformVersion(const QString &version, int *major, int *minor) const |
686 | { |
687 | Q_ASSERT(major != NULL); |
688 | Q_ASSERT(minor != NULL); |
689 | if (!checkVersion(version)) { |
690 | *major = -1; |
691 | *minor = -1; |
692 | } else { |
693 | QRegExp rx(QLatin1String("^([1-9][0-9]*)\\.(0+|[1-9][0-9]*)$" )); |
694 | rx.indexIn(str: version); |
695 | QStringList list = rx.capturedTexts(); |
696 | Q_ASSERT(list.count() == 3); |
697 | *major = list[1].toInt(); |
698 | *minor = list[2].toInt(); |
699 | } |
700 | } |
701 | |
702 | /* |
703 | * Clears the service metadata |
704 | * |
705 | */ |
706 | void ServiceMetaData::clearMetadata() |
707 | { |
708 | xmlVersion = QLatin1String("1.0" ); |
709 | serviceName.clear(); |
710 | serviceLocation.clear(); |
711 | serviceDescription.clear(); |
712 | serviceInterfaces.clear(); |
713 | duplicates.clear(); |
714 | m_latestIndex.clear(); |
715 | serviceType = QService::Plugin; |
716 | } |
717 | |
718 | QT_END_NAMESPACE |
719 | |