33 (string) |
31 (string) |
34 @signal cannotConnect() emitted, if the initial connection fails |
32 @signal cannotConnect() emitted, if the initial connection fails |
35 @signal editorCommand(hash, filename, message) emitted when an editor |
33 @signal editorCommand(hash, filename, message) emitted when an editor |
36 command has been received (string, string, string) |
34 command has been received (string, string, string) |
37 """ |
35 """ |
|
36 |
38 newMessage = pyqtSignal(str, str) |
37 newMessage = pyqtSignal(str, str) |
39 newParticipant = pyqtSignal(str) |
38 newParticipant = pyqtSignal(str) |
40 participantLeft = pyqtSignal(str) |
39 participantLeft = pyqtSignal(str) |
41 connectionError = pyqtSignal(str) |
40 connectionError = pyqtSignal(str) |
42 cannotConnect = pyqtSignal() |
41 cannotConnect = pyqtSignal() |
43 editorCommand = pyqtSignal(str, str, str) |
42 editorCommand = pyqtSignal(str, str, str) |
44 |
43 |
45 def __init__(self, parent=None): |
44 def __init__(self, parent=None): |
46 """ |
45 """ |
47 Constructor |
46 Constructor |
48 |
47 |
49 @param parent reference to the parent object (QObject) |
48 @param parent reference to the parent object (QObject) |
50 """ |
49 """ |
51 super().__init__(parent) |
50 super().__init__(parent) |
52 |
51 |
53 self.__chatWidget = parent |
52 self.__chatWidget = parent |
54 |
53 |
55 self.__servers = [] |
54 self.__servers = [] |
56 for networkInterface in QNetworkInterface.allInterfaces(): |
55 for networkInterface in QNetworkInterface.allInterfaces(): |
57 for addressEntry in networkInterface.addressEntries(): |
56 for addressEntry in networkInterface.addressEntries(): |
58 address = addressEntry.ip() |
57 address = addressEntry.ip() |
59 # fix scope of link local addresses |
58 # fix scope of link local addresses |
60 if address.toString().lower().startswith("fe80"): |
59 if address.toString().lower().startswith("fe80"): |
61 address.setScopeId(networkInterface.humanReadableName()) |
60 address.setScopeId(networkInterface.humanReadableName()) |
62 server = CooperationServer(address, self) |
61 server = CooperationServer(address, self) |
63 server.newConnection.connect(self.__newConnection) |
62 server.newConnection.connect(self.__newConnection) |
64 self.__servers.append(server) |
63 self.__servers.append(server) |
65 |
64 |
66 self.__peers = collections.defaultdict(list) |
65 self.__peers = collections.defaultdict(list) |
67 |
66 |
68 self.__initialConnection = None |
67 self.__initialConnection = None |
69 |
68 |
70 envVariables = ["USERNAME", "USERDOMAIN", "USER", |
69 envVariables = ["USERNAME", "USERDOMAIN", "USER", "HOSTNAME", "DOMAINNAME"] |
71 "HOSTNAME", "DOMAINNAME"] |
|
72 environment = QProcess.systemEnvironment() |
70 environment = QProcess.systemEnvironment() |
73 found = False |
71 found = False |
74 for envVariable in envVariables: |
72 for envVariable in envVariables: |
75 for env in environment: |
73 for env in environment: |
76 if env.startswith(envVariable): |
74 if env.startswith(envVariable): |
77 envList = env.split("=") |
75 envList = env.split("=") |
78 if len(envList) == 2: |
76 if len(envList) == 2: |
79 self.__username = envList[1].strip() |
77 self.__username = envList[1].strip() |
80 found = True |
78 found = True |
81 break |
79 break |
82 |
80 |
83 if found: |
81 if found: |
84 break |
82 break |
85 |
83 |
86 if self.__username == "": |
84 if self.__username == "": |
87 self.__username = self.tr("unknown") |
85 self.__username = self.tr("unknown") |
88 |
86 |
89 self.__listening = False |
87 self.__listening = False |
90 self.__serversErrorString = "" |
88 self.__serversErrorString = "" |
91 |
89 |
92 def chatWidget(self): |
90 def chatWidget(self): |
93 """ |
91 """ |
94 Public method to get a reference to the chat widget. |
92 Public method to get a reference to the chat widget. |
95 |
93 |
96 @return reference to the chat widget (ChatWidget) |
94 @return reference to the chat widget (ChatWidget) |
97 """ |
95 """ |
98 return self.__chatWidget |
96 return self.__chatWidget |
99 |
97 |
100 def sendMessage(self, message): |
98 def sendMessage(self, message): |
101 """ |
99 """ |
102 Public method to send a message. |
100 Public method to send a message. |
103 |
101 |
104 @param message message to be sent (string) |
102 @param message message to be sent (string) |
105 """ |
103 """ |
106 if message == "": |
104 if message == "": |
107 return |
105 return |
108 |
106 |
109 for connectionList in self.__peers.values(): |
107 for connectionList in self.__peers.values(): |
110 for connection in connectionList: |
108 for connection in connectionList: |
111 connection.sendMessage(message) |
109 connection.sendMessage(message) |
112 |
110 |
113 def nickName(self): |
111 def nickName(self): |
114 """ |
112 """ |
115 Public method to get the nick name. |
113 Public method to get the nick name. |
116 |
114 |
117 @return nick name (string) |
115 @return nick name (string) |
118 """ |
116 """ |
119 return "{0}@{1}@{2}".format( |
117 return "{0}@{1}@{2}".format( |
120 self.__username, |
118 self.__username, QHostInfo.localHostName(), self.__servers[0].serverPort() |
121 QHostInfo.localHostName(), |
|
122 self.__servers[0].serverPort() |
|
123 ) |
119 ) |
124 |
120 |
125 def hasConnection(self, senderIp, senderPort=-1): |
121 def hasConnection(self, senderIp, senderPort=-1): |
126 """ |
122 """ |
127 Public method to check for an existing connection. |
123 Public method to check for an existing connection. |
128 |
124 |
129 @param senderIp address of the sender (QHostAddress) |
125 @param senderIp address of the sender (QHostAddress) |
130 @param senderPort port of the sender (integer) |
126 @param senderPort port of the sender (integer) |
131 @return flag indicating an existing connection (boolean) |
127 @return flag indicating an existing connection (boolean) |
132 """ |
128 """ |
133 if senderPort == -1: |
129 if senderPort == -1: |
134 return senderIp in self.__peers |
130 return senderIp in self.__peers |
135 |
131 |
136 if senderIp not in self.__peers: |
132 if senderIp not in self.__peers: |
137 return False |
133 return False |
138 |
134 |
139 return any(connection.peerPort() == senderPort |
135 return any( |
140 for connection in self.__peers[senderIp]) |
136 connection.peerPort() == senderPort for connection in self.__peers[senderIp] |
141 |
137 ) |
|
138 |
142 def hasConnections(self): |
139 def hasConnections(self): |
143 """ |
140 """ |
144 Public method to check, if there are any connections established. |
141 Public method to check, if there are any connections established. |
145 |
142 |
146 @return flag indicating the presence of connections (boolean) |
143 @return flag indicating the presence of connections (boolean) |
147 """ |
144 """ |
148 return any(bool(connectionList) |
145 return any(bool(connectionList) for connectionList in self.__peers.values()) |
149 for connectionList in self.__peers.values()) |
146 |
150 |
|
151 def removeConnection(self, connection): |
147 def removeConnection(self, connection): |
152 """ |
148 """ |
153 Public method to remove a connection. |
149 Public method to remove a connection. |
154 |
150 |
155 @param connection reference to the connection to be removed |
151 @param connection reference to the connection to be removed |
156 (Connection) |
152 (Connection) |
157 """ |
153 """ |
158 if (connection.peerAddress() in self.__peers and |
154 if ( |
159 connection in self.__peers[connection.peerAddress()]): |
155 connection.peerAddress() in self.__peers |
|
156 and connection in self.__peers[connection.peerAddress()] |
|
157 ): |
160 self.__peers[connection.peerAddress()].remove(connection) |
158 self.__peers[connection.peerAddress()].remove(connection) |
161 nick = connection.name() |
159 nick = connection.name() |
162 if nick != "": |
160 if nick != "": |
163 self.participantLeft.emit(nick) |
161 self.participantLeft.emit(nick) |
164 |
162 |
165 if connection.isValid(): |
163 if connection.isValid(): |
166 connection.abort() |
164 connection.abort() |
167 |
165 |
168 def disconnectConnections(self): |
166 def disconnectConnections(self): |
169 """ |
167 """ |
170 Public slot to disconnect from the chat network. |
168 Public slot to disconnect from the chat network. |
171 """ |
169 """ |
172 for connectionList in self.__peers.values(): |
170 for connectionList in self.__peers.values(): |
173 while connectionList: |
171 while connectionList: |
174 self.removeConnection(connectionList[0]) |
172 self.removeConnection(connectionList[0]) |
175 |
173 |
176 def __newConnection(self, connection): |
174 def __newConnection(self, connection): |
177 """ |
175 """ |
178 Private slot to handle a new connection. |
176 Private slot to handle a new connection. |
179 |
177 |
180 @param connection reference to the new connection (Connection) |
178 @param connection reference to the new connection (Connection) |
181 """ |
179 """ |
182 connection.setParent(self) |
180 connection.setParent(self) |
183 connection.setClient(self) |
181 connection.setClient(self) |
184 connection.setGreetingMessage(self.__username, |
182 connection.setGreetingMessage(self.__username, self.__servers[0].serverPort()) |
185 self.__servers[0].serverPort()) |
183 |
186 |
184 connection.error.connect(lambda err: self.__connectionError(err, connection)) |
187 connection.error.connect( |
185 connection.disconnected.connect(lambda: self.__disconnected(connection)) |
188 lambda err: self.__connectionError(err, connection)) |
186 connection.readyForUse.connect(lambda: self.__readyForUse(connection)) |
189 connection.disconnected.connect( |
|
190 lambda: self.__disconnected(connection)) |
|
191 connection.readyForUse.connect( |
|
192 lambda: self.__readyForUse(connection)) |
|
193 connection.rejected.connect(self.__connectionRejected) |
187 connection.rejected.connect(self.__connectionRejected) |
194 |
188 |
195 def __connectionRejected(self, msg): |
189 def __connectionRejected(self, msg): |
196 """ |
190 """ |
197 Private slot to handle the rejection of a connection. |
191 Private slot to handle the rejection of a connection. |
198 |
192 |
199 @param msg error message (string) |
193 @param msg error message (string) |
200 """ |
194 """ |
201 self.connectionError.emit(msg) |
195 self.connectionError.emit(msg) |
202 |
196 |
203 def __connectionError(self, socketError, connection): |
197 def __connectionError(self, socketError, connection): |
204 """ |
198 """ |
205 Private slot to handle a connection error. |
199 Private slot to handle a connection error. |
206 |
200 |
207 @param socketError reference to the error object |
201 @param socketError reference to the error object |
208 @type QAbstractSocket.SocketError |
202 @type QAbstractSocket.SocketError |
209 @param connection connection that caused the error |
203 @param connection connection that caused the error |
210 @type Connection |
204 @type Connection |
211 """ |
205 """ |
212 if socketError != QAbstractSocket.SocketError.RemoteHostClosedError: |
206 if socketError != QAbstractSocket.SocketError.RemoteHostClosedError: |
213 if connection.peerPort() != 0: |
207 if connection.peerPort() != 0: |
214 msg = "* {0}:{1}\n{2}\n".format( |
208 msg = "* {0}:{1}\n{2}\n".format( |
215 connection.peerAddress().toString(), |
209 connection.peerAddress().toString(), |
216 connection.peerPort(), |
210 connection.peerPort(), |
217 connection.errorString() |
211 connection.errorString(), |
218 ) |
212 ) |
219 else: |
213 else: |
220 msg = "* {0}\n".format(connection.errorString()) |
214 msg = "* {0}\n".format(connection.errorString()) |
221 self.connectionError.emit(msg) |
215 self.connectionError.emit(msg) |
222 if connection == self.__initialConnection: |
216 if connection == self.__initialConnection: |
223 self.cannotConnect.emit() |
217 self.cannotConnect.emit() |
224 self.removeConnection(connection) |
218 self.removeConnection(connection) |
225 |
219 |
226 def __disconnected(self, connection): |
220 def __disconnected(self, connection): |
227 """ |
221 """ |
228 Private slot to handle the disconnection of a chat client. |
222 Private slot to handle the disconnection of a chat client. |
229 |
223 |
230 @param connection connection that was disconnected |
224 @param connection connection that was disconnected |
231 @type Connection |
225 @type Connection |
232 """ |
226 """ |
233 self.removeConnection(connection) |
227 self.removeConnection(connection) |
234 |
228 |
235 def __readyForUse(self, connection): |
229 def __readyForUse(self, connection): |
236 """ |
230 """ |
237 Private slot to handle a connection getting ready for use. |
231 Private slot to handle a connection getting ready for use. |
238 |
232 |
239 @param connection connection that got ready for use |
233 @param connection connection that got ready for use |
240 @type Connection |
234 @type Connection |
241 """ |
235 """ |
242 if self.hasConnection(connection.peerAddress(), connection.peerPort()): |
236 if self.hasConnection(connection.peerAddress(), connection.peerPort()): |
243 return |
237 return |
244 |
238 |
245 connection.newMessage.connect(self.newMessage) |
239 connection.newMessage.connect(self.newMessage) |
246 connection.getParticipants.connect( |
240 connection.getParticipants.connect(lambda: self.__getParticipants(connection)) |
247 lambda: self.__getParticipants(connection)) |
|
248 connection.editorCommand.connect(self.editorCommand) |
241 connection.editorCommand.connect(self.editorCommand) |
249 |
242 |
250 self.__peers[connection.peerAddress()].append(connection) |
243 self.__peers[connection.peerAddress()].append(connection) |
251 nick = connection.name() |
244 nick = connection.name() |
252 if nick != "": |
245 if nick != "": |
253 self.newParticipant.emit(nick) |
246 self.newParticipant.emit(nick) |
254 |
247 |
255 if connection == self.__initialConnection: |
248 if connection == self.__initialConnection: |
256 connection.sendGetParticipants() |
249 connection.sendGetParticipants() |
257 self.__initialConnection = None |
250 self.__initialConnection = None |
258 |
251 |
259 def connectToHost(self, host, port): |
252 def connectToHost(self, host, port): |
260 """ |
253 """ |
261 Public method to connect to a host. |
254 Public method to connect to a host. |
262 |
255 |
263 @param host host to connect to (string) |
256 @param host host to connect to (string) |
264 @param port port to connect to (integer) |
257 @param port port to connect to (integer) |
265 """ |
258 """ |
266 self.__initialConnection = Connection(self) |
259 self.__initialConnection = Connection(self) |
267 self.__newConnection(self.__initialConnection) |
260 self.__newConnection(self.__initialConnection) |
268 self.__initialConnection.participants.connect( |
261 self.__initialConnection.participants.connect(self.__processParticipants) |
269 self.__processParticipants) |
|
270 self.__initialConnection.connectToHost(host, port) |
262 self.__initialConnection.connectToHost(host, port) |
271 |
263 |
272 def __getParticipants(self, reqConnection): |
264 def __getParticipants(self, reqConnection): |
273 """ |
265 """ |
274 Private slot to handle the request for a list of participants. |
266 Private slot to handle the request for a list of participants. |
275 |
267 |
276 @param reqConnection reference to the connection to get |
268 @param reqConnection reference to the connection to get |
277 participants for |
269 participants for |
278 @type Connection |
270 @type Connection |
279 """ |
271 """ |
280 participants = [] |
272 participants = [] |
281 for connectionList in self.__peers.values(): |
273 for connectionList in self.__peers.values(): |
282 for connection in connectionList: |
274 for connection in connectionList: |
283 if connection != reqConnection: |
275 if connection != reqConnection: |
284 participants.append("{0}@{1}".format( |
276 participants.append( |
285 connection.peerAddress().toString(), |
277 "{0}@{1}".format( |
286 connection.serverPort())) |
278 connection.peerAddress().toString(), connection.serverPort() |
|
279 ) |
|
280 ) |
287 reqConnection.sendParticipants(participants) |
281 reqConnection.sendParticipants(participants) |
288 |
282 |
289 def __processParticipants(self, participants): |
283 def __processParticipants(self, participants): |
290 """ |
284 """ |
291 Private slot to handle the receipt of a list of participants. |
285 Private slot to handle the receipt of a list of participants. |
292 |
286 |
293 @param participants list of participants (list of strings of |
287 @param participants list of participants (list of strings of |
294 "host:port") |
288 "host:port") |
295 """ |
289 """ |
296 for participant in participants: |
290 for participant in participants: |
297 host, port = participant.split("@") |
291 host, port = participant.split("@") |
298 port = int(port) |
292 port = int(port) |
299 |
293 |
300 if port == 0: |
294 if port == 0: |
301 msg = self.tr("Illegal address: {0}@{1}\n").format( |
295 msg = self.tr("Illegal address: {0}@{1}\n").format(host, port) |
302 host, port) |
|
303 self.connectionError.emit(msg) |
296 self.connectionError.emit(msg) |
304 else: |
297 else: |
305 if not self.hasConnection(QHostAddress(host), port): |
298 if not self.hasConnection(QHostAddress(host), port): |
306 connection = Connection(self) |
299 connection = Connection(self) |
307 self.__newConnection(connection) |
300 self.__newConnection(connection) |
308 connection.connectToHost(host, port) |
301 connection.connectToHost(host, port) |
309 |
302 |
310 def sendEditorCommand(self, projectHash, filename, message): |
303 def sendEditorCommand(self, projectHash, filename, message): |
311 """ |
304 """ |
312 Public method to send an editor command. |
305 Public method to send an editor command. |
313 |
306 |
314 @param projectHash hash of the project (string) |
307 @param projectHash hash of the project (string) |
315 @param filename project relative universal file name of |
308 @param filename project relative universal file name of |
316 the sending editor (string) |
309 the sending editor (string) |
317 @param message editor command to be sent (string) |
310 @param message editor command to be sent (string) |
318 """ |
311 """ |
319 for connectionList in self.__peers.values(): |
312 for connectionList in self.__peers.values(): |
320 for connection in connectionList: |
313 for connection in connectionList: |
321 connection.sendEditorCommand(projectHash, filename, message) |
314 connection.sendEditorCommand(projectHash, filename, message) |
322 |
315 |
323 def __findConnections(self, nick): |
316 def __findConnections(self, nick): |
324 """ |
317 """ |
325 Private method to get a list of connection given a nick name. |
318 Private method to get a list of connection given a nick name. |
326 |
319 |
327 @param nick nick name in the format of self.nickName() (string) |
320 @param nick nick name in the format of self.nickName() (string) |
328 @return list of references to the connection objects (list of |
321 @return list of references to the connection objects (list of |
329 Connection) |
322 Connection) |
330 """ |
323 """ |
331 if "@" not in nick: |
324 if "@" not in nick: |
332 # nick given in wrong format |
325 # nick given in wrong format |
333 return [] |
326 return [] |
334 |
327 |
335 user, host, port = nick.split("@") |
328 user, host, port = nick.split("@") |
336 senderIp = QHostAddress(host) |
329 senderIp = QHostAddress(host) |
337 |
330 |
338 if senderIp not in self.__peers: |
331 if senderIp not in self.__peers: |
339 return [] |
332 return [] |
340 |
333 |
341 return self.__peers[senderIp][:] |
334 return self.__peers[senderIp][:] |
342 |
335 |
343 def kickUser(self, nick): |
336 def kickUser(self, nick): |
344 """ |
337 """ |
345 Public method to kick a user by its nick name. |
338 Public method to kick a user by its nick name. |
346 |
339 |
347 @param nick nick name in the format of self.nickName() (string) |
340 @param nick nick name in the format of self.nickName() (string) |
348 """ |
341 """ |
349 for connection in self.__findConnections(nick): |
342 for connection in self.__findConnections(nick): |
350 connection.abort() |
343 connection.abort() |
351 |
344 |
352 def banUser(self, nick): |
345 def banUser(self, nick): |
353 """ |
346 """ |
354 Public method to ban a user by its nick name. |
347 Public method to ban a user by its nick name. |
355 |
348 |
356 @param nick nick name in the format of self.nickName() (string) |
349 @param nick nick name in the format of self.nickName() (string) |
357 """ |
350 """ |
358 Preferences.syncPreferences() |
351 Preferences.syncPreferences() |
359 user = nick.rsplit("@")[0] |
352 user = nick.rsplit("@")[0] |
360 bannedUsers = Preferences.getCooperation("BannedUsers")[:] |
353 bannedUsers = Preferences.getCooperation("BannedUsers")[:] |
361 if user not in bannedUsers: |
354 if user not in bannedUsers: |
362 bannedUsers.append(user) |
355 bannedUsers.append(user) |
363 Preferences.setCooperation("BannedUsers", bannedUsers) |
356 Preferences.setCooperation("BannedUsers", bannedUsers) |
364 |
357 |
365 def banKickUser(self, nick): |
358 def banKickUser(self, nick): |
366 """ |
359 """ |
367 Public method to ban and kick a user by its nick name. |
360 Public method to ban and kick a user by its nick name. |
368 |
361 |
369 @param nick nick name in the format of self.nickName() (string) |
362 @param nick nick name in the format of self.nickName() (string) |
370 """ |
363 """ |
371 self.banUser(nick) |
364 self.banUser(nick) |
372 self.kickUser(nick) |
365 self.kickUser(nick) |
373 |
366 |
374 def startListening(self, port=-1): |
367 def startListening(self, port=-1): |
375 """ |
368 """ |
376 Public method to start listening for new connections. |
369 Public method to start listening for new connections. |
377 |
370 |
378 @param port port to listen on (integer) |
371 @param port port to listen on (integer) |
379 @return tuple giving a flag indicating success (boolean) and |
372 @return tuple giving a flag indicating success (boolean) and |
380 the port the server listens on |
373 the port the server listens on |
381 """ |
374 """ |
382 if self.__servers: |
375 if self.__servers: |