|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a xmlrpc client for Qt. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 import xmlrpclib as xmlrpc |
|
13 except ImportError: |
|
14 import xmlrpc.client as xmlrpc |
|
15 |
|
16 from PyQt5.QtCore import QObject, QUrl, QByteArray |
|
17 from PyQt5.QtNetwork import QNetworkAccessManager, \ |
|
18 QNetworkRequest, QNetworkReply |
|
19 |
|
20 from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired |
|
21 try: |
|
22 from E5Network.E5SslErrorHandler import E5SslErrorHandler |
|
23 SSL_AVAILABLE = True |
|
24 except ImportError: |
|
25 SSL_AVAILABLE = False |
|
26 |
|
27 |
|
28 class E5XmlRpcClient(QObject): |
|
29 """ |
|
30 Class implementing a xmlrpc client for Qt. |
|
31 """ |
|
32 def __init__(self, url, parent=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param url xmlrpc handler URL (string or QUrl) |
|
37 @param parent parent object (QObject) |
|
38 """ |
|
39 super(E5XmlRpcClient, self).__init__(parent) |
|
40 |
|
41 # attributes for the network objects |
|
42 self.__networkManager = QNetworkAccessManager(self) |
|
43 self.__networkManager.proxyAuthenticationRequired.connect( |
|
44 proxyAuthenticationRequired) |
|
45 self.__networkManager.finished.connect(self.__replyFinished) |
|
46 if SSL_AVAILABLE: |
|
47 self.__sslErrorHandler = E5SslErrorHandler(self) |
|
48 self.__networkManager.sslErrors.connect(self.__sslErrors) |
|
49 |
|
50 self.__callmap = {} |
|
51 |
|
52 self.__request = QNetworkRequest(QUrl(url)) |
|
53 self.__request.setRawHeader("User-Agent", "E5XmlRpcClient/1.0") |
|
54 self.__request.setHeader(QNetworkRequest.ContentTypeHeader, "text/xml") |
|
55 |
|
56 def setUrl(self, url): |
|
57 """ |
|
58 Public slot to set the xmlrpc handler URL. |
|
59 |
|
60 @param url xmlrpc handler URL (string or QUrl) |
|
61 """ |
|
62 url = QUrl(url) |
|
63 if url.isValid(): |
|
64 self.__request.setUrl(url) |
|
65 |
|
66 def call(self, method, args, responseCallback, errorCallback): |
|
67 """ |
|
68 Public method to call the remote server. |
|
69 |
|
70 @param method name of the remote method to be called (string) |
|
71 @param args tuple of method arguments (tuple) |
|
72 @param responseCallback method to be called with the returned |
|
73 result as a tuple (function) |
|
74 @param errorCallback method to be called in case of an error |
|
75 with error code and error string (function) |
|
76 """ |
|
77 assert isinstance(args, tuple), \ |
|
78 "argument must be tuple or Fault instance" |
|
79 |
|
80 data = xmlrpc.dumps(args, method).encode("utf-8") |
|
81 reply = self.__networkManager.post( |
|
82 self.__request, QByteArray(data)) |
|
83 self.__callmap[reply] = (responseCallback, errorCallback) |
|
84 |
|
85 def abort(self): |
|
86 """ |
|
87 Public method to abort all calls. |
|
88 """ |
|
89 for reply in list(self.__callmap): |
|
90 if reply.isRunning(): |
|
91 reply.abort() |
|
92 |
|
93 def __sslErrors(self, reply, errors): |
|
94 """ |
|
95 Private slot to handle SSL errors. |
|
96 |
|
97 @param reply reference to the reply object (QNetworkReply) |
|
98 @param errors list of SSL errors (list of QSslError) |
|
99 """ |
|
100 ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] |
|
101 if ignored == E5SslErrorHandler.NotIgnored and reply in self.__callmap: |
|
102 self.__callmap[reply][1](xmlrpc.TRANSPORT_ERROR, self.tr( |
|
103 "SSL Error")) |
|
104 |
|
105 def __replyFinished(self, reply): |
|
106 """ |
|
107 Private slot handling the receipt of a reply. |
|
108 |
|
109 @param reply reference to the finished reply (QNetworkReply) |
|
110 """ |
|
111 if reply not in self.__callmap: |
|
112 return |
|
113 |
|
114 if reply.error() != QNetworkReply.NoError: |
|
115 self.__callmap[reply][1](xmlrpc.TRANSPORT_ERROR, |
|
116 reply.errorString()) |
|
117 else: |
|
118 data = bytes(reply.readAll()).decode("utf-8") |
|
119 try: |
|
120 data = xmlrpc.loads(data)[0] |
|
121 self.__callmap[reply][0](data) |
|
122 except xmlrpc.Fault as fault: |
|
123 self.__callmap[reply][1](fault.faultCode, fault.faultString) |
|
124 |
|
125 reply.deleteLater() |
|
126 del self.__callmap[reply] |