| 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::extractMetadata() |
| 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 |
Definitions
Learn Advanced QML with KDAB
Find out more
