eric6/E5Network/E5NetworkMonitor.py

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

eric ide

mercurial