FreeCAD

Форк
0
/
NetworkManager.py 
693 строки · 29.6 Кб
1
# SPDX-License-Identifier: LGPL-2.1-or-later
2
# ***************************************************************************
3
# *                                                                         *
4
# *   Copyright (c) 2022-2023 FreeCAD Project Association                   *
5
# *                                                                         *
6
# *   This file is part of FreeCAD.                                         *
7
# *                                                                         *
8
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
9
# *   under the terms of the GNU Lesser General Public License as           *
10
# *   published by the Free Software Foundation, either version 2.1 of the  *
11
# *   License, or (at your option) any later version.                       *
12
# *                                                                         *
13
# *   FreeCAD is distributed in the hope that it will be useful, but        *
14
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
15
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
16
# *   Lesser General Public License for more details.                       *
17
# *                                                                         *
18
# *   You should have received a copy of the GNU Lesser General Public      *
19
# *   License along with FreeCAD. If not, see                               *
20
# *   <https://www.gnu.org/licenses/>.                                      *
21
# *                                                                         *
22
# ***************************************************************************
23

24
"""
25
#############################################################################
26
#
27
# ABOUT NETWORK MANAGER
28
#
29
# A wrapper around QNetworkAccessManager providing proxy-handling
30
# capabilities, and simplified access to submitting requests from any
31
# application thread.
32
#
33
#
34
# USAGE
35
#
36
# Once imported, this file provides access to a global object called
37
# AM_NETWORK_MANAGER. This is a QObject running on the main thread, but
38
# designed to be interacted with from any other application thread. It
39
# provides two principal methods: submit_unmonitored_get() and
40
# submit_monitored_get(). Use the unmonitored version for small amounts of
41
# data (suitable for caching in RAM, and without a need to show a progress
42
# bar during download), and the monitored version for larger amounts of data.
43
# Both functions take a URL, and return an integer index. That index allows
44
# tracking of the completed request by attaching to the signals completed(),
45
# progress_made(), and progress_complete(). All three provide, as the first
46
# argument to the signal, the index of the request the signal refers to.
47
# Code attached to those signals should filter them to look for the indices
48
# of the requests they care about. Requests may complete in any order.
49
#
50
# A secondary blocking interface is also provided, for very short network
51
# accesses: the blocking_get() function blocks until the network transmission
52
# is complete, directly returning a QByteArray object with the received data.
53
# Do not run on the main GUI thread!
54
"""
55

56
import threading
57
import os
58
import queue
59
import itertools
60
import tempfile
61
import sys
62
from typing import Dict, List, Optional
63

64
try:
65
    import FreeCAD
66

67
    if FreeCAD.GuiUp:
68
        import FreeCADGui
69

70
    HAVE_FREECAD = True
71
    translate = FreeCAD.Qt.translate
72
except ImportError:
73
    # For standalone testing support working without the FreeCAD import
74
    HAVE_FREECAD = False
75

76
from PySide import QtCore
77

78
if FreeCAD.GuiUp:
79
    from PySide import QtWidgets
80

81

82
# This is the global instance of the NetworkManager that outside code
83
# should access
84
AM_NETWORK_MANAGER = None
85

86
HAVE_QTNETWORK = True
87
try:
88
    from PySide import QtNetwork
89
except ImportError:
90
    if HAVE_FREECAD:
91
        FreeCAD.Console.PrintError(
92
            translate(
93
                "AddonsInstaller",
94
                'Could not import QtNetwork -- it does not appear to be installed on your system. Your provider may have a package for this dependency (often called "python3-pyside2.qtnetwork")',
95
            )
96
            + "\n"
97
        )
98
    else:
99
        print("Could not import QtNetwork, unable to test this file.")
100
        sys.exit(1)
101
    HAVE_QTNETWORK = False
102

103
if HAVE_QTNETWORK:
104

105
    # Added in Qt 5.15
106
    if hasattr(QtNetwork.QNetworkRequest, "DefaultTransferTimeoutConstant"):
