source: trunk/src/testing/ui/users.py @ 4

Revision 4, 53.9 KB checked in by ajaworski, 13 years ago (diff)

Added modified SAGE sources

Line 
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
40from threading import Thread, RLock
41import time, socket, string, os.path, sys, wx
42
43from globals import *
44from Mywx import MyButton
45from sageui import AboutDialog
46import preferences as prefs
47
48# some globals for this module
49CHUNK_SIZE = 2048 #4096
50SOCKET_TIMEOUT = 1
51MSGLEN = CHUNK_SIZE
52SEPARATOR = '\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
144class 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
313class 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
618class 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
735class 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
913class 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
1119class 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
1301class 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
1327class 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
Note: See TracBrowser for help on using the repository browser.