eric6/E5Network/E5NetworkMonitor.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a network monitor dialog.
8 """
9
10 from __future__ import unicode_literals
11
12 from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex, QUrl, \
13 QSortFilterProxyModel
14 from PyQt5.QtGui import QStandardItemModel
15 from PyQt5.QtWidgets import QDialog
16 from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
17
18 from .Ui_E5NetworkMonitor import Ui_E5NetworkMonitor
19
20
21 class E5NetworkRequest(object):
22 """
23 Class for storing all data related to a specific request.
24 """
25 def __init__(self):
26 """
27 Constructor
28 """
29 self.op = -1
30 self.request = None
31 self.reply = None
32
33 self.response = ""
34 self.length = 0
35 self.contentType = ""
36 self.info = ""
37 self.replyHeaders = [] # list of tuple of two items
38
39
40 class E5NetworkMonitor(QDialog, Ui_E5NetworkMonitor):
41 """
42 Class implementing a network monitor dialog.
43 """
44 _monitor = None
45
46 @classmethod
47 def instance(cls, networkAccessManager):
48 """
49 Class method to get a reference to our singleton.
50
51 @param networkAccessManager reference to the network access manager
52 (QNetworkAccessManager)
53 @return reference to the network monitor singleton (E5NetworkMonitor)
54 """
55 if cls._monitor is None:
56 cls._monitor = E5NetworkMonitor(networkAccessManager)
57
58 return cls._monitor
59
60 @classmethod
61 def closeMonitor(cls):
62 """
63 Class method to close the monitor dialog.
64 """
65 if cls._monitor is not None:
66 cls._monitor.close()
67
68 def __init__(self, networkAccessManager, parent=None):
69 """
70 Constructor
71
72 @param networkAccessManager reference to the network access manager
73 (QNetworkAccessManager)
74 @param parent reference to the parent widget (QWidget)
75 """
76 super(E5NetworkMonitor, self).__init__(parent)
77 self.setupUi(self)
78 self.setWindowFlags(Qt.Window)
79
80 self.__requestHeaders = QStandardItemModel(self)
81 self.__requestHeaders.setHorizontalHeaderLabels(
82 [self.tr("Name"), self.tr("Value")])
83 self.requestHeadersList.setModel(self.__requestHeaders)
84 self.requestHeadersList.horizontalHeader().setStretchLastSection(True)
85 self.requestHeadersList.doubleClicked.connect(
86 lambda index: self.__showHeaderDetails(
87 index, self.requestHeadersList))
88
89 self.__replyHeaders = QStandardItemModel(self)
90 self.__replyHeaders.setHorizontalHeaderLabels(
91 [self.tr("Name"), self.tr("Value")])
92 self.responseHeadersList.setModel(self.__replyHeaders)
93 self.responseHeadersList.horizontalHeader().setStretchLastSection(True)
94 self.responseHeadersList.doubleClicked.connect(
95 lambda index: self.__showHeaderDetails(
96 index, self.responseHeadersList))
97
98 self.requestsList.horizontalHeader().setStretchLastSection(True)
99 self.requestsList.verticalHeader().setMinimumSectionSize(-1)
100
101 self.__proxyModel = QSortFilterProxyModel(self)
102 self.__proxyModel.setFilterKeyColumn(-1)
103 self.searchEdit.textChanged.connect(
104 self.__proxyModel.setFilterFixedString)
105
106 self.removeButton.clicked.connect(self.requestsList.removeSelected)
107 self.removeAllButton.clicked.connect(self.requestsList.removeAll)
108
109 self.__model = E5RequestModel(networkAccessManager, self)
110 self.__proxyModel.setSourceModel(self.__model)
111 self.requestsList.setModel(self.__proxyModel)
112 self.__proxyModel.rowsInserted.connect(
113 self.requestsList.scrollToBottom)
114 self.requestsList.selectionModel()\
115 .currentChanged[QModelIndex, QModelIndex]\
116 .connect(self.__currentChanged)
117
118 fm = self.fontMetrics()
119 em = fm.width("m")
120 self.requestsList.horizontalHeader().resizeSection(0, em * 5)
121 self.requestsList.horizontalHeader().resizeSection(1, em * 20)
122 self.requestsList.horizontalHeader().resizeSection(3, em * 5)
123 self.requestsList.horizontalHeader().resizeSection(4, em * 15)
124
125 self.__headersDlg = None
126
127 def closeEvent(self, evt):
128 """
129 Protected method called upon closing the dialog.
130
131 @param evt reference to the close event object (QCloseEvent)
132 """
133 self.__class__._monitor = None
134 super(E5NetworkMonitor, self).closeEvent(evt)
135
136 def reject(self):
137 """
138 Public slot to close the dialog with a Reject status.
139 """
140 self.__class__._monitor = None
141 super(E5NetworkMonitor, self).reject()
142
143 def __currentChanged(self, current, previous):
144 """
145 Private slot to handle a change of the current index.
146
147 @param current new current index (QModelIndex)
148 @param previous old current index (QModelIndex)
149 """
150 self.__requestHeaders.setRowCount(0)
151 self.__replyHeaders.setRowCount(0)
152
153 if not current.isValid():
154 return
155
156 row = self.__proxyModel.mapToSource(current).row()
157
158 req = self.__model.requests[row].request
159
160 for header in req.rawHeaderList():
161 self.__requestHeaders.insertRows(0, 1, QModelIndex())
162 self.__requestHeaders.setData(
163 self.__requestHeaders.index(0, 0),
164 str(header, "utf-8"))
165 self.__requestHeaders.setData(
166 self.__requestHeaders.index(0, 1),
167 str(req.rawHeader(header), "utf-8"))
168 self.__requestHeaders.item(0, 0).setFlags(
169 Qt.ItemIsSelectable | Qt.ItemIsEnabled)
170 self.__requestHeaders.item(0, 1).setFlags(
171 Qt.ItemIsSelectable | Qt.ItemIsEnabled)
172
173 for header in self.__model.requests[row].replyHeaders:
174 self.__replyHeaders.insertRows(0, 1, QModelIndex())
175 self.__replyHeaders.setData(
176 self.__replyHeaders.index(0, 0),
177 header[0])
178 self.__replyHeaders.setData(
179 self.__replyHeaders.index(0, 1),
180 header[1])
181 self.__replyHeaders.item(0, 0).setFlags(
182 Qt.ItemIsSelectable | Qt.ItemIsEnabled)
183 self.__replyHeaders.item(0, 1).setFlags(
184 Qt.ItemIsSelectable | Qt.ItemIsEnabled)
185
186 def __showHeaderDetails(self, index, headerList):
187 """
188 Private slot to show a dialog with the header details.
189
190 @param index index of the entry to show
191 @type QModelIndex
192 @param headerList list requesting the header details
193 @type QTableView
194 """
195 if not index.isValid():
196 return
197
198 row = index.row()
199 name = headerList.model().data(headerList.model().index(row, 0))
200 value = headerList.model().data(headerList.model().index(row, 1))
201 if self.__headersDlg is None:
202 from .E5NetworkHeaderDetailsDialog import \
203 E5NetworkHeaderDetailsDialog
204 self.__headersDlg = E5NetworkHeaderDetailsDialog(self)
205 self.__headersDlg.setData(name, value)
206 self.__headersDlg.show()
207
208
209 class E5RequestModel(QAbstractTableModel):
210 """
211 Class implementing a model storing request objects.
212 """
213 def __init__(self, networkAccessManager, parent=None):
214 """
215 Constructor
216
217 @param networkAccessManager reference to the network access manager
218 (QNetworkAccessManager)
219 @param parent reference to the parent object (QObject)
220 """
221 super(E5RequestModel, self).__init__(parent)
222
223 self.__headerData = [
224 self.tr("Method"),
225 self.tr("Address"),
226 self.tr("Response"),
227 self.tr("Length"),
228 self.tr("Content Type"),
229 self.tr("Info"),
230 ]
231
232 self.__operations = {
233 QNetworkAccessManager.HeadOperation: "HEAD",
234 QNetworkAccessManager.GetOperation: "GET",
235 QNetworkAccessManager.PutOperation: "PUT",
236 QNetworkAccessManager.PostOperation: "POST",
237 }
238
239 self.requests = []
240 networkAccessManager.requestCreated.connect(self.__requestCreated)
241
242 def __requestCreated(self, operation, request, reply):
243 """
244 Private slot handling the creation of a network request.
245
246 @param operation network operation (QNetworkAccessManager.Operation)
247 @param request reference to the request object (QNetworkRequest)
248 @param reply reference to the reply object(QNetworkReply)
249 """
250 req = E5NetworkRequest()
251 req.op = operation
252 req.request = QNetworkRequest(request)
253 req.reply = reply
254 self.__addRequest(req)
255
256 def __addRequest(self, req):
257 """
258 Private method to add a request object to the model.
259
260 @param req reference to the request object (E5NetworkRequest)
261 """
262 self.beginInsertRows(
263 QModelIndex(), len(self.requests), len(self.requests))
264 self.requests.append(req)
265 req.reply.finished.connect(lambda: self.__addReply(req.reply))
266 self.endInsertRows()
267
268 def __addReply(self, reply):
269 """
270 Private slot to add the reply data to the model.
271
272 @param reply reference to the network reply object
273 @type QNetworkReply
274 """
275 offset = len(self.requests) - 1
276 while offset >= 0:
277 if self.requests[offset].reply is reply:
278 break
279 offset -= 1
280 if offset < 0:
281 return
282
283 # save the reply header data
284 for header in reply.rawHeaderList():
285 self.requests[offset].replyHeaders.append((
286 str(header, "utf-8"), str(reply.rawHeader(header), "utf-8")))
287
288 # save reply info to be displayed
289 status = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) or 0
290 reason = \
291 reply.attribute(QNetworkRequest.HttpReasonPhraseAttribute) or ""
292 self.requests[offset].response = "{0:d} {1}".format(status, reason)
293 self.requests[offset].length = \
294 reply.header(QNetworkRequest.ContentLengthHeader)
295 self.requests[offset].contentType = \
296 reply.header(QNetworkRequest.ContentTypeHeader)
297
298 if status == 302:
299 target = reply.attribute(
300 QNetworkRequest.RedirectionTargetAttribute) or QUrl()
301 self.requests[offset].info = \
302 self.tr("Redirect: {0}").format(target.toString())
303
304 def headerData(self, section, orientation, role=Qt.DisplayRole):
305 """
306 Public method to get header data from the model.
307
308 @param section section number (integer)
309 @param orientation orientation (Qt.Orientation)
310 @param role role of the data to retrieve (integer)
311 @return requested data
312 """
313 if orientation == Qt.Horizontal and role == Qt.DisplayRole:
314 return self.__headerData[section]
315
316 return QAbstractTableModel.headerData(self, section, orientation, role)
317
318 def data(self, index, role=Qt.DisplayRole):
319 """
320 Public method to get data from the model.
321
322 @param index index to get data for (QModelIndex)
323 @param role role of the data to retrieve (integer)
324 @return requested data
325 """
326 if index.row() < 0 or index.row() >= len(self.requests):
327 return None
328
329 if role == Qt.DisplayRole or role == Qt.EditRole:
330 col = index.column()
331 if col == 0:
332 try:
333 return self.__operations[self.requests[index.row()].op]
334 except KeyError:
335 return self.tr("Unknown")
336 elif col == 1:
337 return self.requests[index.row()].request.url().toEncoded()
338 elif col == 2:
339 return self.requests[index.row()].response
340 elif col == 3:
341 return self.requests[index.row()].length
342 elif col == 4:
343 return self.requests[index.row()].contentType
344 elif col == 5:
345 return self.requests[index.row()].info
346
347 return None
348
349 def columnCount(self, parent):
350 """
351 Public method to get the number of columns of the model.
352
353 @param parent parent index (QModelIndex)
354 @return number of columns (integer)
355 """
356 if parent.column() > 0:
357 return 0
358 else:
359 return len(self.__headerData)
360
361 def rowCount(self, parent):
362 """
363 Public method to get the number of rows of the model.
364
365 @param parent parent index (QModelIndex)
366 @return number of columns (integer)
367 """
368 if parent.isValid():
369 return 0
370 else:
371 return len(self.requests)
372
373 def removeRows(self, row, count, parent):
374 """
375 Public method to remove entries from the model.
376
377 @param row start row (integer)
378 @param count number of rows to remove (integer)
379 @param parent parent index (QModelIndex)
380 @return flag indicating success (boolean)
381 """
382 if parent.isValid():
383 return False
384
385 lastRow = row + count - 1
386 self.beginRemoveRows(parent, row, lastRow)
387 del self.requests[row:lastRow + 1]
388 self.endRemoveRows()
389 return True

eric ide

mercurial