107
        default_timeout = QtNetwork.QNetworkRequest.DefaultTransferTimeoutConstant
108
    else:
109
        default_timeout = 30000
110

111
    class QueueItem:
112
        """A container for information about an item in the network queue."""
113

114
        def __init__(self, index: int, request: QtNetwork.QNetworkRequest, track_progress: bool):
115
            self.index = index
116
            self.request = request
117
            self.original_url = request.url()
118
            self.track_progress = track_progress
119

120
    class NetworkManager(QtCore.QObject):
121
        """A single global instance of NetworkManager is instantiated and stored as
122
        AM_NETWORK_MANAGER. Outside threads should send GET requests to this class by
123
        calling the submit_unmonitored_request() or submit_monitored_request() function,
124
        as needed. See the documentation of those functions for details."""
125

126
        # Connect to complete for requests with no progress monitoring (e.g. small amounts of data)
127
        completed = QtCore.Signal(
128
            int, int, QtCore.QByteArray
129
        )  # Index, http response code, received data (if any)
130

131
        # Connect to progress_made and progress_complete for large amounts of data, which get buffered into a temp file
132
        # That temp file should be deleted when your code is done with it
133
        progress_made = QtCore.Signal(int, int, int)  # Index, bytes read, total bytes (may be None)
134

135
        progress_complete = QtCore.Signal(
136
            int, int, os.PathLike
137
        )  # Index, http response code, filename
138

139
        __request_queued = QtCore.Signal()
140

141
        def __init__(self):
142
            super().__init__()
143

144
            self.counting_iterator = itertools.count()
145
            self.queue = queue.Queue()
146
            self.__last_started_index = 0
147
            self.__abort_when_found: List[int] = []
148
            self.replies: Dict[int, QtNetwork.QNetworkReply] = {}
149
            self.file_buffers = {}
150

151
            # We support an arbitrary number of threads using synchronous GET calls:
152
            self.synchronous_lock = threading.Lock()
153
            self.synchronous_complete: Dict[int, bool] = {}
154
            self.synchronous_result_data: Dict[int, QtCore.QByteArray] = {}
155

156
            # Make sure we exit nicely on quit
157
            if QtCore.QCoreApplication.instance() is not None:
158
                QtCore.QCoreApplication.instance().aboutToQuit.connect(self.__aboutToQuit)
159

160
            # Create the QNAM on this thread:
161
            self.QNAM = QtNetwork.QNetworkAccessManager()
162
            self.QNAM.proxyAuthenticationRequired.connect(self.__authenticate_proxy)
163
            self.QNAM.authenticationRequired.connect(self.__authenticate_resource)
164

165
            qnam_cache = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.CacheLocation)
166
            os.makedirs(qnam_cache, exist_ok=True)
167
            self.diskCache = QtNetwork.QNetworkDiskCache()
168
            self.diskCache.setCacheDirectory(qnam_cache)
169
            self.QNAM.setCache(self.diskCache)
170

171
            self.monitored_connections: List[int] = []
172
            self._setup_proxy()
173

174
            # A helper connection for our blocking interface
175
            self.completed.connect(self.__synchronous_process_completion)
176

177
            # Set up our worker connection
178
            self.__request_queued.connect(self.__setup_network_request)
179

180
        def _setup_proxy(self):
181
            """Set up the proxy based on user preferences or prompts on command line"""
182

183
            # Set up the proxy, if necessary:
184
            if HAVE_FREECAD:
185
                (
186
                    noProxyCheck,
187
                    systemProxyCheck,
188
                    userProxyCheck,
189
                    proxy_string,
190
                ) = self._setup_proxy_freecad()
191
            else:
192
                (
193
                    noProxyCheck,
194
                    systemProxyCheck,
195
                    userProxyCheck,
196
                    proxy_string,
197
                ) = self._setup_proxy_standalone()
198

199
            if noProxyCheck:
200
                pass
201
            elif systemProxyCheck:
