1
2#############################################################################
3##
4## Copyright (C) 2013 Riverbank Computing Limited.
5## Copyright (C) 2016 The Qt Company Ltd.
6## Contact: http://www.qt.io/licensing/
7##
8## This file is part of the Qt for Python examples of the Qt Toolkit.
9##
10## $QT_BEGIN_LICENSE:BSD$
11## You may use this file under the terms of the BSD license as follows:
12##
13## "Redistribution and use in source and binary forms, with or without
14## modification, are permitted provided that the following conditions are
15## met:
16##   * Redistributions of source code must retain the above copyright
17##     notice, this list of conditions and the following disclaimer.
18##   * Redistributions in binary form must reproduce the above copyright
19##     notice, this list of conditions and the following disclaimer in
20##     the documentation and/or other materials provided with the
21##     distribution.
22##   * Neither the name of The Qt Company Ltd nor the names of its
23##     contributors may be used to endorse or promote products derived
24##     from this software without specific prior written permission.
25##
26##
27## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
38##
39## $QT_END_LICENSE$
40##
41#############################################################################
42
43"""PySide2 port of the network/blockingfortunclient example from Qt v5.x, originating from PyQt"""
44
45from PySide2.QtCore import (Signal, QDataStream, QMutex, QMutexLocker,
46        QThread, QWaitCondition)
47from PySide2.QtGui import QIntValidator
48from PySide2.QtWidgets import (QApplication, QDialogButtonBox, QGridLayout,
49        QLabel, QLineEdit, QMessageBox, QPushButton, QWidget)
50from PySide2.QtNetwork import (QAbstractSocket, QHostAddress, QNetworkInterface,
51        QTcpSocket)
52
53
54class FortuneThread(QThread):
55    newFortune = Signal(str)
56
57    error = Signal(int, str)
58
59    def __init__(self, parent=None):
60        super(FortuneThread, self).__init__(parent)
61
62        self.quit = False
63        self.hostName = ''
64        self.cond = QWaitCondition()
65        self.mutex = QMutex()
66        self.port = 0
67
68    def __del__(self):
69        self.mutex.lock()
70        self.quit = True
71        self.cond.wakeOne()
72        self.mutex.unlock()
73        self.wait()
74
75    def requestNewFortune(self, hostname, port):
76        locker = QMutexLocker(self.mutex)
77        self.hostName = hostname
78        self.port = port
79        if not self.isRunning():
80            self.start()
81        else:
82            self.cond.wakeOne()
83
84    def run(self):
85        self.mutex.lock()
86        serverName = self.hostName
87        serverPort = self.port
88        self.mutex.unlock()
89
90        while not self.quit:
91            Timeout = 5 * 1000
92
93            socket = QTcpSocket()
94            socket.connectToHost(serverName, serverPort)
95
96            if not socket.waitForConnected(Timeout):
97                self.error.emit(socket.error(), socket.errorString())
98                return
99
100            while socket.bytesAvailable() < 2:
101                if not socket.waitForReadyRead(Timeout):
102                    self.error.emit(socket.error(), socket.errorString())
103                    return
104
105            instr = QDataStream(socket)
106            instr.setVersion(QDataStream.Qt_4_0)
107            blockSize = instr.readUInt16()
108
109            while socket.bytesAvailable() < blockSize:
110                if not socket.waitForReadyRead(Timeout):
111                    self.error.emit(socket.error(), socket.errorString())
112                    return
113
114            self.mutex.lock()
115            fortune = instr.readQString()
116            self.newFortune.emit(fortune)
117
118            self.cond.wait(self.mutex)
119            serverName = self.hostName
120            serverPort = self.port
121            self.mutex.unlock()
122
123
124class BlockingClient(QWidget):
125    def __init__(self, parent=None):
126        super(BlockingClient, self).__init__(parent)
127
128        self.thread = FortuneThread()
129        self.currentFortune = ''
130
131        hostLabel = QLabel("&Server name:")
132        portLabel = QLabel("S&erver port:")
133
134        for ipAddress in QNetworkInterface.allAddresses():
135            if ipAddress != QHostAddress.LocalHost and ipAddress.toIPv4Address() != 0:
136                break
137        else:
138            ipAddress = QHostAddress(QHostAddress.LocalHost)
139
140        ipAddress = ipAddress.toString()
141
142        self.hostLineEdit = QLineEdit(ipAddress)
143        self.portLineEdit = QLineEdit()
144        self.portLineEdit.setValidator(QIntValidator(1, 65535, self))
145
146        hostLabel.setBuddy(self.hostLineEdit)
147        portLabel.setBuddy(self.portLineEdit)
148
149        self.statusLabel = QLabel(
150                "This example requires that you run the Fortune Server example as well.")
151        self.statusLabel.setWordWrap(True)
152
153        self.getFortuneButton = QPushButton("Get Fortune")
154        self.getFortuneButton.setDefault(True)
155        self.getFortuneButton.setEnabled(False)
156
157        quitButton = QPushButton("Quit")
158
159        buttonBox = QDialogButtonBox()
160        buttonBox.addButton(self.getFortuneButton, QDialogButtonBox.ActionRole)
161        buttonBox.addButton(quitButton, QDialogButtonBox.RejectRole)
162
163        self.getFortuneButton.clicked.connect(self.requestNewFortune)
164        quitButton.clicked.connect(self.close)
165        self.hostLineEdit.textChanged.connect(self.enableGetFortuneButton)
166        self.portLineEdit.textChanged.connect(self.enableGetFortuneButton)
167        self.thread.newFortune.connect(self.showFortune)
168        self.thread.error.connect(self.displayError)
169
170        mainLayout = QGridLayout()
171        mainLayout.addWidget(hostLabel, 0, 0)
172        mainLayout.addWidget(self.hostLineEdit, 0, 1)
173        mainLayout.addWidget(portLabel, 1, 0)
174        mainLayout.addWidget(self.portLineEdit, 1, 1)
175        mainLayout.addWidget(self.statusLabel, 2, 0, 1, 2)
176        mainLayout.addWidget(buttonBox, 3, 0, 1, 2)
177        self.setLayout(mainLayout)
178
179        self.setWindowTitle("Blocking Fortune Client")
180        self.portLineEdit.setFocus()
181
182    def requestNewFortune(self):
183        self.getFortuneButton.setEnabled(False)
184        self.thread.requestNewFortune(self.hostLineEdit.text(),
185                int(self.portLineEdit.text()))
186
187    def showFortune(self, nextFortune):
188        if nextFortune == self.currentFortune:
189            self.requestNewFortune()
190            return
191
192        self.currentFortune = nextFortune
193        self.statusLabel.setText(self.currentFortune)
194        self.getFortuneButton.setEnabled(True)
195
196    def displayError(self, socketError, message):
197        if socketError == QAbstractSocket.HostNotFoundError:
198            QMessageBox.information(self, "Blocking Fortune Client",
199                    "The host was not found. Please check the host and port "
200                    "settings.")
201        elif socketError == QAbstractSocket.ConnectionRefusedError:
202            QMessageBox.information(self, "Blocking Fortune Client",
203                    "The connection was refused by the peer. Make sure the "
204                    "fortune server is running, and check that the host name "
205                    "and port settings are correct.")
206        else:
207            QMessageBox.information(self, "Blocking Fortune Client",
208                    "The following error occurred: %s." % message)
209
210        self.getFortuneButton.setEnabled(True)
211
212    def enableGetFortuneButton(self):
213        self.getFortuneButton.setEnabled(self.hostLineEdit.text() != '' and
214                self.portLineEdit.text() != '')
215
216
217if __name__ == '__main__':
218
219    import sys
220
221    app = QApplication(sys.argv)
222    client = BlockingClient()
223    client.show()
224    sys.exit(app.exec_())
225