|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the client of the cooperation package. |
|
8 """ |
|
9 |
|
10 import collections |
|
11 |
|
12 from PyQt4.QtCore import QObject, pyqtSignal, QProcess, QRegExp |
|
13 from PyQt4.QtNetwork import QHostInfo, QHostAddress, QAbstractSocket |
|
14 |
|
15 from .CooperationServer import CooperationServer |
|
16 from .Connection import Connection |
|
17 |
|
18 class CooperationClient(QObject): |
|
19 """ |
|
20 Class implementing the client of the cooperation package. |
|
21 |
|
22 @signal newMessage(user, message) emitted after a new message has |
|
23 arrived (string, string) |
|
24 @signal newParticipant(nickname) emitted after a new participant joined (string) |
|
25 @signal participantLeft(nickname) emitted after a participant left (string) |
|
26 @signal connectionError(message) emitted when a connection error occurs (string) |
|
27 @signal cannotConnect() emitted, if the initial connection fails |
|
28 """ |
|
29 newMessage = pyqtSignal(str, str) |
|
30 newParticipant = pyqtSignal(str) |
|
31 participantLeft = pyqtSignal(str) |
|
32 connectionError = pyqtSignal(str) |
|
33 cannotConnect = pyqtSignal() |
|
34 |
|
35 def __init__(self): |
|
36 """ |
|
37 Constructor |
|
38 """ |
|
39 QObject.__init__(self) |
|
40 |
|
41 self.__server = CooperationServer() |
|
42 self.__peers = collections.defaultdict(list) |
|
43 |
|
44 self.__initialConnection = None |
|
45 |
|
46 envVariables = ["USERNAME.*", "USER.*", "USERDOMAIN.*", |
|
47 "HOSTNAME.*", "DOMAINNAME.*"] |
|
48 environment = QProcess.systemEnvironment() |
|
49 found = False |
|
50 for envVariable in envVariables: |
|
51 for env in environment: |
|
52 if QRegExp(envVariable).exactMatch(env): |
|
53 envList = env.split("=") |
|
54 if len(envList) == 2: |
|
55 self.__username = envList[1].strip() |
|
56 found = True |
|
57 break |
|
58 |
|
59 if found: |
|
60 break |
|
61 |
|
62 if self.__username == "": |
|
63 self.__username = self.trUtf8("unknown") |
|
64 |
|
65 self.__server.newConnection.connect(self.__newConnection) |
|
66 |
|
67 def server(self): |
|
68 """ |
|
69 Public method to get a reference to the server. |
|
70 |
|
71 @return reference to the server object (CooperationServer) |
|
72 """ |
|
73 return self.__server |
|
74 |
|
75 def sendMessage(self, message): |
|
76 """ |
|
77 Public method to send a message. |
|
78 |
|
79 @param message message to be sent (string) |
|
80 """ |
|
81 if message == "": |
|
82 return |
|
83 |
|
84 for connectionList in self.__peers.values(): |
|
85 for connection in connectionList: |
|
86 connection.sendMessage(message) |
|
87 |
|
88 def nickName(self): |
|
89 """ |
|
90 Public method to get the nick name. |
|
91 |
|
92 @return nick name (string) |
|
93 """ |
|
94 return "{0}@{1}:{2}".format( |
|
95 self.__username, |
|
96 QHostInfo.localHostName(), |
|
97 self.__server.serverPort() |
|
98 ) |
|
99 |
|
100 def hasConnection(self, senderIp, senderPort = -1): |
|
101 """ |
|
102 Public method to check for an existing connection. |
|
103 |
|
104 @param senderIp address of the sender (QHostAddress) |
|
105 @param senderPort port of the sender (integer) |
|
106 @return flag indicating an existing connection (boolean) |
|
107 """ |
|
108 if senderPort == -1: |
|
109 return senderIp in self.__peers |
|
110 |
|
111 if senderIp not in self.__peers: |
|
112 return False |
|
113 |
|
114 for connection in self.__peers[senderIp]: |
|
115 if connection.peerPort() == senderPort: |
|
116 return True |
|
117 |
|
118 return False |
|
119 |
|
120 def hasConnections(self): |
|
121 """ |
|
122 Public method to check, if there are any connections established. |
|
123 |
|
124 @return flag indicating the presence of connections (boolean) |
|
125 """ |
|
126 for connectionList in self.__peers.values(): |
|
127 if connectionList: |
|
128 return True |
|
129 |
|
130 return False |
|
131 |
|
132 def __removeConnection(self, connection): |
|
133 """ |
|
134 Private method to remove a connection. |
|
135 |
|
136 @param connection reference to the connection to be removed (Connection) |
|
137 """ |
|
138 if connection.peerAddress() in self.__peers and \ |
|
139 connection in self.__peers[connection.peerAddress()]: |
|
140 self.__peers[connection.peerAddress()].remove(connection) |
|
141 nick = connection.name() |
|
142 if nick != "": |
|
143 self.participantLeft.emit(nick) |
|
144 |
|
145 connection.deleteLater() |
|
146 |
|
147 def disconnectConnections(self): |
|
148 """ |
|
149 Public slot to disconnect from the chat network. |
|
150 """ |
|
151 for connectionList in self.__peers.values(): |
|
152 while connectionList: |
|
153 self.__removeConnection(connectionList[0]) |
|
154 |
|
155 def __newConnection(self, connection): |
|
156 """ |
|
157 Private slot to handle a new connection. |
|
158 |
|
159 @param connection reference to the new connection (Connection) |
|
160 """ |
|
161 connection.setGreetingMessage(self.__username, |
|
162 self.__server.serverPort()) |
|
163 |
|
164 connection.error.connect(self.__connectionError) |
|
165 connection.disconnected.connect(self.__disconnected) |
|
166 connection.readyForUse.connect(self.__readyForUse) |
|
167 |
|
168 def __connectionError(self, socketError): |
|
169 """ |
|
170 Private slot to handle a connection error. |
|
171 |
|
172 @param socketError reference to the error object (QAbstractSocket.SocketError) |
|
173 """ |
|
174 connection = self.sender() |
|
175 if socketError != QAbstractSocket.RemoteHostClosedError: |
|
176 if connection.peerPort() != 0: |
|
177 msg = "{0}:{1}\n{2}\n".format( |
|
178 connection.peerAddress().toString(), |
|
179 connection.peerPort(), |
|
180 connection.errorString() |
|
181 ) |
|
182 else: |
|
183 msg = "{0}\n".format(connection.errorString()) |
|
184 self.connectionError.emit(msg) |
|
185 if connection == self.__initialConnection: |
|
186 self.cannotConnect.emit() |
|
187 self.__removeConnection(connection) |
|
188 |
|
189 def __disconnected(self): |
|
190 """ |
|
191 Private slot to handle the disconnection of a chat client. |
|
192 """ |
|
193 connection = self.sender() |
|
194 self.__removeConnection(connection) |
|
195 |
|
196 def __readyForUse(self): |
|
197 """ |
|
198 Private slot to handle a connection getting ready for use. |
|
199 """ |
|
200 connection = self.sender() |
|
201 if self.hasConnection(connection.peerAddress(), connection.peerPort()): |
|
202 return |
|
203 |
|
204 connection.newMessage.connect(self.newMessage) |
|
205 connection.getParticipants.connect(self.__getParticipants) |
|
206 |
|
207 self.__peers[connection.peerAddress()].append(connection) |
|
208 nick = connection.name() |
|
209 if nick != "": |
|
210 self.newParticipant.emit(nick) |
|
211 |
|
212 if connection == self.__initialConnection: |
|
213 connection.sendGetParticipants() |
|
214 self.__initialConnection = None |
|
215 |
|
216 def connectToHost(self, host, port): |
|
217 """ |
|
218 Public method to connect to a host. |
|
219 |
|
220 @param host host to connect to (string) |
|
221 @param port port to connect to (integer) |
|
222 """ |
|
223 self.__initialConnection = Connection(self) |
|
224 self.__newConnection(self.__initialConnection) |
|
225 self.__initialConnection.participants.connect(self.__processParticipants) |
|
226 self.__initialConnection.connectToHost(host, port) |
|
227 |
|
228 def __getParticipants(self): |
|
229 """ |
|
230 Private slot to handle the request for a list of participants. |
|
231 """ |
|
232 reqConnection = self.sender() |
|
233 participants = [] |
|
234 for connectionList in self.__peers.values(): |
|
235 for connection in connectionList: |
|
236 if connection != reqConnection: |
|
237 participants.append("{0}:{1}".format( |
|
238 connection.peerAddress().toString(), connection.serverPort())) |
|
239 reqConnection.sendParticipants(participants) |
|
240 |
|
241 def __processParticipants(self, participants): |
|
242 """ |
|
243 Private slot to handle the receipt of a list of participants. |
|
244 |
|
245 @param participants list of participants (list of strings of "host:port") |
|
246 """ |
|
247 for participant in participants: |
|
248 host, port = participant.split(":") |
|
249 port = int(port) |
|
250 |
|
251 if port == 0: |
|
252 msg = self.trUtf8("Illegal address: {0}:{1}\n").format(host, port) |
|
253 self.connectionError.emit(msg) |
|
254 else: |
|
255 if not self.hasConnection(QHostAddress(host), port): |
|
256 connection = Connection(self) |
|
257 self.__newConnection(connection) |
|
258 connection.connectToHost(host, port) |