202
                query = QtNetwork.QNetworkProxyQuery(
203
                    QtCore.QUrl("https://github.com/FreeCAD/FreeCAD")
204
                )
205
                proxy = QtNetwork.QNetworkProxyFactory.systemProxyForQuery(query)
206
                if proxy and proxy[0]:
207
                    self.QNAM.setProxy(proxy[0])  # This may still be QNetworkProxy.NoProxy
208
            elif userProxyCheck:
209
                host, _, port_string = proxy_string.rpartition(":")
210
                try:
211
                    port = 0 if not port_string else int(port_string)
212
                except ValueError:
213
                    FreeCAD.Console.PrintError(
214
                        translate(
215
                            "AddonsInstaller",
216
                            "Failed to convert the specified proxy port '{}' to a port number",
217
                        ).format(port_string)
218
                        + "\n"
219
                    )
220
                    port = 0
221
                # For now assume an HttpProxy, but eventually this should be a parameter
222
                proxy = QtNetwork.QNetworkProxy(QtNetwork.QNetworkProxy.HttpProxy, host, port)
223
                self.QNAM.setProxy(proxy)
224

225
        def _setup_proxy_freecad(self):
226
            """If we are running within FreeCAD, this uses the config data to set up the proxy"""
227
            noProxyCheck = True
228
            systemProxyCheck = False
229
            userProxyCheck = False
230
            proxy_string = ""
231
            pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
232
            noProxyCheck = pref.GetBool("NoProxyCheck", noProxyCheck)
233
            systemProxyCheck = pref.GetBool("SystemProxyCheck", systemProxyCheck)
234
            userProxyCheck = pref.GetBool("UserProxyCheck", userProxyCheck)
235
            proxy_string = pref.GetString("ProxyUrl", "")
236

237
            # Add some error checking to the proxy setup, since for historical reasons they
238
            # are independent booleans, rather than an enumeration:
239
            option_count = [noProxyCheck, systemProxyCheck, userProxyCheck].count(True)
240
            if option_count != 1:
241
                FreeCAD.Console.PrintWarning(
242
                    translate(
243
                        "AddonsInstaller",
244
                        "Parameter error: mutually exclusive proxy options set. Resetting to default.",
245
                    )
246
                    + "\n"
247
                )
248
                noProxyCheck = True
249
                systemProxyCheck = False
250
                userProxyCheck = False
251
                pref.SetBool("NoProxyCheck", noProxyCheck)
252
                pref.SetBool("SystemProxyCheck", systemProxyCheck)
253
                pref.SetBool("UserProxyCheck", userProxyCheck)
254

255
            if userProxyCheck and not proxy_string:
256
                FreeCAD.Console.PrintWarning(
257
                    translate(
258
                        "AddonsInstaller",
259
                        "Parameter error: user proxy indicated, but no proxy provided. Resetting to default.",
260
                    )
261
                    + "\n"
262
                )
263
                noProxyCheck = True
264
                userProxyCheck = False
265
                pref.SetBool("NoProxyCheck", noProxyCheck)
266
                pref.SetBool("UserProxyCheck", userProxyCheck)
267
            return noProxyCheck, systemProxyCheck, userProxyCheck, proxy_string
268

269
        def _setup_proxy_standalone(self):
270
            """If we are NOT running inside FreeCAD, prompt the user for proxy information"""
271
            noProxyCheck = True
272
            systemProxyCheck = False
273
            userProxyCheck = False
274
            proxy_string = ""
275
            print("Please select a proxy type:")
276
            print("1) No proxy")
277
            print("2) Use system proxy settings")
278
            print("3) Custom proxy settings")
279
            result = input("Choice: ")
280
            if result == "1":
281
                pass
282
            elif result == "2":
283
                noProxyCheck = False
284
                systemProxyCheck = True
285
            elif result == "3":
286
                noProxyCheck = False
287
                userProxyCheck = True
288
                proxy_string = input("Enter your proxy server (host:port): ")
