1 | ############################################################################ |
---|
2 | # |
---|
3 | # SAGE UI - A Graphical User Interface for SAGE |
---|
4 | # Copyright (C) 2005 Electronic Visualization Laboratory, |
---|
5 | # University of Illinois at Chicago |
---|
6 | # |
---|
7 | # All rights reserved. |
---|
8 | # |
---|
9 | # Redistribution and use in source and binary forms, with or without |
---|
10 | # modification, are permitted provided that the following conditions are met: |
---|
11 | # |
---|
12 | # * Redistributions of source code must retain the above copyright |
---|
13 | # notice, this list of conditions and the following disclaimer. |
---|
14 | # * Redistributions in binary form must reproduce the above |
---|
15 | # copyright notice, this list of conditions and the following disclaimer |
---|
16 | # in the documentation and/or other materials provided with the distribution. |
---|
17 | # * Neither the name of the University of Illinois at Chicago nor |
---|
18 | # the names of its contributors may be used to endorse or promote |
---|
19 | # products derived from this software without specific prior written permission. |
---|
20 | # |
---|
21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
---|
22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
---|
23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
---|
24 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
---|
25 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
---|
26 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
---|
27 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
---|
28 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
---|
29 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
---|
30 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
---|
31 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
32 | # |
---|
33 | # Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum |
---|
34 | # |
---|
35 | # Author: Ratko Jagodic |
---|
36 | # |
---|
37 | ############################################################################ |
---|
38 | |
---|
39 | |
---|
40 | from threading import Thread, RLock |
---|
41 | import time, socket, string, os.path, sys, wx |
---|
42 | |
---|
43 | from globals import * |
---|
44 | from Mywx import MyButton |
---|
45 | from sageui import AboutDialog |
---|
46 | import preferences as prefs |
---|
47 | |
---|
48 | # some globals for this module |
---|
49 | CHUNK_SIZE = 2048 #4096 |
---|
50 | SOCKET_TIMEOUT = 1 |
---|
51 | MSGLEN = CHUNK_SIZE |
---|
52 | SEPARATOR = '\0' |
---|
53 | |
---|
54 | |
---|
55 | |
---|
56 | # MESSAGES: |
---|
57 | # |
---|
58 | # |
---|
59 | # Notes: |
---|
60 | # -------------------- |
---|
61 | # All messages are sent in this format (as strings): |
---|
62 | # code |
---|
63 | # data |
---|
64 | # |
---|
65 | # For example: |
---|
66 | # "2002" |
---|
67 | # "Ratko" |
---|
68 | # |
---|
69 | # |
---|
70 | # All machines are always keyed by machineId that the users should connect to to control SAGE |
---|
71 | # |
---|
72 | # |
---|
73 | # <<< UI ---> SERVER >>> |
---|
74 | # CODE FORMAT MESSAGE |
---|
75 | # ---------------------------------------------------------------- |
---|
76 | # 2000 username register this user with the Users server |
---|
77 | # info |
---|
78 | # machine_id the machines the user is connected to |
---|
79 | # |
---|
80 | # 2001 from={username} send a chat message to one person or to all |
---|
81 | # to={"all" | id} id = specific to users connected to a sepcific SAGE machine |
---|
82 | # message |
---|
83 | # |
---|
84 | # 2002 username check username for duplicates |
---|
85 | # |
---|
86 | # 2003 username unregister this username from the machine specified |
---|
87 | # machine_id |
---|
88 | # |
---|
89 | # |
---|
90 | # <<< SERVER ---> UI >>> |
---|
91 | # CODE FORMAT MESSAGE |
---|
92 | # ---------------------------------------------------------------- |
---|
93 | # |
---|
94 | # 30000 machine_name a status list of all the MACHINES registered with the server |
---|
95 | # ip |
---|
96 | # port |
---|
97 | # machineId |
---|
98 | # alive={"1" | "0"} if the machine is alive, send 1, otherwise send 0 |
---|
99 | # displayInfo in this format: "Xtiles Ytiles tileWidth tileHeight" |
---|
100 | # "\x00" < --- a break between different blocks of data |
---|
101 | # machine_name |
---|
102 | # ip |
---|
103 | # port |
---|
104 | # machineId |
---|
105 | # alive={"1" | "0"} if the machine is alive, send 1, otherwise send 0 |
---|
106 | # displayInfo in this format: "Xtiles Ytiles tileWidth tileHeight" |
---|
107 | # "\x00" |
---|
108 | # .... |
---|
109 | # |
---|
110 | # |
---|
111 | # 30001 username receive a list of USERS that are connected and their info |
---|
112 | # info |
---|
113 | # machine_id the machines the user is connected to |
---|
114 | # machine_id |
---|
115 | # "\x00" < --- a break between different users' info |
---|
116 | # username |
---|
117 | # info |
---|
118 | # machine_id |
---|
119 | # "\x00" |
---|
120 | # .... |
---|
121 | # |
---|
122 | # 30002 from={username} receive a chat message from one person, |
---|
123 | # to={"all" | id} id = specific to users connected to a specific SAGE machine |
---|
124 | # message |
---|
125 | # |
---|
126 | # 30003 "1" | "0" 1=username OK, 0=username already in use |
---|
127 | # |
---|
128 | # 31000 message an informative message... just any string |
---|
129 | # |
---|
130 | # |
---|
131 | # |
---|
132 | # <<< SAGE ---> SERVER >>> |
---|
133 | # CODE FORMAT MESSAGE |
---|
134 | # ---------------------------------------------------------------- |
---|
135 | # |
---|
136 | # 100 machine_name "i am alive" message from SAGE |
---|
137 | # ip < --- machine ip and port that SAGE UIs should connect to |
---|
138 | # port |
---|
139 | # |
---|
140 | |
---|
141 | |
---|
142 | |
---|
143 | |
---|
144 | class MachineListCtrl(wx.ListCtrl): |
---|
145 | |
---|
146 | def __init__(self, dialogClass, parent, pos, size): |
---|
147 | wx.ListCtrl.__init__(self, parent, -1, pos, size, style=wx.LC_SINGLE_SEL | wx.LC_REPORT | wx.LC_NO_HEADER) |
---|
148 | if "__WXMAC__" not in wx.PlatformInfo: |
---|
149 | self.SetTextColour(wx.WHITE) |
---|
150 | self.SetBackgroundColour(appPanelColor) |
---|
151 | self.parent = parent |
---|
152 | self.usersData = getUsersData() |
---|
153 | self.host = None |
---|
154 | self.port = 20001 |
---|
155 | self.hostname = "" |
---|
156 | self.dialogClass = dialogClass |
---|
157 | |
---|
158 | # holds the two images for |
---|
159 | imageList = wx.ImageList(16, 16) |
---|
160 | greenImg = wx.Image(ConvertPath("images/green_circle.gif")) |
---|
161 | redImg = wx.Image(ConvertPath("images/red_circle.gif")) |
---|
162 | greenBmp = greenImg.Rescale(16,16).ConvertToBitmap() |
---|
163 | redBmp = redImg.Rescale(16,16).ConvertToBitmap() |
---|
164 | self.greenIndex = imageList.Add(greenBmp) |
---|
165 | self.redIndex = imageList.Add(redBmp) |
---|
166 | self.AssignImageList(imageList, wx.IMAGE_LIST_SMALL) |
---|
167 | self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnDoubleClick) |
---|
168 | self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectItem) |
---|
169 | self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnDeselectItem) |
---|
170 | |
---|
171 | self.Bind(wx.EVT_MOTION, self.OnMouseMotion) |
---|
172 | self.currentIndex = -1 |
---|
173 | |
---|
174 | self.InsertColumn(0, "", wx.LIST_FORMAT_LEFT) |
---|
175 | self.SetColumnWidth(0, self.GetSize().width-2) |
---|
176 | |
---|
177 | self.machineHash = {} #contains all the machines currently displayed in the list |
---|
178 | self.RefreshMachineList() #this will fill the self.machineHash and fill the list with it |
---|
179 | |
---|
180 | |
---|
181 | def OnSelectItem(self, evt): |
---|
182 | self.dialogClass.EnableButtons() |
---|
183 | |
---|
184 | def OnDeselectItem(self, evt): |
---|
185 | self.dialogClass.EnableButtons(False) |
---|
186 | |
---|
187 | |
---|
188 | #returns the last selected SAGEMachine, if none was selected, returns None |
---|
189 | def GetSelectedMachine(self): |
---|
190 | index = self.GetFirstSelected() |
---|
191 | if self.toolTipHash.has_key(index): |
---|
192 | return self.toolTipHash[index] |
---|
193 | return None |
---|
194 | |
---|
195 | |
---|
196 | # gets the hash of machines from the config file, |
---|
197 | # gets the hash of machines received from the server, |
---|
198 | # combines them into one hash (self.machineHash), |
---|
199 | # if there are overlaps, the one from the server gets precedence |
---|
200 | # everything is keyed by the ID of each machine and the data |
---|
201 | # stored in every hash is actually a bunch of SAGEMachines |
---|
202 | def RefreshMachineList(self, data=None): |
---|
203 | self.DeleteAllItems() #first delete everything |
---|
204 | |
---|
205 | #now update the hash holding all the machines |
---|
206 | self.machineHash = {} |
---|
207 | self.machineHash = self.usersData.GetMachinesStatus().copy() |
---|
208 | |
---|
209 | # insert the sorted list of machines into the control |
---|
210 | self.toolTipHash = {} # a hash of sageMachines indexed by their ListCtrl index |
---|
211 | index = 0 |
---|
212 | machines = self.machineHash.values() |
---|
213 | machines.sort(lambda x, y: cmp(x.GetName().lower(), y.GetName().lower())) |
---|
214 | for sageMachine in machines: |
---|
215 | self.toolTipHash[index] = sageMachine #used for creation of correct tooltips |
---|
216 | self.InsertRow(sageMachine, index) |
---|
217 | index = index + 1 |
---|
218 | |
---|
219 | self.dialogClass.EnableButtons(False) |
---|
220 | |
---|
221 | |
---|
222 | ## def GetMachineForAutoload(self, name): |
---|
223 | ## self.machineHash = {} |
---|
224 | ## print "usersData machinestatus: ", self.usersData.GetMachinesStatus() |
---|
225 | ## self.machineHash = self.usersData.GetMachinesStatus().copy() |
---|
226 | ## return self.GetMachineByName(name) |
---|
227 | |
---|
228 | |
---|
229 | # inserts an item into the ListCtrl and sets the appropriate icon for it |
---|
230 | # it compares the machines based on their IDs |
---|
231 | def InsertRow(self, sageMachine, index=-1): |
---|
232 | if index == -1: #if true, insert at the end |
---|
233 | index = self.GetItemCount() |
---|
234 | |
---|
235 | if self.usersData.HasMachine(sageMachine.GetId()): # set the correct icon |
---|
236 | if sageMachine.IsAlive(): |
---|
237 | self.InsertImageStringItem(index, sageMachine.GetName(), self.greenIndex ) |
---|
238 | else: #the machine is not running but the room is still open since there are users in it |
---|
239 | self.InsertImageStringItem(index, sageMachine.GetName(), self.redIndex ) |
---|
240 | else: #the machine is not even running |
---|
241 | self.InsertImageStringItem(index, sageMachine.GetName(), self.redIndex ) |
---|
242 | |
---|
243 | if self.GetItemCount() > self.GetCountPerPage(): # a visual fix |
---|
244 | self.SetColumnWidth(0, self.GetSize().width - 25 - 2) |
---|
245 | else: |
---|
246 | self.SetColumnWidth(0, self.GetSize().width - 2) |
---|
247 | |
---|
248 | |
---|
249 | #--------------------------------------------------------------- |
---|
250 | # for tooltips |
---|
251 | #--------------------------------------------------------------- |
---|
252 | |
---|
253 | def OnMouseMotion(self, event): |
---|
254 | return |
---|
255 | (index, flag) = self.HitTest(event.GetPosition()) |
---|
256 | if not index == wx.NOT_FOUND: |
---|
257 | if self.currentIndex == index: # if the tooltip is already displayed, just return |
---|
258 | return |
---|
259 | self.currentIndex = index |
---|
260 | #sageMachine = self.GetMachineByName( self.GetItem(index).GetText() ) |
---|
261 | self.MakeToolTip(self.toolTipHash[index]) |
---|
262 | |
---|
263 | |
---|
264 | def GetMachineByName(self, name): #returns the first machine with the specified name, or None |
---|
265 | for sageMachine in self.machineHash.itervalues(): |
---|
266 | if sageMachine.GetName() == name: |
---|
267 | return sageMachine |
---|
268 | return None |
---|
269 | |
---|
270 | |
---|
271 | def MakeToolTip(self, sageMachine): |
---|
272 | name = string.upper(sageMachine.GetName()) |
---|
273 | ip = "\nIP: "+sageMachine.GetIP() |
---|
274 | port = "\nPort: "+str(sageMachine.GetPort()) |
---|
275 | if sageMachine.GetNumTiles()[0] == 0: |
---|
276 | size = "\nSize: ? x ?" |
---|
277 | else: |
---|
278 | size = "\nSize: "+str(sageMachine.GetNumTiles()[0])+" x "+str(sageMachine.GetNumTiles()[1]) |
---|
279 | ttString = name + ip + port + size |
---|
280 | tt = wx.ToolTip(ttString) |
---|
281 | tt.SetDelay(300) |
---|
282 | self.SetToolTip(tt) |
---|
283 | |
---|
284 | #--------------------------------------------------------------- |
---|
285 | |
---|
286 | def OnDoubleClick(self, event): |
---|
287 | self.hostname = self.GetItemText(event.GetIndex()) |
---|
288 | if hasattr(self.parent, "IsModal") and self.parent.IsModal(): |
---|
289 | self.parent.EndModal(wx.ID_OK) |
---|
290 | |
---|
291 | |
---|
292 | # when the user presses "Add" button |
---|
293 | def AddMachine(self, name, host, port, sysIP, sysPort): |
---|
294 | newMachine = SAGEMachine(name, host, port, sysIP, sysPort, host+":"+str(port), False) |
---|
295 | prefs.machines.AddMachine(newMachine) # add it to the preferences |
---|
296 | self.usersData.AddNewSAGEMachine(newMachine) # and then to the list of all the machines |
---|
297 | self.RefreshMachineList() #this will refresh the list |
---|
298 | |
---|
299 | |
---|
300 | # when the user presses "Delete" button |
---|
301 | def RemoveMachine(self, sageMachine): |
---|
302 | prefs.machines.RemoveMachine(sageMachine) # remove from preferences |
---|
303 | self.usersData.RemoveMachine(sageMachine) # now remove from the datastructure as well |
---|
304 | self.RefreshMachineList() # refresh the list visually |
---|
305 | |
---|
306 | |
---|
307 | |
---|
308 | |
---|
309 | |
---|
310 | |
---|
311 | |
---|
312 | |
---|
313 | class ConnectionDialog: |
---|
314 | |
---|
315 | def __init__(self, sageGate, usersClient, usersServerIP="sage.sl.startap.net", usersServerPort=15558, firstConnection=True, autologinMachine=None): |
---|
316 | self.sageGate = sageGate |
---|
317 | self.client = usersClient |
---|
318 | self.usersData = getUsersData() |
---|
319 | self.selectedMachine = None |
---|
320 | self.firstConnection = firstConnection # first SAGE connection for this UI |
---|
321 | self.__firstTry = True # first try of this connection... to disable autologin after the first try |
---|
322 | self.connectedToServer = False |
---|
323 | self.autologinMachine = autologinMachine |
---|
324 | if self.firstConnection: |
---|
325 | if not self.client.Connect(usersServerIP, usersServerPort): |
---|
326 | dlg = wx.MessageDialog(None, "Could not connect to sage server \"" + usersServerIP + "\". Chat will be unavailable." |
---|
327 | , "Connection Error", style=wx.OK) |
---|
328 | dlg.ShowModal() |
---|
329 | dlg.Destroy() |
---|
330 | else: |
---|
331 | self.connectedToServer = True |
---|
332 | self.usernameOK = None #it's None until we get a reply from the UsersServer about the validity of the chosen username |
---|
333 | |
---|
334 | # data produced by this dialog |
---|
335 | self.machine = None |
---|
336 | |
---|
337 | |
---|
338 | # the machine that we are trying to connect to |
---|
339 | def GetMachine(self): |
---|
340 | return self.machine |
---|
341 | |
---|
342 | |
---|
343 | # show the initial connection dialog |
---|
344 | # if "Connect" was pressed, check the username for duplicate and if that's OK, |
---|
345 | # try to connect to SAGE |
---|
346 | def ShowDialog(self): |
---|
347 | while True: # loop until the user gets it right or quits |
---|
348 | if not self.ShowConfigDialog(): #user pressed "Quit" |
---|
349 | return False |
---|
350 | else: # first check the username with the UsersServer and then try to connect to SAGE |
---|
351 | if self.TryStart(): |
---|
352 | if self.firstConnection: |
---|
353 | self.usersData.UnregisterUICallback(30000) #FIX, for multiple connections |
---|
354 | return True |
---|
355 | else: # something failed so show the dialog again |
---|
356 | self.usernameOK = None # so that we have to check the username again |
---|
357 | |
---|
358 | |
---|
359 | # this does all the checks |
---|
360 | def TryStart(self): |
---|
361 | if self.firstConnection and self.connectedToServer: |
---|
362 | usernameResult = self.CheckUsername() |
---|
363 | if usernameResult == False: |
---|
364 | dlg = wx.MessageDialog(None, "Username \"" + self.usersData.GetMyUsername() + "\" already taken. Please choose another one." |
---|
365 | , "Username Invalid", style=wx.OK) |
---|
366 | dlg.ShowModal() |
---|
367 | dlg.Destroy() |
---|
368 | return False |
---|
369 | |
---|
370 | elif usernameResult == -1: #connection failed but we'll let the user continue |
---|
371 | dlg = wx.MessageDialog(None, "Connection to sage server failed.\nChat will be unavailable." |
---|
372 | , "Connection Failed", style=wx.OK) |
---|
373 | dlg.ShowModal() |
---|
374 | dlg.Destroy() |
---|
375 | |
---|
376 | # if we got here, username checked out OK so try to connect to SAGE and Register |
---|
377 | if self.ConnectToSAGE(): |
---|
378 | if self.firstConnection or (not self.usersData.AmIConnectedTo(self.machine.GetId())): |
---|
379 | self.RegisterUser() #only register if the connection to SAGE succeeded |
---|
380 | return True |
---|
381 | else: |
---|
382 | return False |
---|
383 | |
---|
384 | |
---|
385 | # enables or disables the buttons that require that a selection in the list |
---|
386 | # is made before proceeding (Connect and Delete buttons notably) |
---|
387 | def EnableButtons(self, doEnable=True): |
---|
388 | if hasattr(self, "okBtn"): |
---|
389 | if doEnable: |
---|
390 | self.okBtn.Enable(True) |
---|
391 | self.delButton.Enable(True) |
---|
392 | self.infoButton.Enable(True) |
---|
393 | else: |
---|
394 | self.okBtn.Enable(False) |
---|
395 | self.delButton.Enable(False) |
---|
396 | self.infoButton.Enable(False) |
---|
397 | |
---|
398 | |
---|
399 | def ShowConfigDialog(self): |
---|
400 | |
---|
401 | # UI elements |
---|
402 | yOffset = 7 |
---|
403 | dialog = wx.Dialog(None, -1, "SAGE Connection")#, size=(200, 240))#, style = wx.SIMPLE_BORDER) |
---|
404 | if "__WXMAC__" not in wx.PlatformInfo: |
---|
405 | dialog.SetBackgroundColour(dialogColor) |
---|
406 | dialog.SetForegroundColour(wx.WHITE) |
---|
407 | dialog.SetClientSizeWH(200, 300+yOffset) |
---|
408 | |
---|
409 | #helpButton = MyButton(dialog, (170, 10+yOffset), (20,20), self.OnHelp, "images/help.png", tTip="Help") |
---|
410 | self.machineList = MachineListCtrl(self, dialog, (20,35+yOffset), (160,125)) |
---|
411 | if self.firstConnection: |
---|
412 | self.usersData.RegisterUICallback( 30000, self.machineList.RefreshMachineList) #so that we receive updates even if we are not registered yet |
---|
413 | text = wx.StaticText(dialog, -1, "Connect To:", (10, 10+yOffset)) |
---|
414 | addButton = wx.Button(dialog, -1, "Add", (15, 160+yOffset), (50, 20)) |
---|
415 | addButton.SetClientSize((50, 20)) |
---|
416 | smallFont = addButton.GetFont() |
---|
417 | smallFont.SetPointSize(smallFont.GetPointSize() - smallFont.GetPointSize()/5) |
---|
418 | addButton.SetFont(smallFont) |
---|
419 | self.infoButton = wx.Button(dialog, -1, "Info", (75, 160+yOffset), (50, 20)) |
---|
420 | self.infoButton.SetClientSize((50,20)) |
---|
421 | self.infoButton.SetFont(smallFont) |
---|
422 | self.infoButton.Enable(False) |
---|
423 | self.delButton = wx.Button(dialog, -1, "Delete", (135, 160+yOffset), (50, 20)) |
---|
424 | self.delButton.Enable(False) |
---|
425 | self.delButton.SetClientSize((50, 20)) |
---|
426 | self.delButton.SetFont(smallFont) |
---|
427 | self.okBtn = wx.Button(dialog, wx.ID_OK, "Connect", (10, 264+yOffset)) |
---|
428 | self.okBtn.Enable(False) |
---|
429 | if self.firstConnection: #if it's a first connection, allow the user to enter a username |
---|
430 | cancelBtn = wx.Button(dialog, wx.ID_CANCEL, "Quit", (110, 264+yOffset)) |
---|
431 | userLabel = wx.StaticText(dialog, -1, "Connect As:", (10, 195+yOffset)) |
---|
432 | initialUsername = prefs.usernames.GetDefault() |
---|
433 | userText = wx.ComboBox(dialog, -1, initialUsername, (10,215+yOffset), (180,25), choices=prefs.usernames.GetUsernames(), style=wx.CB_DROPDOWN) |
---|
434 | userText.SetForegroundColour(wx.BLACK) |
---|
435 | userText.Bind(wx.EVT_KEY_DOWN, self.OnEnter, userText) |
---|
436 | userText.SetFocus() |
---|
437 | else: # login under the same name but to a another machine |
---|
438 | cancelBtn = wx.Button(dialog, wx.ID_CANCEL, "Cancel", (110, 264+yOffset)) |
---|
439 | userLabel = wx.StaticText(dialog, -1, "Connect As: "+self.usersData.GetMyUsername(), (10, 205)) |
---|
440 | |
---|
441 | dialog.Bind(wx.EVT_BUTTON, self.OnAddButton, addButton) |
---|
442 | dialog.Bind(wx.EVT_BUTTON, self.OnDelButton, self.delButton) |
---|
443 | dialog.Bind(wx.EVT_BUTTON, self.OnInfoButton, self.infoButton) |
---|
444 | #### - end UI elements |
---|
445 | |
---|
446 | |
---|
447 | # try autologging first... if that fails display the dialog |
---|
448 | if self.__firstTry and self.autologinMachine and self.usernameOK == None: |
---|
449 | print "Autologin to:", self.autologinMachine |
---|
450 | tries = 0 |
---|
451 | while not self.machineList.GetMachineByName(self.autologinMachine) and tries < 10: |
---|
452 | time.sleep(0.5) |
---|
453 | tries += 1 |
---|
454 | self.machineList.RefreshMachineList() |
---|
455 | print "Trying to autologin... try#", tries |
---|
456 | self.machine = self.machineList.GetMachineByName(self.autologinMachine) |
---|
457 | |
---|
458 | |
---|
459 | # so that we don't try to autologin after the first try... |
---|
460 | # go back to the manual mode |
---|
461 | if self.__firstTry: self.__firstTry = False |
---|
462 | else: self.machine = None |
---|
463 | |
---|
464 | |
---|
465 | # display the dialog |
---|
466 | if not self.machine: |
---|
467 | while self.machineList.GetFirstSelected() == -1: #loop until the user selects something |
---|
468 | if not dialog.ShowModal() == wx.ID_OK: |
---|
469 | return False |
---|
470 | self.machine = self.machineList.GetSelectedMachine() #get the selected SAGEMachine |
---|
471 | |
---|
472 | if self.firstConnection: #get the username... but only if this is our first connection |
---|
473 | if userText.GetValue() == "": #if user entered nothing, use the default |
---|
474 | self.usersData.SetMyUsername("No Name") |
---|
475 | else: |
---|
476 | self.usersData.SetMyUsername( userText.GetValue() ) |
---|
477 | prefs.usernames.AddUsername(userText.GetValue()) |
---|
478 | self.usersData.SetMyInfo("no info") |
---|
479 | return True |
---|
480 | |
---|
481 | |
---|
482 | def ShowNewConnectionDialog(self): |
---|
483 | dialog = wx.Dialog(None, -1, "New Connection", size=(260, 250))#, style = wx.SIMPLE_BORDER) |
---|
484 | dialog.SetClientSizeWH(250, 250) |
---|
485 | |
---|
486 | hostLabel = wx.StaticText(dialog, -1, "Host:", (20, 20)) |
---|
487 | hostText = wx.TextCtrl(dialog, -1, "", (20, 40), (120, 22)) |
---|
488 | portLabel = wx.StaticText(dialog, -1, "Port:", (160, 20)) |
---|
489 | portText = wx.TextCtrl(dialog, -1, "20001", (160, 40), (70, 22)) |
---|
490 | |
---|
491 | sysIPLabel = wx.StaticText(dialog, -1, "System IP (optional):", (20, 90)) |
---|
492 | sysIPText = wx.TextCtrl(dialog, -1, "", (20, 110), (120, 22)) |
---|
493 | sysPortLabel = wx.StaticText(dialog, -1, "System Port:", (160, 90)) |
---|
494 | sysPortText = wx.TextCtrl(dialog, -1, "20002", (160, 110), (70, 22)) |
---|
495 | |
---|
496 | labelLabel = wx.StaticText(dialog, -1, "Label this connection as:", (20, 160)) |
---|
497 | labelText = wx.TextCtrl(dialog, -1, "", (20, 180), (140, 22)) |
---|
498 | self.okBtn = wx.Button(dialog, wx.ID_OK, "Save", (40, 210)) |
---|
499 | cancelBtn = wx.Button(dialog, wx.ID_CANCEL, "Cancel", (140, 210)) |
---|
500 | |
---|
501 | # so that the user can just click ENTER when they are done entering the data |
---|
502 | labelText.Bind(wx.EVT_KEY_DOWN, self.OnEnter, labelText) |
---|
503 | |
---|
504 | if dialog.ShowModal() == wx.ID_OK: |
---|
505 | return (labelText.GetValue(), hostText.GetValue(), portText.GetValue(), sysIPText.GetValue(), sysPortText.GetValue()) |
---|
506 | else: |
---|
507 | return (None, None, None, None, None) |
---|
508 | |
---|
509 | |
---|
510 | def OnHelp(self): |
---|
511 | AboutDialog(None) |
---|
512 | |
---|
513 | |
---|
514 | # sends a request to the usersServer asking if the username already exists |
---|
515 | def CheckUsername(self): |
---|
516 | if self.client.IsConnected(): |
---|
517 | self.client.CheckUsername(self.usersData.GetMyUsername()) |
---|
518 | self.totalTime = 0 |
---|
519 | return self.WaitForUsernameReply() |
---|
520 | else: #we are not even connected so dont bother |
---|
521 | return -1 |
---|
522 | |
---|
523 | |
---|
524 | # returns: True if username is valid, False if invalid and0 |
---|
525 | # -1 if something went wrong but we should still continue execution w/o the chat |
---|
526 | def RegisterUser(self): |
---|
527 | if self.client.IsConnected(): |
---|
528 | self.client.Register( self.machine.GetId() ) |
---|
529 | |
---|
530 | |
---|
531 | # waits for 5 seconds and sleeps every 1/2 second |
---|
532 | # once the message from UsersServer arrives informing us of validity of |
---|
533 | # the chosen username, this function returns True if username was accepted or False otherwise |
---|
534 | def WaitForUsernameReply(self): |
---|
535 | while self.client.usernameOK == None: |
---|
536 | time.sleep(0.5) |
---|
537 | self.totalTime = self.totalTime + 0.5 |
---|
538 | if self.totalTime > 3: #dont wait forever... if we waited longer than 5 secs... return -1 (means "failed") |
---|
539 | self.totalTime = 0 |
---|
540 | return -1 |
---|
541 | else: # we got a reply back |
---|
542 | self.totalTime = 0 |
---|
543 | return self.client.usernameOK |
---|
544 | |
---|
545 | |
---|
546 | # try to connect to the selected machine |
---|
547 | def ConnectToSAGE(self): |
---|
548 | retVal = self.sageGate.connectToSage( self.machine.GetIP(), self.machine.GetPort() ) |
---|
549 | if retVal == 1: |
---|
550 | return True |
---|
551 | elif retVal == -1: |
---|
552 | dlg = wx.MessageDialog(None, "Could not connect to the appLauncher on " + self.machine.GetName(), "AppLauncher Connection Failed", wx.OK) |
---|
553 | dlg.ShowModal() |
---|
554 | return True |
---|
555 | else: |
---|
556 | dlg = wx.MessageDialog(None, "Could not connect to SAGE on " + self.machine.GetName() + " (" + self.machine.GetId() + ")", "Connection Failed", wx.OK) |
---|
557 | dlg.ShowModal() |
---|
558 | return False |
---|
559 | |
---|
560 | |
---|
561 | # so that the user can press ENTER to connect (when focus is on the username text field) |
---|
562 | def OnEnter(self, evt): |
---|
563 | if evt.GetKeyCode() == wx.WXK_RETURN: |
---|
564 | obj = evt.GetEventObject() |
---|
565 | if hasattr(obj, "EndModal"): #since we use this in NewConnection and Config dialogs |
---|
566 | obj.EndModal(wx.ID_OK) #OnEnter was bound to a dialog |
---|
567 | else: |
---|
568 | obj.GetParent().EndModal(wx.ID_OK) #OnEnter was bound to a child of a dialog |
---|
569 | else: |
---|
570 | evt.Skip() |
---|
571 | |
---|
572 | |
---|
573 | # shows the info about a selected machine |
---|
574 | def OnInfoButton(self, evt): |
---|
575 | sageMachine = self.machineList.GetSelectedMachine() |
---|
576 | if sageMachine == None: |
---|
577 | return |
---|
578 | |
---|
579 | info = "MACHINE: " + str(sageMachine.GetName()) |
---|
580 | info += "\n-----------------------------------------------------" |
---|
581 | info += "\nIP ADDRESS: " + str(sageMachine.GetIP()) |
---|
582 | info += "\nPORT FOR SAGE UI CONNECTIONS: " + str(sageMachine.GetPort()) |
---|
583 | info += "\nSYSTEM IP: " + sageMachine.GetSystemIP() |
---|
584 | info += "\nSYSTEM PORT: " + sageMachine.GetSystemPort() |
---|
585 | if sageMachine.IsAlive(): |
---|
586 | info += "\nRUNNING:\tYes" |
---|
587 | else: |
---|
588 | info += "\nRUNNING:\tNo" |
---|
589 | info += "\nTILE ARRANGEMENT:\t"+str(sageMachine.GetNumTiles()[0])+" x "+str(sageMachine.GetNumTiles()[1]) |
---|
590 | info += "\nDISPLAY SIZE:\t"+str(sageMachine.GetDisplaySize()[0])+" x "+str(sageMachine.GetDisplaySize()[1])+" pixels" |
---|
591 | info += "\nTILE SIZE:\t"+str(sageMachine.GetTileSize()[0])+" x "+str(sageMachine.GetTileSize()[1])+ " pixels" |
---|
592 | |
---|
593 | dialog = wx.MessageDialog(None, info, "Details for "+str(sageMachine.GetName()), style=wx.OK) |
---|
594 | dialog.ShowModal() |
---|
595 | dialog.Destroy() |
---|
596 | |
---|
597 | |
---|
598 | def OnAddButton(self, evt): |
---|
599 | while True: |
---|
600 | (name, host, port, sysIP, sysPort) = self.ShowNewConnectionDialog() |
---|
601 | if host==None: #user pressed "Cancel" |
---|
602 | return |
---|
603 | break |
---|
604 | if len(sysIP) < 2: |
---|
605 | sysIP = host #in case user hasnt filled the sysIP field |
---|
606 | self.machineList.AddMachine(name, host, port, sysIP, sysPort) #inserts the item into the machineList and update the config file |
---|
607 | |
---|
608 | |
---|
609 | def OnDelButton(self, evt): |
---|
610 | selectedMachine = self.machineList.GetSelectedMachine() |
---|
611 | if not selectedMachine == None: #if the user actually has something selected |
---|
612 | self.machineList.RemoveMachine(selectedMachine) |
---|
613 | |
---|
614 | |
---|
615 | |
---|
616 | |
---|
617 | |
---|
618 | class ChatRoom(wx.Panel): |
---|
619 | |
---|
620 | def __init__(self, parent, machineId, machine_name, usersClient): |
---|
621 | wx.Panel.__init__(self, parent, -1) |
---|
622 | self.SetBackgroundColour(dialogColor) |
---|
623 | self.SetForegroundColour(wx.WHITE) |
---|
624 | self.client = usersClient |
---|
625 | self.machineId = machineId #the machine this chatRoom corresponds to |
---|
626 | self.machine_name = machine_name #the machine this chatRoom corresponds to |
---|
627 | self.usersData = getUsersData() |
---|
628 | |
---|
629 | self.MakeControls() |
---|
630 | |
---|
631 | |
---|
632 | def GetId(self): |
---|
633 | return self.machineId |
---|
634 | |
---|
635 | def GetMachineName(self): |
---|
636 | return self.machine_name |
---|
637 | |
---|
638 | |
---|
639 | def MakeControls(self): |
---|
640 | # create the sizer and the panel |
---|
641 | self.mainSizer = wx.BoxSizer(wx.VERTICAL) |
---|
642 | self.mainSizer.Fit(self) |
---|
643 | self.SetSizer(self.mainSizer) |
---|
644 | |
---|
645 | # create the users list |
---|
646 | self.userListLabel = wx.StaticText(self, -1, "Connected Users:") |
---|
647 | self.userList = wx.ListBox(self, -1, choices=self.usersData.GetUsernames(self.machineId))#, style=wx.LB_SINGLE) |
---|
648 | self.userList.SetSizeHints(-1, -1, -1, maxH = 200) |
---|
649 | |
---|
650 | # create the chat window |
---|
651 | self.chatLabel = wx.StaticText(self, -1, "Chat Window:") |
---|
652 | self.chatWindow = wx.SplitterWindow(self, -1, style = wx.SP_3DSASH )#| wx.SP_LIVE_UPDATE ) |
---|
653 | self.typePanel = wx.TextCtrl(self.chatWindow, -1, "Type your message here and press ENTER to send", style=wx.TE_MULTILINE ) |
---|
654 | self.typePanel.Bind(wx.EVT_KEY_DOWN, self.SendMessage) #when you press enter, the message is sent |
---|
655 | self.typePanel.Bind(wx.EVT_SET_FOCUS, self.OnFocus) #when it receives focus, remove the info |
---|
656 | self.chatPanel = wx.TextCtrl(self.chatWindow, -1, style=wx.TE_MULTILINE | wx.TE_RICH | wx.TE_LINEWRAP) |
---|
657 | self.chatPanel.SetEditable(False) |
---|
658 | |
---|
659 | self.chatWindow.SetMinimumPaneSize(45) |
---|
660 | self.chatWindow.SplitHorizontally(self.chatPanel, self.typePanel, -45) |
---|
661 | self.chatWindow.SetBackgroundColour(dialogColor)#appPanelColor) |
---|
662 | self.chatWindow.SetForegroundColour(wx.WHITE) |
---|
663 | # add them to the sizer in the right order |
---|
664 | self.mainSizer.Add(self.userListLabel, 0, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.TOP | wx.LEFT, border=10) |
---|
665 | self.mainSizer.Add(self.userList, 1, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.ALL | wx.EXPAND, border=5) |
---|
666 | self.mainSizer.Add(self.chatLabel, 0, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.TOP | wx.LEFT, border=10) |
---|
667 | self.mainSizer.Add(self.chatWindow, 2, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.ALL | wx.EXPAND, border=5) |
---|
668 | #self.mainSizer.Layout() #causes "QPixmap: Invalid pixmap parameters" error |
---|
669 | self.chatWindow.SetSashGravity(1.0) #must set the gravity here, otherwise default size gets screwed up |
---|
670 | |
---|
671 | if "__WXMSW__" in wx.PlatformInfo: #windows has problems showing splitterWindow in the beginning |
---|
672 | e = wx.SizeEvent(self.chatWindow.GetSize()) |
---|
673 | self.chatWindow.ProcessEvent(e) |
---|
674 | |
---|
675 | # send a message when user presses ENTER inside the typePanel |
---|
676 | def SendMessage(self, event): |
---|
677 | if event.GetKeyCode() == wx.WXK_RETURN or event.GetKeyCode() == wx.WXK_NUMPAD_ENTER: |
---|
678 | if not self.client.IsConnected(): #if we are not even connected, dont send anything |
---|
679 | dlg = wx.MessageDialog(None, "Message could not be sent since there is no connection to the sage server." |
---|
680 | , "Message not sent", style=wx.OK) |
---|
681 | dlg.ShowModal() |
---|
682 | dlg.Destroy() |
---|
683 | return |
---|
684 | |
---|
685 | if self.typePanel.GetValue() != "": #did the user actually type something |
---|
686 | if not self.usersData.AmIConnectedTo(self.GetId()): #if the message was sent from a room that this user is not logged into |
---|
687 | if self.usersData.HasMachine(self.usersData.GetMyConnections()[0]): |
---|
688 | fromUser = self.usersData.GetMyUsername() + "@" + self.usersData.GetMachine(self.usersData.GetMyConnections()[0]).GetName() |
---|
689 | else: |
---|
690 | fromUser = self.usersData.GetMyUsername() + "@none" |
---|
691 | else: #i am in the room that i am connected to so just print my name |
---|
692 | fromUser = self.usersData.GetMyUsername() |
---|
693 | self.client.SendChatMessage(fromUser, self.GetId(), self.typePanel.GetValue()) |
---|
694 | self.typePanel.Clear() |
---|
695 | else: |
---|
696 | event.Skip() |
---|
697 | |
---|
698 | #when it receives focus, remove the info "Type your message here and press ENTER to send" |
---|
699 | def OnFocus(self, event): |
---|
700 | self.typePanel.Clear() |
---|
701 | |
---|
702 | |
---|
703 | #------------------------------------------------------- |
---|
704 | # MESSAGE CALLBACKS |
---|
705 | #------------------------------------------------------- |
---|
706 | |
---|
707 | def OnUsersStatus(self): |
---|
708 | # update the ListBox control with the new data from UsersDatastructure |
---|
709 | self.userList.Set(self.usersData.GetUsernames(self.GetId())) |
---|
710 | |
---|
711 | |
---|
712 | #just write the message to the screen with the right font properties |
---|
713 | def OnChatMessage(self, fromUser, message): |
---|
714 | self.chatPanel.SetDefaultStyle(wx.TextAttr(wx.BLUE)) |
---|
715 | self.chatPanel.SetInsertionPointEnd() |
---|
716 | self.chatPanel.AppendText(fromUser + ": ") |
---|
717 | self.chatPanel.SetDefaultStyle(wx.TextAttr(wx.BLACK)) |
---|
718 | self.chatPanel.AppendText(message+"\n") |
---|
719 | |
---|
720 | |
---|
721 | |
---|
722 | |
---|
723 | ############################################################################ |
---|
724 | # |
---|
725 | # CLASS: UsersPanel |
---|
726 | # |
---|
727 | # DESCRIPTION: This is the panel on the side of SAGE UI that contains the |
---|
728 | # list of users logged in to this SAGE environment. It also |
---|
729 | # enables users to chat. |
---|
730 | # |
---|
731 | # DATE: May, 2005 |
---|
732 | # |
---|
733 | ############################################################################ |
---|
734 | |
---|
735 | class UsersPanel(wx.Frame): |
---|
736 | |
---|
737 | def __init__(self, parent, usersClient): |
---|
738 | wx.Frame.__init__(self, parent, -1, "SAGE UI Chat")#, style= wx.SIMPLE_BORDER | wx.NO_FULL_REPAINT_ON_RESIZE) |
---|
739 | #self.SetBackgroundColour(wx.Colour(155,200,200)) |
---|
740 | #self.SetBackgroundColour(dialogColor) |
---|
741 | #self.SetForegroundColour(wx.WHITE) |
---|
742 | self.Bind(wx.EVT_CLOSE, self.OnCloseFrame) |
---|
743 | self.Bind(wx.EVT_MOVE, self.OnMove) |
---|
744 | self.frame = parent |
---|
745 | self.sticky = True #if it moves together with the main frame |
---|
746 | |
---|
747 | #resize and position it correctly |
---|
748 | self.SetSize( (300, 500) ) |
---|
749 | self.OnFrameMove() |
---|
750 | |
---|
751 | # register functions that will be called when messages arrive |
---|
752 | self.client = usersClient |
---|
753 | self.usersData = getUsersData() |
---|
754 | self.usersData.RegisterUICallback( 30000, self.OnMachinesStatus ) |
---|
755 | self.usersData.RegisterUICallback( 30001, self.OnUsersStatus ) |
---|
756 | self.usersData.RegisterUICallback( 30002, self.OnChatMessage ) |
---|
757 | |
---|
758 | # local data storage |
---|
759 | self.CreateNotebook() |
---|
760 | #self.Show() < -- this is done in DisplayCanvas.OnStatusMessage() (canvases.py) |
---|
761 | |
---|
762 | |
---|
763 | def CreateNotebook(self): |
---|
764 | self.notebook = wx.Notebook(self, -1) |
---|
765 | #self.notebook.SetBackgroundColour(appPanelColor) |
---|
766 | #self.notebook.SetForegroundColour(wx.WHITE) |
---|
767 | self.chatRooms = {} # a collection of ChatRooms objects (basically pages in self.notebook) |
---|
768 | |
---|
769 | # first make the image list: |
---|
770 | il = wx.ImageList(16, 16) |
---|
771 | aliveImage = wx.Image(ConvertPath("images/green_circle.gif")) |
---|
772 | aliveBmp = aliveImage.Rescale(16,16).ConvertToBitmap() |
---|
773 | |
---|
774 | deadImage = wx.Image(ConvertPath("images/red_circle.gif")) |
---|
775 | deadBmp = deadImage.Rescale(16,16).ConvertToBitmap() |
---|
776 | il.Add( aliveBmp ) |
---|
777 | il.Add( deadBmp ) |
---|
778 | self.notebook.AssignImageList(il) |
---|
779 | |
---|
780 | # create the pages (chat rooms) for every SAGE machine that's currently running |
---|
781 | # but first, insert the "All" page in the beginning |
---|
782 | self.chatRooms["all"] = ChatRoom(self.notebook, "all", "all", self.client) |
---|
783 | self.notebook.AddPage(self.chatRooms["all"], "All") |
---|
784 | |
---|
785 | # now add the rest of them |
---|
786 | for machineId in self.usersData.GetMachinesStatus(): |
---|
787 | if self.usersData.AmIConnectedTo(machineId): |
---|
788 | self.AddChatRoom(machineId, doSelect=True) |
---|
789 | else: |
---|
790 | self.AddChatRoom(machineId) |
---|
791 | |
---|
792 | |
---|
793 | #make this frame follow the main frame if sticky |
---|
794 | def OnMove(self, evt): |
---|
795 | frameX = self.frame.GetSize().width + self.frame.GetPosition().x |
---|
796 | frameY = self.frame.GetPosition().y |
---|
797 | if abs(evt.GetPosition().x - frameX) < 40: |
---|
798 | self.sticky = True |
---|
799 | self.OnFrameMove() |
---|
800 | else: |
---|
801 | self.sticky = False |
---|
802 | |
---|
803 | |
---|
804 | # called whenever the main frame moves so that this Users Frame follows it |
---|
805 | def OnFrameMove(self): |
---|
806 | if self.sticky: |
---|
807 | x = self.frame.GetSize().width + self.frame.GetPosition().x + 3 |
---|
808 | y = self.frame.GetPosition().y + 20 |
---|
809 | self.MoveXY(x,y) |
---|
810 | |
---|
811 | |
---|
812 | def OnCloseFrame(self, evt): |
---|
813 | self.Hide() |
---|
814 | self.frame.miOptionsShowChat.Check(False) |
---|
815 | |
---|
816 | |
---|
817 | def Disconnect(self): |
---|
818 | self.client.Disconnect() |
---|
819 | |
---|
820 | |
---|
821 | def IsConnected(self): |
---|
822 | return self.client.IsConnected() |
---|
823 | |
---|
824 | |
---|
825 | def AddChatRoom(self, machineId, machine_name="All", doSelect=False): |
---|
826 | if self.usersData.HasMachine(machineId): #if false, assign a custom name that was passed in |
---|
827 | machine_name = self.usersData.GetMachine(machineId).GetName() |
---|
828 | |
---|
829 | # now create the page and add it to the notebook |
---|
830 | self.chatRooms[machineId] = ChatRoom(self.notebook, machineId, machine_name, self.client) |
---|
831 | self.notebook.AddPage(self.chatRooms[machineId], machine_name, select=doSelect) |
---|
832 | |
---|
833 | # set the appropriate icon for it |
---|
834 | if not machine_name == "All": #"All" has no icon |
---|
835 | if self.usersData.HasMachine(machineId) and self.usersData.GetMachine(machineId).IsAlive(): |
---|
836 | self.notebook.SetPageImage(self.notebook.GetPageCount()-1, 0) |
---|
837 | else: |
---|
838 | self.notebook.SetPageImage(self.notebook.GetPageCount()-1, 1) |
---|
839 | |
---|
840 | |
---|
841 | #------------------------------------------------------- |
---|
842 | # MESSAGE CALLBACKS |
---|
843 | #------------------------------------------------------- |
---|
844 | |
---|
845 | def OnMachinesStatus(self): |
---|
846 | # first create new pages for new machines |
---|
847 | for machineId in self.usersData.GetMachinesStatus(): |
---|
848 | if not self.chatRooms.has_key(machineId): #if the room doesnt exist already... |
---|
849 | self.AddChatRoom(machineId) |
---|
850 | |
---|
851 | # now update the icons for pages that are no longer valid (ie the machine died) |
---|
852 | pageIndexToDelete = -1 |
---|
853 | pageIdToDelete = -1 |
---|
854 | for pageIndex in range(1, self.notebook.GetPageCount()): #start from 1 since the 0th page is "All" = no icon |
---|
855 | chatRoom = self.notebook.GetPage(pageIndex) |
---|
856 | machineId = chatRoom.GetId() |
---|
857 | if not self.usersData.HasMachine(machineId): #the machine died and there are no more people in that room, so remove the tab |
---|
858 | pageIndexToDelete = pageIndex # perform delayed deletion since page indices |
---|
859 | pageIdToDelete = machineId # would not be valid if we deleted a page now |
---|
860 | else: #otherwise, the machine may not be running but there are still people in there so don't remove the tab |
---|
861 | if self.usersData.GetMachine(machineId).IsAlive(): |
---|
862 | self.notebook.SetPageImage(pageIndex, 0) |
---|
863 | else: |
---|
864 | self.notebook.SetPageImage(pageIndex, 1) #the machine died but there are still people in that room, so keep it open |
---|
865 | |
---|
866 | # finally delete the page (if there were any for deletion) |
---|
867 | if pageIndexToDelete > -1: |
---|
868 | self.notebook.DeletePage(pageIndexToDelete) |
---|
869 | del self.chatRooms[pageIdToDelete] |
---|
870 | |
---|
871 | |
---|
872 | def OnUsersStatus(self): |
---|
873 | # update all the chatRooms with the new data from UsersDatastructure |
---|
874 | try: |
---|
875 | for chatRoom in self.chatRooms.itervalues(): |
---|
876 | chatRoom.OnUsersStatus() |
---|
877 | except wx.PyDeadObjectError: # a fix so that the error doesnt show up on exit (it happens because some events might still be in the queue) |
---|
878 | pass |
---|
879 | |
---|
880 | |
---|
881 | def OnChatMessage(self, message): |
---|
882 | tokens = string.split(message, "\n", 2) |
---|
883 | fromUser = tokens[0] |
---|
884 | toRoom = tokens[1] |
---|
885 | message = tokens[2] |
---|
886 | |
---|
887 | # write the message to the appropriate window |
---|
888 | if toRoom == "all": |
---|
889 | for chatRoom in self.chatRooms.itervalues(): |
---|
890 | chatRoom.OnChatMessage(fromUser, message) |
---|
891 | else: #send a message to the specific room |
---|
892 | if self.chatRooms.has_key(toRoom): |
---|
893 | self.chatRooms[toRoom].OnChatMessage(fromUser, message) |
---|
894 | |
---|
895 | |
---|
896 | |
---|
897 | |
---|
898 | |
---|
899 | ############################################################################ |
---|
900 | # |
---|
901 | # CLASS: UsersClient |
---|
902 | # |
---|
903 | # DESCRIPTION: This is the connection to the SAGE users server. It connects |
---|
904 | # to the server, it receives and sends messages. The receiving |
---|
905 | # is done in a thread that constantly loops and checks for new |
---|
906 | # messages on a socket. It sends messages about the apps that |
---|
907 | # this UI started and also sends all the chat messages. |
---|
908 | # |
---|
909 | # DATE: May, 2005 |
---|
910 | # |
---|
911 | ############################################################################ |
---|
912 | |
---|
913 | class UsersClient: |
---|
914 | |
---|
915 | def __init__(self): |
---|
916 | self.connected = False |
---|
917 | self.threadKilled = False |
---|
918 | self.usersData = getUsersData() |
---|
919 | self.usernameOK = None |
---|
920 | |
---|
921 | |
---|
922 | #------------------------------------------------------- |
---|
923 | # CONNECTION |
---|
924 | #------------------------------------------------------- |
---|
925 | |
---|
926 | def IsConnected(self): |
---|
927 | return self.connected |
---|
928 | |
---|
929 | |
---|
930 | def Connect(self, host, port): |
---|
931 | if self.connected: return False |
---|
932 | |
---|
933 | # make the socket |
---|
934 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
---|
935 | if self.socket is None: |
---|
936 | print "\n\nUsersClient: socket could not be created" |
---|
937 | return False |
---|
938 | |
---|
939 | # set the timeout the first time to 5 secs so that we dont wait for too long when |
---|
940 | # you cant connect to the users server... the timeout will be reset below |
---|
941 | self.socket.settimeout(2) |
---|
942 | |
---|
943 | # try to connect |
---|
944 | try: |
---|
945 | self.socket.connect((host, port)) |
---|
946 | except socket.error, socket.timeout: |
---|
947 | print "\n\nUsersClient: can't connect to SAGE UI Users Server." |
---|
948 | return False |
---|
949 | |
---|
950 | # set some socket options |
---|
951 | self.socket.settimeout(SOCKET_TIMEOUT) |
---|
952 | |
---|
953 | |
---|
954 | # start the receiver in a thread |
---|
955 | self.receiverThread = Thread(target=self.Receiver, args=()) |
---|
956 | self.receiverThread.start() |
---|
957 | self.threadKilled = False |
---|
958 | self.connected = True |
---|
959 | |
---|
960 | return True |
---|
961 | |
---|
962 | |
---|
963 | |
---|
964 | def Disconnect(self): |
---|
965 | #if not self.connected: return False |
---|
966 | |
---|
967 | self.threadKilled = True |
---|
968 | self.connected = False |
---|
969 | |
---|
970 | if hasattr(self, "receiverThread"): |
---|
971 | self.receiverThread.join() |
---|
972 | |
---|
973 | self.socket.close() |
---|
974 | del self.socket |
---|
975 | return True |
---|
976 | |
---|
977 | |
---|
978 | #------------------------------------------------------- |
---|
979 | # SENDING |
---|
980 | #------------------------------------------------------- |
---|
981 | |
---|
982 | # for formatting the messages correctly and sending them |
---|
983 | def MakeMsg(self,code,data): |
---|
984 | if not self.connected: |
---|
985 | print "UsersClient: not connected to server, message",code,"was not sent" |
---|
986 | return False |
---|
987 | |
---|
988 | msg = '%8s\n%s' %(code,data) |
---|
989 | msg = msg + ' ' * (MSGLEN-len(msg)) |
---|
990 | self.Send(msg) |
---|
991 | return msg |
---|
992 | |
---|
993 | |
---|
994 | # for sending string data (used by MakeMsg) |
---|
995 | def Send(self, msg): |
---|
996 | try: |
---|
997 | self.socket.send(msg) |
---|
998 | except socket.error: |
---|
999 | print "UsersClient: Could not send message - socket error" |
---|
1000 | self.connected = False |
---|
1001 | |
---|
1002 | |
---|
1003 | # register with the UsersServer |
---|
1004 | def Register(self, machineId): |
---|
1005 | data = self.usersData.GetMyUsername() + "\n" + self.usersData.GetMyInfo() + "\n" + machineId |
---|
1006 | self.usersData.AddMyConnection(machineId) |
---|
1007 | self.MakeMsg(2000, data) |
---|
1008 | |
---|
1009 | |
---|
1010 | def Unregister(self, machineId): |
---|
1011 | data = self.usersData.GetMyUsername() + "\n" + machineId |
---|
1012 | self.usersData.RemoveMyConnection(machineId) |
---|
1013 | self.MakeMsg(2003, data) |
---|
1014 | |
---|
1015 | |
---|
1016 | def SendChatMessage(self, fromUser, toRoom, message): |
---|
1017 | data = fromUser + "\n" + toRoom + "\n" + message |
---|
1018 | self.MakeMsg(2001, data) |
---|
1019 | |
---|
1020 | |
---|
1021 | def CheckUsername(self, username): |
---|
1022 | self.usernameOK = None |
---|
1023 | self.MakeMsg(2002, username + "\n" + str(getUIVersion())) |
---|
1024 | |
---|
1025 | |
---|
1026 | #------------------------------------------------------- |
---|
1027 | # RECEIVING |
---|
1028 | #------------------------------------------------------- |
---|
1029 | |
---|
1030 | # this runs in a thread, loops forever and receives messages |
---|
1031 | def Receiver(self): |
---|
1032 | |
---|
1033 | while not self.threadKilled: |
---|
1034 | try: |
---|
1035 | |
---|
1036 | msg = self.socket.recv(CHUNK_SIZE) #retrieve the message from the socket |
---|
1037 | if len( msg ) < 2: |
---|
1038 | print "UsersClient: connection closed" |
---|
1039 | break |
---|
1040 | |
---|
1041 | # since recv is not guaranteed to receive exactly CHUNK_SIZE bytes |
---|
1042 | # so keep receiving until it gets the whole chunk |
---|
1043 | while len( msg ) < CHUNK_SIZE: |
---|
1044 | msg = msg + self.socket.recv(CHUNK_SIZE - len( msg)) |
---|
1045 | |
---|
1046 | if self.threadKilled: |
---|
1047 | break |
---|
1048 | |
---|
1049 | # strip all the empty spaces from the message and retrieve useful information |
---|
1050 | cleanMsg = self.CleanBuffer( msg ) |
---|
1051 | cleanMsg = string.strip( cleanMsg ) |
---|
1052 | msgHeader = string.split(cleanMsg, "\n", 1) |
---|
1053 | if len(msgHeader) < 1: |
---|
1054 | continue |
---|
1055 | code = int(msgHeader[0]) |
---|
1056 | if len(msgHeader) == 1: |
---|
1057 | data = "" |
---|
1058 | else: |
---|
1059 | data = msgHeader[1] |
---|
1060 | |
---|
1061 | |
---|
1062 | except socket.timeout: |
---|
1063 | continue |
---|
1064 | except socket.error: |
---|
1065 | print "UsersClient: socket error on socket.receive" |
---|
1066 | break |
---|
1067 | #except: |
---|
1068 | #print 'UsersClient: exception occured in Receiver: ', sys.exc_info()[0], sys.exc_info()[1] |
---|
1069 | # break |
---|
1070 | |
---|
1071 | |
---|
1072 | ################## |
---|
1073 | # Call function for updating ui-information |
---|
1074 | # wx.CallAfter is used because we pass this onto the GUI thread |
---|
1075 | # to do since wx functions need to be called from the main thread (the GUI thread) |
---|
1076 | ########################################### |
---|
1077 | |
---|
1078 | # call the appropriate function to update the datastructure |
---|
1079 | if code == 30000: |
---|
1080 | self.usersData.OnMachinesStatus(data) |
---|
1081 | if code == 30001: |
---|
1082 | wx.CallAfter(self.usersData.OnUsersStatus, data) |
---|
1083 | if code == 30002: |
---|
1084 | if data == "": continue |
---|
1085 | wx.CallAfter(self.usersData.OnChatMessage, data ) |
---|
1086 | if code == 30003: |
---|
1087 | self.SaveUsernameOK(data) |
---|
1088 | |
---|
1089 | # exited the while loop so we are not connected anymore |
---|
1090 | self.connected = False |
---|
1091 | self.threadKilled = True |
---|
1092 | |
---|
1093 | |
---|
1094 | # converts all non-printable characters from the buffer to white spaces |
---|
1095 | # (so that they can be removed using string.strip() function) |
---|
1096 | def CleanBuffer( self, stBuffer ): |
---|
1097 | stNewBuffer = "" |
---|
1098 | |
---|
1099 | for ch in stBuffer: |
---|
1100 | if ch == '\0': #this is our message separator so handle it separately |
---|
1101 | stNewBuffer += ch |
---|
1102 | elif ch in string.printable: |
---|
1103 | stNewBuffer += ch |
---|
1104 | else: |
---|
1105 | stNewBuffer += " " |
---|
1106 | return stNewBuffer |
---|
1107 | |
---|
1108 | |
---|
1109 | # when user tries to register, it first has to check the username so |
---|
1110 | # this message comes back to confirm the validity of it |
---|
1111 | def SaveUsernameOK(self, data): |
---|
1112 | self.usernameOK = bool(int(data)) |
---|
1113 | |
---|
1114 | |
---|
1115 | |
---|
1116 | |
---|
1117 | |
---|
1118 | # a data structure to hold all the machines, users and so on |
---|
1119 | class UsersDatastructure: |
---|
1120 | |
---|
1121 | def __init__(self): |
---|
1122 | self.usersStatusHash = {} |
---|
1123 | self.machinesStatusHash = {} |
---|
1124 | self.uiCallback = {} # functions for updating the UI |
---|
1125 | self.myconnections = [] # the machines this user is connected to (SAGEMachine objects keyed by machineId) |
---|
1126 | self._username = "No Name" # this user's username |
---|
1127 | self._info = "No Info" # this user's info |
---|
1128 | self.machinesStatusHash.update(prefs.machines.GetMachineHash().copy()) |
---|
1129 | |
---|
1130 | |
---|
1131 | # for updating the UI once the datastructure has been updated |
---|
1132 | def RegisterUICallback(self, code, func): |
---|
1133 | self.uiCallback[code] = func |
---|
1134 | |
---|
1135 | def UnregisterUICallback(self, code): |
---|
1136 | if code in self.uiCallback: |
---|
1137 | del self.uiCallback[code] |
---|
1138 | |
---|
1139 | |
---|
1140 | ##### UPDATE DATA ----------------------------------------------------- |
---|
1141 | |
---|
1142 | # updates the data structure when the new machine status comes in |
---|
1143 | def OnMachinesStatus(self, data): |
---|
1144 | machines = string.split(data, SEPARATOR) |
---|
1145 | self.ClearMachinesStatus() |
---|
1146 | |
---|
1147 | # first add the machines from the preferences |
---|
1148 | self.machinesStatusHash.update(prefs.machines.GetMachineHash().copy()) |
---|
1149 | if data != "": # when no machines are reported we get an empty message from the server |
---|
1150 | # now add the machines from the server (but they first need to be created) |
---|
1151 | for machine in machines: |
---|
1152 | machineData = machine.splitlines() |
---|
1153 | name = machineData[0] |
---|
1154 | ip = machineData[1] |
---|
1155 | port = machineData[2] |
---|
1156 | machineId = machineData[3] |
---|
1157 | alive = bool( int(machineData[4]) ) |
---|
1158 | displayInfo = str(machineData[5]) |
---|
1159 | displayData = string.split(displayInfo) #extract the data from the displayInfo string |
---|
1160 | if len(machineData) > 6: # the first time we connect we dont get the system port/ip |
---|
1161 | (sysIP, sysPort) = machineData[6].split() |
---|
1162 | else: |
---|
1163 | (sysIP, sysPort) = (ip, port+str(1)) |
---|
1164 | self.AddNewMachine(name, ip, port, sysIP, sysPort, machineId, alive, displayData) |
---|
1165 | |
---|
1166 | # update the ui |
---|
1167 | if 30000 in self.uiCallback: |
---|
1168 | wx.CallAfter(self.uiCallback[30000]) |
---|
1169 | |
---|
1170 | |
---|
1171 | # updates the data structure when the new machine status comes in |
---|
1172 | def OnUsersStatus(self, data): |
---|
1173 | users = string.split(data, SEPARATOR) #split it into individual client's status |
---|
1174 | self.ClearUsersStatus() #recreate the hash from scratch |
---|
1175 | for user in users: |
---|
1176 | if user == "": |
---|
1177 | break |
---|
1178 | userData = string.split(user, "\n",2) |
---|
1179 | username = userData[0] |
---|
1180 | info = userData[1] |
---|
1181 | if len(userData) > 2: |
---|
1182 | machineList = string.split(userData[2], "\n") #split the machines into a list |
---|
1183 | else: |
---|
1184 | machineList = [] |
---|
1185 | self.AddNewUser(username, info, machineList) #store info about all the users in a hash |
---|
1186 | |
---|
1187 | # update the ui |
---|
1188 | if 30001 in self.uiCallback: |
---|
1189 | self.uiCallback[30001]() |
---|
1190 | |
---|
1191 | |
---|
1192 | # actually just relays the message to the appropriate UI component |
---|
1193 | def OnChatMessage(self, data): |
---|
1194 | # update the ui |
---|
1195 | if 30002 in self.uiCallback: |
---|
1196 | self.uiCallback[30002](data) |
---|
1197 | |
---|
1198 | |
---|
1199 | ##### MY DATA --------------------------------------------------------- |
---|
1200 | |
---|
1201 | def GetMyInfo(self): |
---|
1202 | return self._info |
---|
1203 | |
---|
1204 | def SetMyInfo(self, newInfo): |
---|
1205 | self._info = newInfo |
---|
1206 | |
---|
1207 | def GetMyUsername(self): |
---|
1208 | return self._username |
---|
1209 | |
---|
1210 | def SetMyUsername(self, username): |
---|
1211 | self._username = username |
---|
1212 | |
---|
1213 | def GetMyConnections(self): |
---|
1214 | return self.myconnections |
---|
1215 | |
---|
1216 | def AddMyConnection(self, machineId): |
---|
1217 | self.myconnections.append(machineId) |
---|
1218 | |
---|
1219 | def RemoveMyConnection(self, machineId): |
---|
1220 | if machineId in self.GetMyConnections(): |
---|
1221 | self.myconnections.remove(machineId) |
---|
1222 | |
---|
1223 | def AmIConnectedTo(self, machineId): |
---|
1224 | return machineId in self.GetMyConnections() |
---|
1225 | |
---|
1226 | |
---|
1227 | ##### MACHINES ---------------------------------------------------------- |
---|
1228 | |
---|
1229 | def HasMachine(self, machineId): |
---|
1230 | return self.GetMachinesStatus().has_key(machineId) #True of False |
---|
1231 | |
---|
1232 | def FindMachineByIP(self, ip): |
---|
1233 | for machine in self.machinesStatusHash.itervalues(): |
---|
1234 | if machine.HasIP(ip): |
---|
1235 | return machine |
---|
1236 | return False |
---|
1237 | |
---|
1238 | def GetMachine(self, machineId): |
---|
1239 | return self.GetMachinesStatus()[machineId] #returns SAGEMachine |
---|
1240 | |
---|
1241 | def GetMachineNames(self): |
---|
1242 | tempList = [] |
---|
1243 | for sageMachine in self.GetMachinesStatus().itervalues(): |
---|
1244 | tempList.append(sageMachine.GetName()) |
---|
1245 | return tempList |
---|
1246 | |
---|
1247 | def GetMachinesStatus(self): #returns SAGEMachines hash |
---|
1248 | return self.machinesStatusHash |
---|
1249 | |
---|
1250 | def AddNewMachine(self, name, ip, port, sysIP, sysPort, machineId, alive, displayInfo=[]): |
---|
1251 | self.machinesStatusHash[ machineId ] = SAGEMachine(name, ip, port, sysIP, sysPort, machineId, alive, displayInfo) |
---|
1252 | |
---|
1253 | def AddNewSAGEMachine(self, newMachine): |
---|
1254 | self.machinesStatusHash[ newMachine.GetId() ] = newMachine |
---|
1255 | |
---|
1256 | def RemoveMachine(self, machine): |
---|
1257 | if machine.GetId() in self.machinesStatusHash: |
---|
1258 | del self.machinesStatusHash[ machine.GetId() ] |
---|
1259 | |
---|
1260 | def ClearMachinesStatus(self): |
---|
1261 | del self.machinesStatusHash |
---|
1262 | self.machinesStatusHash = {} |
---|
1263 | |
---|
1264 | |
---|
1265 | ##### USERS ------------------------------------------------------------- |
---|
1266 | |
---|
1267 | def HasUsername(self, username): |
---|
1268 | return self.GetUsersStatus().has_key(username) |
---|
1269 | |
---|
1270 | def GetUser(self, username): |
---|
1271 | return self.GetUsersStatus()[username] |
---|
1272 | |
---|
1273 | def GetUsernames(self, machine="all"): #if machine is specified, it returns a list of all the users connected to that machine |
---|
1274 | if machine == "all": |
---|
1275 | return self.GetUsersStatus().keys() |
---|
1276 | else: |
---|
1277 | tempList = [] |
---|
1278 | # loop through all the users and see if the machine they are connected to matches |
---|
1279 | # the machine we passed in here |
---|
1280 | for username, sageUser in self.GetUsersStatus().iteritems(): |
---|
1281 | if machine in sageUser.GetMachines(): |
---|
1282 | tempList.append(username) |
---|
1283 | return tempList |
---|
1284 | |
---|
1285 | def GetUsersStatus(self): #returns SAGEUsers |
---|
1286 | return self.usersStatusHash |
---|
1287 | |
---|
1288 | def AddNewUser(self, username, info, machineList=[]): |
---|
1289 | self.usersStatusHash[ username ] = SAGEUser(username, info, machineList) |
---|
1290 | |
---|
1291 | def ClearUsersStatus(self): |
---|
1292 | del self.usersStatusHash |
---|
1293 | self.usersStatusHash = {} |
---|
1294 | |
---|
1295 | |
---|
1296 | |
---|
1297 | |
---|
1298 | |
---|
1299 | |
---|
1300 | # holds info about every connected user |
---|
1301 | class SAGEUser: |
---|
1302 | |
---|
1303 | def __init__(self, username, info, machineList=[]): |
---|
1304 | self._username = username |
---|
1305 | self._info = info |
---|
1306 | self._machines = machineList[:] #the list of machines this user is connected to |
---|
1307 | |
---|
1308 | |
---|
1309 | def GetName(self): |
---|
1310 | return self._username |
---|
1311 | |
---|
1312 | def SetName(self, name): |
---|
1313 | self._username = name |
---|
1314 | |
---|
1315 | def GetInfo(self): |
---|
1316 | return self._info |
---|
1317 | |
---|
1318 | def SetInfo(self, info): |
---|
1319 | self._info = info |
---|
1320 | |
---|
1321 | def GetMachines(self): |
---|
1322 | return self._machines |
---|
1323 | |
---|
1324 | |
---|
1325 | |
---|
1326 | # holds info about every currently running SAGE machine |
---|
1327 | class SAGEMachine: |
---|
1328 | |
---|
1329 | def __init__(self, name, ip, port, sysIP, sysPort, machineId, alive, displayInfo=[] ): |
---|
1330 | self.name = name |
---|
1331 | self.ip = ip |
---|
1332 | self.port = int(port) |
---|
1333 | self.sysIP = sysIP |
---|
1334 | self.sysPort = sysPort |
---|
1335 | self.machineId = machineId |
---|
1336 | self.alive = alive |
---|
1337 | if not len(displayInfo) == 6: |
---|
1338 | displayInfo = ["?","?","?","?","?","?"] |
---|
1339 | |
---|
1340 | self.xTiles = displayInfo[0] |
---|
1341 | self.yTiles = displayInfo[1] |
---|
1342 | self.displayWidth = displayInfo[2] |
---|
1343 | self.displayHeight = displayInfo[3] |
---|
1344 | self.tileWidth = displayInfo[4] |
---|
1345 | self.tileHeight = displayInfo[5] |
---|
1346 | |
---|
1347 | def GetName(self): |
---|
1348 | return self.name |
---|
1349 | |
---|
1350 | def GetIP(self): |
---|
1351 | return self.ip |
---|
1352 | |
---|
1353 | def GetPort(self): |
---|
1354 | return self.port |
---|
1355 | |
---|
1356 | def GetSystemIP(self): |
---|
1357 | return self.sysIP |
---|
1358 | |
---|
1359 | def GetSystemPort(self): |
---|
1360 | return self.sysPort |
---|
1361 | |
---|
1362 | def GetId(self): |
---|
1363 | return self.machineId |
---|
1364 | |
---|
1365 | def IsAlive(self): |
---|
1366 | return self.alive |
---|
1367 | |
---|
1368 | def GetNumTiles(self): |
---|
1369 | return (self.xTiles, self.yTiles) #returns a tuple of (x*y) number of tiles |
---|
1370 | |
---|
1371 | def GetDisplaySize(self): |
---|
1372 | return (self.displayWidth, self.displayHeight) |
---|
1373 | |
---|
1374 | def GetTileSize(self): |
---|
1375 | return (self.tileWidth, self.tileHeight) |
---|
1376 | |
---|
1377 | def HasIP(self, ip): |
---|
1378 | return self.sysIP==ip or self.ip==ip |
---|