1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import (pyqtSignal, QDataStream, QMutex, QMutexLocker,
46        QThread, QWaitCondition)
47from PyQt5.QtGui import QIntValidator
48from PyQt5.QtWidgets import (QApplication, QDialogButtonBox, QGridLayout,
49        QLabel, QLineEdit, QMessageBox, QPushButton, QWidget)
50from PyQt5.QtNetwork import (QAbstractSocket, QHostAddress, QNetworkInterface,
51        QTcpSocket)
52
53
54class FortuneThread(QThread):
55    newFortune = pyqtSignal(str)
56
57    error = pyqtSignal(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