289
            else:
290
                print(f"Got {result}, expected 1, 2, or 3.")
291
                app.quit()
292
            return noProxyCheck, systemProxyCheck, userProxyCheck, proxy_string
293

294
        def __aboutToQuit(self):
295
            """Called when the application is about to quit. Not currently used."""
296

297
        def __setup_network_request(self):
298
            """Get the next request off the queue and launch it."""
299
            try:
300
                item = self.queue.get_nowait()
301
                if item:
302
                    if item.index in self.__abort_when_found:
303
                        self.__abort_when_found.remove(item.index)
304
                        return  # Do not do anything with this item, it's been aborted...
305
                    if item.track_progress:
306
                        self.monitored_connections.append(item.index)
307
                    self.__launch_request(item.index, item.request)
308
            except queue.Empty:
309
                pass
310

311
        def __launch_request(self, index: int, request: QtNetwork.QNetworkRequest) -> None:
312
            """Given a network request, ask the QNetworkAccessManager to begin processing it."""
313
            reply = self.QNAM.get(request)
314
            self.replies[index] = reply
315

316
            self.__last_started_index = index
317
            reply.finished.connect(self.__reply_finished)
318
            reply.redirected.connect(self.__follow_redirect)
319
            reply.sslErrors.connect(self.__on_ssl_error)
320
            if index in self.monitored_connections:
321
                reply.readyRead.connect(self.__ready_to_read)
322
                reply.downloadProgress.connect(self.__download_progress)
323

324
        def submit_unmonitored_get(
325
            self,
326
            url: str,
327
            timeout_ms: int = default_timeout,
328
        ) -> int:
329
            """Adds this request to the queue, and returns an index that can be used by calling code
330
            in conjunction with the completed() signal to handle the results of the call. All data is
331
            kept in memory, and the completed() call includes a direct handle to the bytes returned. It
332
            is not called until the data transfer has finished and the connection is closed."""
333

334
            current_index = next(self.counting_iterator)  # A thread-safe counter
335
            # Use a queue because we can only put things on the QNAM from the main event loop thread
336
            self.queue.put(
337
                QueueItem(
338
                    current_index, self.__create_get_request(url, timeout_ms), track_progress=False
339
                )
340
            )
341
            self.__request_queued.emit()
342
            return current_index
343

344
        def submit_monitored_get(
345
            self,
346
            url: str,
347
            timeout_ms: int = default_timeout,
348
        ) -> int:
349
            """Adds this request to the queue, and returns an index that can be used by calling code
350
            in conjunction with the progress_made() and progress_completed() signals to handle the
351
            results of the call. All data is cached to disk, and progress is reported periodically
352
            as the underlying QNetworkReply reports its progress. The progress_completed() signal
353
            contains a path to a temporary file with the stored data. Calling code should delete this
354
            file when done with it (or move it into its final place, etc.)."""
355

356
            current_index = next(self.counting_iterator)  # A thread-safe counter
357
            # Use a queue because we can only put things on the QNAM from the main event loop thread
358
            self.queue.put(
359
                QueueItem(
360
                    current_index, self.__create_get_request(url, timeout_ms), track_progress=True
361
                )
362
            )
363
            self.__request_queued.emit()
364
            return current_index
365

366
        def blocking_get(
367
            self,
368
            url: str,
369
            timeout_ms: int = default_timeout,
370
        ) -> Optional[QtCore.QByteArray]:
371
            """Submits a GET request to the QNetworkAccessManager and block until it is complete"""
372

373
            current_index = next(self.counting_iterator)  # A thread-safe counter
374
            with self.synchronous_lock:
375
                self.synchronous_complete[current_index] = False
376

377
            self.queue.put(
378
                QueueItem(
379
                    current_index, self.__create_get_request(url, timeout_ms), track_progress=False
380
                )
381
            )
382
            self.__request_queued.emit()
383
            while True:
384
                if QtCore.QThread.currentThread().isInterruptionRequested():
385
                    return None
386
                QtCore.QCoreApplication.processEvents()
387
                with self.synchronous_lock:
388
                    if self.synchronous_complete[current_index]:
389
                        break
390

391
            with self.synchronous_lock:
392
                self.synchronous_complete.pop(current_index)
393
                if current_index in self.synchronous_result_data:
394
                    return self.synchronous_result_data.pop(current_index)
395
                return None
396

397
        def __synchronous_process_completion(
398
            self, index: int, code: int, data: QtCore.QByteArray
399
        ) -> None:
400
            """Check the return status of a completed process, and handle its returned data (if
401
            any)."""
402
            with self.synchronous_lock:
403
                if index in self.synchronous_complete:
404
                    if code == 200:
405
                        self.synchronous_result_data[index] = data
406
                    else:
407
                        FreeCAD.Console.PrintWarning(
408
                            translate(
409
                                "AddonsInstaller",
410
                                "Addon Manager: Unexpected {} response from server",
411
                            ).format(code)
412
                            + "\n"
413
                        )
414
                    self.synchronous_complete[index] = True
415

416
        @staticmethod
417
        def __create_get_request(url: str, timeout_ms: int) -> QtNetwork.QNetworkRequest:
418
            """Construct a network request to a given URL"""
419
            request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))
420
            request.setAttribute(
421
                QtNetwork.QNetworkRequest.RedirectPolicyAttribute,
422
                QtNetwork.QNetworkRequest.UserVerifiedRedirectPolicy,
423
            )
424
            request.setAttribute(QtNetwork.QNetworkRequest.CacheSaveControlAttribute, True)
425
            request.setAttribute(
426
                QtNetwork.QNetworkRequest.CacheLoadControlAttribute,
427
                QtNetwork.QNetworkRequest.PreferNetwork,
428
            )
429
            if hasattr(request, "setTransferTimeout"):
430
                # Added in Qt 5.15
431
                request.setTransferTimeout(timeout_ms)
432
            return request
433

434
        def abort_all(self):
435
            """Abort ALL network calls in progress, including clearing the queue"""
436
            for reply in self.replies.values():
437
                if reply.abort().isRunning():
438
                    reply.abort()
439
            while True:
440
                try:
441
                    self.queue.get()
442
                    self.queue.task_done()
443
                except queue.Empty:
444
                    break
445

446
        def abort(self, index: int):
447
            """Abort a specific request"""
448
            if index in self.replies and self.replies[index].isRunning():
449
                self.replies[index].abort()
450
            elif index < self.__last_started_index:
451
                # It's still in the queue. Mark it for later destruction.
452
                self.__abort_when_found.append(index)
453

454
        def __authenticate_proxy(
455
            self,
456
            reply: QtNetwork.QNetworkProxy,
457
            authenticator: QtNetwork.QAuthenticator,
458
        ):
459
            """If proxy authentication is required, attempt to authenticate. If the GUI is running this displays
460
            a window asking for credentials. If the GUI is not running, it prompts on the command line.
461
            """
462
            if HAVE_FREECAD and FreeCAD.GuiUp:
463
                proxy_authentication = FreeCADGui.PySideUic.loadUi(
464
                    os.path.join(os.path.dirname(__file__), "proxy_authentication.ui")
465
                )
466
                proxy_authentication.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, True)
467
                # Show the right labels, etc.
468
                proxy_authentication.labelProxyAddress.setText(f"{reply.hostName()}:{reply.port()}")
469
                if authenticator.realm():
470
                    proxy_authentication.labelProxyRealm.setText(authenticator.realm())
471
                else:
472
                    proxy_authentication.labelProxyRealm.hide()
473
                    proxy_authentication.labelRealmCaption.hide()
474
                result = proxy_authentication.exec()
475
                if result == QtWidgets.QDialogButtonBox.Ok:
476
                    authenticator.setUser(proxy_authentication.lineEditUsername.text())
477
                    authenticator.setPassword(proxy_authentication.lineEditPassword.text())
478
            else:
479
                username = input("Proxy username: ")
480
                import getpass
481

482
                password = getpass.getpass()
483
                authenticator.setUser(username)
484
                authenticator.setPassword(password)
485

486
        def __authenticate_resource(
487
            self,
488
            _reply: QtNetwork.QNetworkReply,
489
            _authenticator: QtNetwork.QAuthenticator,
490
        ):
491
            """Unused."""
492

493
        def __follow_redirect(self, url):
494
            """Used with the QNetworkAccessManager to follow redirects."""
495
            sender = self.sender()
496
            current_index = -1
497
            timeout_ms = default_timeout
498
            # TODO: Figure out what the actual timeout value should be from the original request
499
            if sender:
500
                for index, reply in self.replies.items():
501
                    if reply == sender:
502
                        current_index = index
503
                        break
504

505
                if current_index != -1:
506
                    self.__launch_request(current_index, self.__create_get_request(url, timeout_ms))
507

508
        def __on_ssl_error(self, reply: str, errors: List[str] = None):
509
            """Called when an SSL error occurs: prints the error information."""
510
            if HAVE_FREECAD:
511
                FreeCAD.Console.PrintWarning(
512
                    translate("AddonsInstaller", "Error with encrypted connection") + "\n:"
513
                )
514
                FreeCAD.Console.PrintWarning(reply)
515
                if errors is not None:
516
                    for error in errors:
517
                        FreeCAD.Console.PrintWarning(error)
518
            else:
519
                print("Error with encrypted connection")
520
                if errors is not None:
521
                    for error in errors:
522
                        print(error)
523

524
        def __download_progress(self, bytesReceived: int, bytesTotal: int) -> None:
525
            """Monitors download progress and emits a progress_made signal"""
526
            sender = self.sender()
527
            if not sender:
528
                return
529
            for index, reply in self.replies.items():
530
                if reply == sender:
531
                    self.progress_made.emit(index, bytesReceived, bytesTotal)
532
                    return
533

534
        def __ready_to_read(self) -> None:
535
            """Called when data is available, this reads that data."""
536
            sender = self.sender()
537
            if not sender:
538
                return
539

540
            for index, reply in self.replies.items():
541
                if reply == sender:
542
                    self.__data_incoming(index, reply)
543
                    return
544

545
        def __data_incoming(self, index: int, reply: QtNetwork.QNetworkReply) -> None:
546
            """Read incoming data and attach it to a data object"""
547
            if not index in self.replies:
548
                # We already finished this reply, this is a vestigial signal
549
                return
550
            buffer = reply.readAll()
551
            if not index in self.file_buffers:
552
                f = tempfile.NamedTemporaryFile("wb", delete=False)
553
                self.file_buffers[index] = f
554
            else:
555
                f = self.file_buffers[index]
556
            try:
557
                f.write(buffer.data())
558
            except OSError as e:
559
                if HAVE_FREECAD:
560
                    FreeCAD.Console.PrintError(f"Network Manager internal error: {str(e)}")
561
                else:
562
                    print(f"Network Manager internal error: {str(e)}")
563

564
        def __reply_finished(self) -> None:
565
            """Called when a reply has been completed: this makes sure the data has been read and
566
            any notifications have been called."""
567
            reply = self.sender()
568
            if not reply:
569
                # This can happen during a cancellation operation: silently do nothing
570
                return
571

572
            index = None
573
            for key, value in self.replies.items():
574
                if reply == value:
575
                    index = key
576
                    break
577
            if index is None:
578
                return
579

580
            response_code = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
581
            if response_code == 301 or response_code == 302:  # This is a redirect, bail out
582
                return
583
            if reply.error() != QtNetwork.QNetworkReply.NetworkError.OperationCanceledError:
584
                # It this was not a timeout, make sure we mark the queue task done
585
                self.queue.task_done()
586
            if reply.error() == QtNetwork.QNetworkReply.NetworkError.NoError:
587
                if index in self.monitored_connections:
588
                    # Make sure to read any remaining data
589
                    self.__data_incoming(index, reply)
590
                    self.monitored_connections.remove(index)
591
                    f = self.file_buffers[index]
592
                    f.close()
593
                    self.progress_complete.emit(index, response_code, f.name)
594
                else:
595
                    data = reply.readAll()
596
                    self.completed.emit(index, response_code, data)
597
            else:
598
                if index in self.monitored_connections:
599
                    self.progress_complete.emit(index, response_code, "")
600
                else:
601
                    self.completed.emit(index, response_code, None)
602
            self.replies.pop(index)
603

604
else:  # HAVE_QTNETWORK is false:
605

606
    class NetworkManager(QtCore.QObject):
607
        """A dummy class to enable an offline mode when the QtNetwork package is not yet installed"""
608

609
        completed = QtCore.Signal(
610
            int, int, bytes
611
        )  # Emitted as soon as the request is made, with a connection failed error
612
        progress_made = QtCore.Signal(int, int, int)  # Never emitted, no progress is made here
613
        progress_complete = QtCore.Signal(
614
            int, int, os.PathLike
615
        )  # Emitted as soon as the request is made, with a connection failed error
616

617
        def __init__(self):
618
            super().__init__()
619
            self.monitored_queue = queue.Queue()
620
            self.unmonitored_queue = queue.Queue()
621

622
        def submit_unmonitored_request(self, _) -> int:
623
            """Returns a fake index that can be used for testing -- nothing is actually queued"""
624
            current_index = next(itertools.count())
625
            self.unmonitored_queue.put(current_index)
626
            return current_index
627

628
        def submit_monitored_request(self, _) -> int:
629
            """Returns a fake index that can be used for testing -- nothing is actually queued"""
630
            current_index = next(itertools.count())
631
            self.monitored_queue.put(current_index)
632
            return current_index
633

634
        def blocking_get(self, _: str) -> QtCore.QByteArray:
635
            """No operation - returns None immediately"""
636
            return None
637

638
        def abort_all(
639
            self,
640
        ):
641
            """There is nothing to abort in this case"""
642

643
        def abort(self, _):
644
            """There is nothing to abort in this case"""
645

646

647
def InitializeNetworkManager():
648
    """Called once at the beginning of program execution to create the appropriate manager object"""
649
    global AM_NETWORK_MANAGER
650
    if AM_NETWORK_MANAGER is None:
651
        AM_NETWORK_MANAGER = NetworkManager()
652

653

654
if __name__ == "__main__":
655
    app = QtCore.QCoreApplication()
656

657
    InitializeNetworkManager()
658

659
    count = 0
660

661
    # For testing, create several network requests and send them off in quick succession:
662
    # (Choose small downloads, no need for significant data)
663
    urls = [
664
        "https://api.github.com/zen",
665
        "http://climate.ok.gov/index.php/climate/rainfall_table/local_data",
666
        "https://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/AIANNHA/MapServer",
667
    ]
668

669
    def handle_completion(index: int, code: int, data):
670
        """Attached to the completion signal, prints diagnostic information about the network access"""
671
        global count
672
        if code == 200:
673
            print(f"For request {index+1}, response was {data.size()} bytes.", flush=True)
674
        else:
675
            print(
676
                f"For request {index+1}, request failed with HTTP result code {code}",
677
                flush=True,
678
            )
679

680
        count += 1
681
        if count >= len(urls):
682
            print("Shutting down...", flush=True)
683
            AM_NETWORK_MANAGER.requestInterruption()
684
            AM_NETWORK_MANAGER.wait(5000)
685
            app.quit()
686

687
    AM_NETWORK_MANAGER.completed.connect(handle_completion)
688
    for test_url in urls:
689
        AM_NETWORK_MANAGER.submit_unmonitored_get(test_url)
690

691
    app.exec_()
692

693
    print("Done with all requests.")
694

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.