import repository from arizona
[raven.git] / apps / gui1 / storkslicemanager.py
1 #!/usr/bin/env python
2
3 #           [option, long option,                    variable,            action,        data,     default,                            metavar,    description]
4 """arizonaconfig
5    options=[   
6             ["",   "--managerconf",                 "managerconf",        "store",       "string", "~/.storkmanager.conf",                  "FILE",      "use a different config file (~/.storkmanager.conf is the default)"]
7    ]
8    includes=[]        
9 """
10
11
12 from Tkinter import *
13 from tkMessageBox import *
14 from Tkinter import _setit
15 import tkFileDialog
16 import tkFont
17 import os
18 import sys
19 import re
20 import webbrowser
21
22 import storkcurlfuncs as sc
23 import arizonaconfig
24 import storkpackage
25 import storkutil
26
27
28
29 #the version of the gui, this number should be incremented
30 #whenever there is a change to this file, or a change to 
31 guiversion = "$Revision: 1.30 $"
32
33 checkedforupdate = False
34
35 debug = False
36
37 groupframes = {} 
38 groups = []
39 nodes  = {} #a dict of groupname->array of nodes          
40 actions= {}
41 to_upload=[]
42
43 next_group_row = 0
44 switch_user = False
45
46 # whether the current state is in synch with the repository
47 synched = True
48
49 root = None
50
51 topoptions = None
52 username = None
53 password = None
54 wheretologin = None
55 storkusername = "jplichta"
56 if not debug: storkusername = None
57
58 # Flags that will always be passed to storkutil to identify this user. 
59 # This gets set later, don't add extra flags to this here.
60 storkutiluserflags = None
61
62 # The directory the script is located in.
63 scriptpath = os.path.realpath(os.path.dirname(sys.argv[0]))
64
65 # Path to images used by the GUI.
66 imagepath = os.path.join(scriptpath, "images")
67
68 # Path to where all non-temp files that are read and created.
69 # Note: not fully implemented yet. Leave empty to have it be ignored.
70 # The idea would be to make setting/changing the working directory
71 # available through the GUI, but this works for testing.
72 # Example:
73 #workingpath = "/home/myusername/somedirectory";
74 workingpath = ""
75 commandstub = ""
76
77 # Change working directory so files are read from and created there.
78 # This would be done somewhere else once one can set this from the GUI.
79 if workingpath:
80     print "Changing working directory to: "+workingpath
81     os.chdir(workingpath)
82
83
84 # Change the commands for different OS
85 #HOME is for Linux. Command should be '../storkutil.py' For windows, 
86 #it is simply 'storkutil.py'
87 if 'HOME' in os.environ:
88     commandstub = '/'
89
90
91
92 def isPackage(path):
93    # lets try not to tar or rpm in case they are on windows,
94    # so we will do 'dummy' checking by filename
95    base = os.path.basename(path)
96    for foo in ["tar", "tgz", "tbz","rpm"]:
97       if foo in base: return True
98
99    return False
100
101 def trustPackage(path):
102    if not os.path.isfile(path): return
103
104    # Question: should a key really be generated here if there isn't one?
105    # see if we need to make the user a key
106    #if not os.path.exists(storkusername+".publickey"):
107       #makeKey(storkusername)
108
109    command = scriptpath+commandstub+'storkutil.py '+storkutiluserflags+' addfile '+path
110    print "debug: about to run ",command
111    os.system(command)
112
113 def removeTrust(file):
114    # see if we need to make the user a key
115    if not os.path.exists(storkusername+".publickey"):
116       makeKey(storkusername)
117       
118    command = scriptpath+commandstub+'storkutil.py '+storkutiluserflags+' removefile '+file
119    print "debug: about to run ",command
120    os.system(command)
121
122
123 def uploadPackage(path):
124    metahash = storkpackage.get_package_metadata_hash(path)
125    if not sc.url_exists("metadata/stork-repository.cs.arizona.edu_packages_PlanetLab_V3_Testing/"+str(metahash) ):
126       # sense it does not already seem to be on the repository, upload it
127       sc.upload_file(username, password, path, "package", None, wheretologin)
128    else:
129       print "debug: "+path+" exists on server, not uploading"  
130
131
132
133 def makeKey(user):
134    command = scriptpath+commandstub+'storkutil.py --dontask --username='+user+' genpair '+user
135    print "debug: about to run: ",command
136    os.system(command)
137    # If statusframe is defined, then the main window is open and we want to
138    # change the syched state. Otherwise, we just want to flag that when the
139    # main window opens it should not show as synched.
140    global statusframe
141    try:
142        statusframe
143    except NameError:
144        global synched
145        synched = False
146    else:
147        statusframe.set_in_synch(False)
148    
149
150 def getPubKey():
151       keyFilePath = storkusername+".publickey"
152       print "DEBUG: getPubKey: "+keyFilePath
153       if not os.path.isfile(keyFilePath):
154           return None
155       newFile = open(storkusername+".publickey","r")
156       newFile.readline()
157       storkpubkey = newFile.readline()+newFile.readline()
158       return storkpubkey.replace("\n","").replace("/","_").rstrip("=")
159
160 def getGroups():
161    storkpubkey = getPubKey()
162    grps = []
163    grps.append("All")
164    groupsfilepath = storkusername+"."+storkpubkey+".groups.pacman"
165    if not os.path.isfile(groupsfilepath):
166         print groupsfilepath
167         print "No groups file found. No groups will be added." 
168    else:
169         groupFile = open(groupsfilepath)
170         for line in groupFile:
171            if line.find("GROUP NAME") != -1:
172                groupname = line[16:-6]
173                grps.append(groupname)
174    # get empty groups that have something set for them in the packages file
175    packagesfilepath = storkusername+"."+storkpubkey+".packages.pacman"
176    if os.path.isfile(packagesfilepath):
177        packagesFile = open(packagesfilepath)
178        regex = re.compile(r'CONFIG GROUP="(.+?)"')
179        for line in packagesFile:
180            matches = regex.search(line)
181            if matches and matches.group(1) not in grps:
182                grps.append(matches.group(1))
183    return grps
184
185 def getNodes(group):
186    storkpubkey = getPubKey()  
187   
188    nodeList = []
189    groupsfilepath = storkusername+"."+storkpubkey+".groups.pacman"
190    if not os.path.isfile(groupsfilepath):
191         return nodeList
192    else:
193         groupFile = open(groupsfilepath)
194         
195         while(True): #Loops through each line in the groups file
196                 outerLine = groupFile.readline()
197                 if outerLine.find("\""+group+"\"") != -1:
198                         while(True): #Loops through every line for this group
199                                 innerLine = groupFile.readline()
200                                 if innerLine.find("GROUP") == -1:
201                                         if innerLine.find("INCLUDE") != -1:
202                                                 print "about to append: "+innerLine[18:-7]+" to "+group
203                                                 nodeList.append(innerLine[18:-7])
204                                 else:
205                                         break #Stop looping if we are at the end of the group
206                         break #Stop looping through the file if we find the group
207
208                 if outerLine == "": #Stop looping if we are at the end of the file
209                         break
210         return nodeList
211
212 def getSlices():
213    if debug: return ["arizona_client1"]
214
215    slices = sc.getslices(username,password,wheretologin)
216    if slices == None:
217       return ["Could not fetch slices"]
218    else:
219       return slices
220
221 # This doesn't appear to be used anywhere. trustPackage() is used for this.
222 def addPackage(package):
223    os.system(scriptpath+'/storkutil.py '+storkutiluserflags+' addfile '+package)
224
225 def getActions(group):
226    #Provides tuples of the form (action, package) for the provided group name. actions will be the returned
227    #list of tuples, curAct is the current action that needs to be tuplified, preLen is used to parse the 
228    #package name from the line
229    storkpubkey = getPubKey()
230    actions = []
231    curAct = ""
232    packagesfilepath = storkusername+"."+storkpubkey+".packages.pacman"
233    if not os.path.isfile(packagesfilepath):
234         return actions
235    else:
236         try:
237            actionsFile = open(packagesfilepath, "r")
238         except:
239            return actions
240                 
241         while(True): #Loop through each line of the packages file
242                 outerLine = actionsFile.readline()
243                 
244                 if group == "All":
245                         if outerLine.find("CONFIG&gt") != -1:
246                                 while(True):
247                                         innerLine = actionsFile.readline()
248                                         if innerLine.find("/CONFIG") == -1:
249                                                 if innerLine.find("PACKAGE") != -1:
250                                                         if innerLine.find("INSTALL") != -1:
251                                                                 curAct = "install"
252                                                                 preLen = 21
253                                                         elif innerLine.find("UPDATE") != -1:
254                                                                 curAct = "update"
255                                                                 preLen = 20
256                                                         elif innerLine.find("REMOVE") != -1:
257                                                                 curAct = "remove"
258                                                                 preLen = 20
259                                                         curPack = innerLine[preLen:-7]
260                                                         actions.append( (curPack, curAct) )
261                                         else:
262                                                 break #If we looped through the whole group, stop
263                                 break #If we find the group, stop looping through the file
264                         if outerLine == "":
265                                 break                                           
266
267                 else: #The case where the group IS NOT "All"
268                         if outerLine.find("CONFIG GROUP=\""+group+"\"") != -1:
269                                 while(True):
270                                         innerLine = actionsFile.readline()
271                                         if innerLine.find("/CONFIG") == -1:
272                                                 if innerLine.find("PACKAGE") != -1:
273                                                         if innerLine.find("INSTALL") != -1:
274                                                                 curAct = "install"
275                                                                 preLen = 21
276                                                         elif innerLine.find("UPDATE") != -1:
277                                                                 curAct = "update"
278                                                                 preLen = 20
279                                                         elif innerLine.find("REMOVE") != -1:
280                                                                 curAct = "remove"
281                                                                 preLen = 20
282                                                         curPack = innerLine[preLen:-7]
283                                                         actions.append( (curPack, curAct) )
284                                         else:
285                                                 break #If we looped through the whole group, stop
286                                 break #If we find the group, stop looping through the file
287                         if outerLine == "":
288                                 break
289         actionsFile.close()
290         return actions
291
292 def createFiles():
293         #<Purpose>
294         #Creates pacman files, tpfile, config file and list of packages for upload.
295
296         #<Arguments>   
297         #None
298
299         #<Exceptions>
300         #None
301
302         #<Side Effects>
303         #Deletes and rebuilds the aforementioned files
304
305         #<Returns>
306         #None
307
308         #Note: This function is currently done by simply invoking storkutil.py with the appropriate command
309         #line commands. This can be cleaned up by replacing the "if args=..." statements in storkutil with 
310         #functions. If this is done, this section will need to be modified.
311
312     pubkey = storkusername +".publickey"
313     prvkey = storkusername +".privatekey"
314     if not os.path.isfile(prvkey):
315        #file does not exist, we need to create a key for them
316            makeKey(storkusername)
317  
318     storkpubkey = getPubKey()           
319         
320     #Create pacgroups file. Make sure to add in 'python' when cvsing
321     if os.path.isfile(storkusername+"."+storkpubkey+".groups.pacman"):
322         os.remove(storkusername+'.'+storkpubkey+'.groups.pacman')
323     for gp in nodes:
324         nodelist = ''
325         for nd in nodes[gp]:
326                 nodelist += nd+' '
327         if nodelist != '':
328                 os.system(scriptpath+'/storkutil.py '+storkutiluserflags+' pacgroups include '+"'"+gp+"'"+' '+nodelist)
329     
330     #Create pacpackages file
331     if os.path.isfile(storkusername+"."+storkpubkey+".packages.pacman"):        
332         os.remove(storkusername+'.'+storkpubkey+'.packages.pacman')
333     for gp in actions:
334         for act in actions[gp]:
335             if(gp == 'All'):
336                 os.system(scriptpath+'/storkutil.py '+storkutiluserflags+' pacpackages all '+act[1]+' '+act[0])
337             else:
338                 os.system(scriptpath+'/storkutil.py '+storkutiluserflags+' pacpackages group '+"'"+gp+"'"+' '+act[1]+' '+act[0])
339                 
340         #Create a tpfile if one doesn't exist
341     if not os.path.isfile(storkusername+"."+getPubKey()+".tpfile"):
342         storkutil.makeTPFile(storkusername, False)
343         storkutil.signTPFile(storkusername+'.tpfile', storkusername+'.privatekey')
344         storkutil.pubKeyEmbed(storkusername+'.tpfile', storkusername+'.publickey')
345         os.remove(storkusername+'.tpfile')
346     
347     # download and alter configuration file
348     if not debug and not sc.fetch_configuration(topoptions.get_slice()):
349        print "Could not fetch configuration file for "+topoptions.get_slice()+" from the repository."
350        print "       This slice will use a copy of the default configuration file."
351        # use default conf file if no conf file can be found for this slice
352        if not sc.fetch_configuration(topoptions.get_slice(), defaultconf=True):
353            print "ERROR: Could not fetch default configuration file."
354   
355     # change the configuration file
356     if not sc.alter_configuration(topoptions.get_slice()+".stork.conf", storkusername, pubkey):
357        #if we are about to change some values then
358        #warn the user here with a pop-up, and ask them wether to try again, this time overwriting
359        #any values
360
361        (susername, pk) = sc.parse_config( topoptions.get_slice()+".stork.conf" )
362        if susername == None: susername = "unknown"
363        if pk       == None: pk       = "unknown"
364        
365        # the key is only being compared in name
366        if susername == 'default' and pk == 'default.publickey':
367            changeidentity = True
368        elif storkusername != susername:
369            changeidentity = askyesno('Verify', "Slice '"+topoptions.get_slice()+"' \nis currently managed by \nuser '"+susername+"'.\n\nDo you want to change it to your current user '"+storkusername+"'?")
370        else:
371            changeidentity = askyesno('Verify', "Slice '"+topoptions.get_slice()+"' \ncurrently uses public key '"+pk+"'.\n\nDo you want to change it to your current public key '"+pubkey+"'?") 
372        if changeidentity:
373           if not sc.alter_configuration(topoptions.get_slice()+".stork.conf", storkusername, pubkey, True):
374               print "ERROR: there was an error changing the configuration file"
375
376     # clean up and make any other required changes to the configuration file
377     if not sc.clean_configuration(topoptions.get_slice()+".stork.conf"):
378         print "ERROR: there was an error cleaning the configuration file"
379         
380     # upload files
381     if not debug:
382        showwarning("About to upload new files", "About to upload new files to the repository. This may take a while. During the upload, the GUI will be unresponsive.")
383        sc.upload_file(username, password, storkusername+'.'+getPubKey()+'.groups.pacman', "pacman",None,wheretologin)
384        sc.upload_file(username, password, storkusername+'.'+getPubKey()+'.packages.pacman', "pacman",None,wheretologin)
385        sc.upload_file(username, password, storkusername+"."+getPubKey()+".tpfile", "tp",None,wheretologin)
386        sc.upload_file(username, password, pubkey, "pk", topoptions.get_slice(), wheretologin)
387        sc.upload_file(username, password, topoptions.get_slice()+".stork.conf", "conf", topoptions.get_slice() ,wheretologin)
388
389        # upload any packages we need to
390        if len(to_upload) > 0:
391            short = []
392            for foo in to_upload: 
393               short.append( os.path.basename(foo) )
394            for foo in to_upload:
395               uploadPackage(foo)
396
397        # call the auto update page with a list of all the possible nodes to add to this slice
398        nodestoadd = []
399        for group in groups:
400           for node in nodes[group]:
401              if node not in nodestoadd:
402                 nodestoadd.append(node)
403
404        sc.autoSetup(username,password,topoptions.get_slice(),nodestoadd, wheretologin); 
405
406
407 #HARDCODED initial group values
408 #groups.append("Alpha Group")
409 #groups.append("Beta Group")
410
411 #nodes["Alpha Group"] = ["planetlab-1.cs.princeton.edu", "planetlab-2.cs.princeton.edu", "planetlab-3.cs.princeton.edu"]
412 #nodes["Beta Group"] = ["planetlab-4.cs.princeton.edu", "planetlab-5.cs.princeton.edu"]
413 nodes["All"] = []
414 #actions["Alpha Group"] = [("foo","install"), ("bar","remove")]
415 #actions["Beta Group"]  = []
416 actions["All"] = []
417
418
419
420
421 class StatusFrame(Frame):
422    def __init__(self, parent=None):
423       Frame.__init__(self, parent)
424       self.parent = parent
425
426       self.goodimage = PhotoImage(file=os.path.join(imagepath, "in-synch-1.gif"))   
427       #self.badimage  = PhotoImage(file=os.path.join(imagepath, "out-of-synch-1.gif"))   
428       self.upload    = PhotoImage(file=os.path.join(imagepath, "upload-1.gif"))   
429
430       self.helv36 = tkFont.Font ( family="Helvetica",\
431         size=14, weight="bold" )
432
433       self.helv10 = tkFont.Font ( family="Helvetica",\
434         size=10 )
435
436       self.helv9 = tkFont.Font ( family="Helvetica",\
437         size=9, )
438
439       self.linktext = tkFont.Font ( family="Helvetica",\
440         size=9, underline=1, weight=tkFont.BOLD)
441
442       self.statusimage = Label(self, image=self.goodimage)
443
444       self.statustext  = Label(self, text="In synch with repository.")
445       self.uploadtext  = Label(self, text="Files added or changed.\nSynch with repository.", fg="blue")
446       self.uploadimage = Button(self, image=self.upload, command=lambda: self.set_in_synch(True) )
447
448       #self.statusimage.bind("<Button-1>", lambda event: self.set_in_synch(False) )
449       self.uploadtext.bind("<Button-1>", lambda event: self.set_in_synch(True))
450       
451       global synched
452       if synched:
453           self.statustext.grid(row=1, column=0, sticky=E)
454           self.statusimage.grid(row=1, column=1, sticky=E)
455       else:
456          self.uploadtext.grid(row=0, column=0, sticky=E)
457          self.uploadimage.grid(row=0, column=1, sticky=E) 
458
459    def set_in_synch(self, b):
460       global synched
461       if b:
462          # if already synched, let user know no synch necessary
463          if synched == True:
464             showwarning("No synch necessary", "You are already synched with the repository.")
465          else:
466             if not topoptions.get_slice():
467                showwarning("Select slice", "You must select a slice before synchronizing with the repository.")
468                return
469             createFiles()
470             self.uploadtext.grid_forget()
471             self.uploadimage.grid_forget()
472             self.statustext.grid(row=0, column=0, sticky=E)
473             self.statusimage.grid(row=0, column=1, sticky=E)
474       else:
475          self.statustext.grid_forget()
476          self.statusimage.grid_forget()
477          self.uploadtext.grid(row=0, column=0, sticky=E)
478          self.uploadimage.grid(row=0, column=1, sticky=E)
479       synched = b
480      
481
482 class TopOptions(Frame):
483    def __init__(self, parent=None, root=None):
484       Frame.__init__(self, parent)
485       self.root = root
486       self.helv36 = tkFont.Font ( family="Helvetica",\
487         size=14, weight="bold" )
488
489       #self.userimage = PhotoImage(file=os.path.join(imagepath, "user-1.gif"))
490
491       managingslice_label = Label(self, text="is managing") 
492       self.username       = Label(self, text="jplichta",font=self.helv36)
493       if storkusername != None and storkusername != "":
494          self.username.config(text=storkusername)
495
496       self.username.bind("Button-1", lambda event: self.quit() )
497
498       #self.changeframe = Frame(self)
499       #self.changetext  = Label(self.changeframe, text="Switch User")
500       #self.changeimage = Button(self.changeframe, image=self.userimage, command=self.switchuser)
501
502       self.slicevar       = StringVar()
503       self.slicemenu      = OptionMenu(self, self.slicevar, None)
504       self.slicemenu["menu"].config(font=self.helv36)
505       self.slicemenu["menu"].delete(0, END)
506       
507       slices = getSlices()
508       #if len(slices)>0:
509       #   self.slicevar.set(slices[0])
510       self.selectsliceprompttext = "[select slice]"
511       self.slicevar.set(self.selectsliceprompttext)
512
513       def set_active_slice(self):
514          global statusframe
515          statusframe.set_in_synch(False)
516     
517       for foo in slices:
518          self.slicemenu["menu"].insert('end','command', label=foo, command=_setit(self.slicevar, foo, set_active_slice)) 
519       
520       self.username.grid(row=0, column=0, sticky=W)
521       managingslice_label.grid(row=0, column=1, sticky=E)
522       self.slicemenu.grid(row=0, column=2, sticky=W)
523       #self.changetext.grid(row=0, column=0,sticky=W)
524       #self.changeimage.grid(row=0, column=1) 
525       #self.changeframe.grid(row=1, column=2, sticky=E)
526
527    
528    def get_slice(self):
529       if self.slicevar.get() == self.selectsliceprompttext:
530           return None
531       else:
532           return self.slicevar.get()
533
534    def switchuser(self):
535       #clear all state and reset the application 
536       global statusframe
537       statusframe.set_in_synch(False)
538       #TODO -actually clear the state out
539       global switch_user
540       switch_user = True
541       print "about to switch user, switch_user=",switch_user
542       close_window_callback()
543
544 class TopBlock(Frame):
545    def __init__(self, parent=None):
546       Frame.__init__(self, parent)
547       self.parent=parent
548       self.linktext = tkFont.Font ( family="Helvetica",\
549         size=9, underline=1, weight=tkFont.BOLD)
550       self.helv10 = tkFont.Font ( family="Helvetica",\
551         size=10, weight="bold" )
552
553       self.var = StringVar()
554
555       #addnew   = Label(self, text="Add/Remove group: ")
556       self.newfield = Entry(self, textvariable=self.var)
557       self.add_group= Button(self, text="Ok", font=self.helv10, command=self.add_group )
558       self.cancel   = Button(self, text="Cancel", font=self.helv10, command=self.hide_addfields)
559       self.newfield.bind("<Return>", lambda event: self.add_group.invoke() )
560
561       self.addgroup_label = Label(self, text="add group", font=self.linktext, fg="blue", cursor="hand2")
562       self.addgroup_label.bind("<Button-1>", lambda event: self.show_addfields() )
563       self.addgroup_label.grid(column=0, row=0, sticky=NW) 
564
565    def hide_addfields(self):
566       self.addgroup_label.grid(column=0, row=0, sticky=NW)
567       self.newfield.grid_forget()
568       self.add_group.grid_forget()
569       self.cancel.grid_forget()
570
571    def show_addfields(self):
572       self.addgroup_label.grid_forget()    
573       self.newfield.grid(column=0, row=0, sticky=W)
574       self.add_group.grid(column=1, row=0, sticky=W)
575       self.cancel.grid(column=2, row=0, sticky=W)
576       self.var.set("")
577       self.newfield.focus()
578
579    def add_group(self) :
580       group = self.newfield.get().strip()
581       if len(group) == 0: return
582       if re.search(r"[^a-zA-Z0-9_-]", group):
583           showwarning("Invalid group name", "Group names can only contain the characters a-z, A-Z, 0-9, underscores, and hyphens.")
584           return
585
586       if group not in groups:
587          groups.append(group)
588          nodes[group] = []
589          actions[group] = []
590          #create a new group
591          newgroup = Group(self.parent, group, self)
592          newgroup.grid(pady=3,columnspan=2, sticky=W)
593          groupframes[group]=newgroup
594          self.set_group(group)
595          self.grid_forget()
596          self.grid(column=0,pady=10, sticky=NW)
597          global statusframe
598          statusframe.set_in_synch(False)
599          
600       self.hide_addfields()
601
602    def remove_group(self):
603       if self.var.get() != None and self.var.get() != "":
604          # make sure the thing we are trying to move actually exists
605          toremove = self.var.get()
606          if toremove not in groups: return
607
608          # remove it
609          groupframes[toremove].destroy()
610          groups.remove(toremove)
611          nodes[toremove] = []
612
613          if len(groups) > 0:
614             self.set_group( groups[-1] )
615          else:
616             self.set_group("")
617             
618          global statusframe
619          statusframe.set_in_synch(False)
620
621    def set_group(self, groupname):
622       if groupname != None:
623          self.var.set(groupname)
624
625
626 class Group(Frame):
627     
628    nodelist = [] 
629    actions  = []
630    removenow= (None, None) # the event handler for the node remove button will store its parameters in here
631    tempfile = None
632
633    def __init__(self, parent=None, groupname=None, top=None ):
634       Frame.__init__(self, parent)
635       self.collapsed=1
636       self.top = top
637       self.group_name = groupname
638       self.next_action_row = 0
639       self.config(bd=2, relief=RIDGE)
640       
641       self.ximage = PhotoImage(file=os.path.join(imagepath, "xbutton-1.gif"))   
642       #self.addimage=PhotoImage(file=os.path.join(imagepath, "addbutton-1.gif"))
643
644       self.helv36 = tkFont.Font ( family="Helvetica",\
645         size=14, weight="bold" )
646
647       self.helv10 = tkFont.Font ( family="Helvetica",\
648         size=10 )
649
650       self.helv9 = tkFont.Font ( family="Helvetica",\
651         size=9, )
652
653       self.linktext = tkFont.Font ( family="Helvetica",\
654         size=9, underline=1, weight=tkFont.BOLD)
655
656
657       nodelist = nodes[groupname]
658       if nodelist != None:
659          numnodes = str( len(nodelist) )
660       else:
661          nodelist = []
662          numnodes = "0"
663
664       actionlist = actions[groupname]
665       print "actionlist: ",actionlist
666       if actionlist == None:
667          actionlist = []
668       
669
670       if groupname == None:
671          groupname = "None"
672
673       group_label_frame = Frame(self)
674       inner_group_label_frame = Frame(group_label_frame)
675       group_label_frame.grid(row=0, column=0, rowspan=2,  sticky=NW)
676
677      
678       remove_group = Button(inner_group_label_frame, image=self.ximage,borderwidth=0, command=self.remove_group)
679       self.group= Label(inner_group_label_frame, text=groupname+" ("+numnodes+")", width=30, anchor=W, font=self.helv36)
680       if groupname == "All":
681          self.group.config(text=groupname+" nodes")
682
683       if groupname != "All":
684          remove_group.grid(column=0, row=0, sticky=W)
685       
686       self.group.grid(column=1, row=0, sticky=W )
687
688       inner_group_label_frame.grid(row=0, column=0, sticky=W)
689
690       self.expandgroup = Label(group_label_frame, text="expand", font=self.linktext, fg="blue", cursor="hand2")
691       if groupname != "All":
692          self.expandgroup.grid(column=0, row=1, sticky=NW)
693          self.expandgroup.bind('<Button-1>', self.expand_group) 
694
695       #self.sbar = Scrollbar(self)
696       #self.lbox = Listbox(self, height=5, width=40, relief=SUNKEN)
697       self.lbox = Frame(group_label_frame, width=40, relief=GROOVE)
698
699       #construct the box for adding new nodes
700       self.addbox = Frame(group_label_frame, bg="#7AFFA4")
701       self.add_label    = Label(self.addbox, text="Add:", font=self.helv10, bg="#7AFFA4")
702       self.addmethodvar = StringVar()
703       self.addmethodvar.set("single node")
704       self.optionmenu = OptionMenu(self.addbox, self.addmethodvar, "single node", "CoMon query", "set operation")
705
706       def onchange(name, index, mode):
707          if self.addmethodvar.get() == "single node":
708             self.use_singlenode()
709          elif self.addmethodvar.get() == "CoMon query":
710             self.use_comon()
711          elif self.addmethodvar.get() == "set operation":
712             self.use_set()
713
714       remember = self.addmethodvar.trace_variable('w', onchange)
715       #self.optionmenu["menu"].config(command=onchange)
716
717       self.singlenode_ex = Label(self.addbox, justify=LEFT,width=35,height=2, text="Type in the hostname of a node in \nthe box, ex: planetlab-1.cs.princeton.edu", font=self.helv9, bg="#7AFFA4")
718       #self.comon_ex      = Label(self.addbox, justify=LEFT,width=50,height=3, text="Type in a well formed CoMon query to return a set of nodes. \nWARNING: the nodes returned from the query will \nreplace any nodes that are currently in this group.", font=self.helv9, bg="#7AFFA4")
719       #self.set_ex        = Label(self.addbox, justify=LEFT,width=25,height=2, text="Union or Intersect this group \nwith another existing group.", font=self.helv9, bg="#7AFFA4")
720       self.comon_ex      = Label(self.addbox, justify=LEFT,width=50,height=3, text="Comon support is currently not functional", font=self.helv9, bg="#666666")
721       self.set_ex        = Label(self.addbox, justify=LEFT,width=50,height=3, text="Set support is currently not functional", font=self.helv9, bg="#666666")
722
723
724       self.node_entry    = Entry(self.addbox, width=25 )
725       self.node_label    = Label(self.addbox, text="Node:", font=self.helv10, bg="#7AFFA4")
726
727       self.comon_query   = Text(self.addbox, height=4, width=30 , relief=SUNKEN)
728       self.comon_label   = Label(self.addbox, text="Query:", font=self.helv10, bg="#7AFFA4")
729
730       self.set_label     = Label(self.addbox, text="Operation:", font=self.helv10, bg="#7AFFA4")
731       self.set_option    = StringVar()
732       self.set_option.set("intersect")
733       self.set_menu      = OptionMenu(self.addbox, self.set_option, "intersect", "union")
734       self.set_with      = Label(self.addbox, text="With", font=self.helv10, bg="#7AFFA4")
735       self.set_groupvar   = StringVar()
736       self.set_grouplist  = OptionMenu(self.addbox, self.set_groupvar, None)
737       self.set_grouplist["menu"].delete(0, END)
738       self.add_label.grid(row=0, column=0, sticky=NE)
739       self.optionmenu.grid(row=0, column=1,sticky=NW)
740       self.singlenode_ex.grid(row=1, column=1,sticky=NW)
741       self.node_label.grid(row=2, column=0, sticky=NE)
742       self.node_entry.grid(row=2, column=1, sticky=NW)
743
744       self.node_buttons = Frame(self.addbox, bg="#7AFFA4")
745       self.node_ok        = Button(self.node_buttons, text="Ok", font=self.helv9,  command=self.add_node)
746       self.node_cancel    = Button(self.node_buttons, text="Cancel", font=self.helv9,  command=self.hide_addnodes)
747       self.node_entry.bind("<Return>", lambda event: self.node_ok.invoke() ) 
748
749
750       self.node_cancel.grid(row=0, column=0, sticky=NE)
751       self.node_ok.grid(row=0, column=1, sticky=NE)
752       self.node_buttons.grid(row=3, column=1, sticky=NE)
753       
754      
755       self.addnode_label = Label(group_label_frame, text="add node(s)", font=self.linktext, fg="blue", cursor="hand2")
756       self.addnode_label.bind("<Button-1>", lambda event: self.show_addnodes() )
757       if groupname != "All":
758          self.addnode_label.grid(row=3, column=0, sticky=NW)
759
760
761       self.actionframe = Frame(self)
762       self.action=Label(self.actionframe, text="Actions",width=38,anchor=W, font=self.helv36)
763       self.action.grid(column=0, row=0,columnspan=2,  padx=1, sticky=NW)
764
765
766       self.actionbox = Frame(self.actionframe, width=39, relief=SUNKEN)
767       self.actionbox.grid( column=0, row=1, rowspan=1, sticky=NW )
768
769       self.actionframe.grid(column=1, row=0, sticky=NW )
770       
771
772
773       
774
775
776       for i,foo in enumerate(nodelist):
777          n = Label(self.lbox,text=foo, font=self.helv9)
778          x = Button(self.lbox, image=self.ximage, borderwidth=0 )
779          x['command'] = lambda param=(x,n): self.remove_node(param)
780
781          self.nodelist.append(n)
782          x.grid(column=0, row=i, sticky=W, pady=0 ) 
783          n.grid(column=1, row=i, sticky=W, pady=0 ) 
784
785       for foo in actionlist:
786          n = Label(self.actionbox, text="    "+foo[0]+" ("+foo[1]+")", font=self.helv9, anchor=W)
787          x = Button(self.actionbox, image=self.ximage, borderwidth=0)
788          x["command"]= lambda param=(n,x,foo): self.remove_action(param)
789          self.actions.append(n)
790          x.grid(column=0, row=self.next_action_row, padx=0,  sticky=NW)
791          n.grid(column=1, row=self.next_action_row, padx=0,  sticky=NW)
792          self.next_action_row += 1
793
794       #create the addaction button
795       self.add_action_area = Frame(self.actionbox, bg="#7AFFA4", relief=SUNKEN)
796       self.add_action_label = Label( self.actionbox, text="add action", font=self.linktext, fg="blue", cursor="hand2")
797       self.add_action_label.bind("<Button-1>", lambda event: self.show_actionbuttons() )
798
799       self.action_explenation = Label( self.add_action_area,  text="Either type the name of a package in the text box\nor browse for a package on your filesystem.", font=self.helv9, fg="black", height=2, bg="#7AFFA4")
800
801       self.var = StringVar()
802       self.var.set("install")
803       self.optionmenu = OptionMenu(self.add_action_area, self.var, "install", "update", "remove")
804
805
806       self.action_var= StringVar()
807       self.add_action= Entry(self.add_action_area,textvariable=self.action_var, width=22, relief=SUNKEN)
808       self.add_action.bind("<Return>", lambda event: self.add_action_to_list() )
809       self.browse    = Button(self.add_action_area,text="Browse...", font=self.helv10, command=self.browse_for_file)
810
811       self.cancel    = Button(self.add_action_area,text="Cancel", font=self.helv10, command=lambda: self.hide_actionbuttons() )
812       self.ok        = Button(self.add_action_area,text="Ok", font=self.helv10,command=lambda: self.add_action_to_list() )
813       
814       self.action_explenation.grid(column=0, row=0,columnspan=2,  sticky=NW)
815       self.add_action.grid(column=0, row=2, sticky=NW)
816       self.browse.grid(column=1,  row=2, sticky=NW)
817       self.optionmenu.grid(column=0, row=3, sticky=NE)
818       self.cancel.grid(column=0, row=4, sticky=NE)
819       self.ok.grid(column=1, row=4, sticky=NW)
820       
821
822       self.add_action_label.grid(column=0, columnspan=2, sticky=W)
823
824    def browse_for_file(self):
825       filename = tkFileDialog.askopenfilename()
826       if not filename: return
827       if not isPackage(filename):
828             showerror("Package not understood", "The package must be an rpm or a tar.")
829             self.action_var.set("")
830             return
831
832       print "filename=",filename
833       if (self.var.get() == "update" or self.var.get() == "install") and os.path.isfile(filename):
834          self.tempfile = filename
835          self.action_var.set( os.path.basename( filename ) )
836       
837
838
839    def show_addnodes(self):
840       self.addnode_label.grid_forget()
841       self.addbox.grid(row=4,column=0, sticky=NW)
842       self.addmethodvar.set("single node")
843       self.node_entry.delete(0, len(self.node_entry.get()))
844       #self.use_singlenode()
845
846    def hide_addnodes(self):
847       self.addbox.grid_forget()
848       self.addnode_label.grid(row=3,column=0, sticky=NW)
849
850    def use_singlenode(self):
851       self.node_buttons.grid_forget()
852       self.comon_ex.grid_forget()
853       self.comon_label.grid_forget()
854       self.comon_query.grid_forget()
855
856       self.set_ex.grid_forget()
857       self.set_label.grid_forget()
858       self.set_menu.grid_forget()
859       self.set_with.grid_forget()
860       self.set_grouplist.grid_forget()     
861
862       self.singlenode_ex.grid(row=1, column=1,sticky=NW)
863       self.node_label.grid(row=2, column=0, sticky=NE)
864       self.node_entry.grid(row=2, column=1, sticky=NW)
865       self.node_cancel.grid(row=3, column=0, sticky=NE)
866       self.node_ok.grid(row=3, column=1, sticky=NE)
867       self.node_buttons.grid(row=3, column=1, sticky=NE)
868       self.node_entry.focus() 
869   
870    def use_comon(self):
871       self.node_buttons.grid_forget()
872       self.singlenode_ex.grid_forget()
873       self.node_label.grid_forget()
874       self.node_entry.grid_forget()
875
876       self.set_ex.grid_forget()
877       self.set_label.grid_forget()
878       self.set_menu.grid_forget()
879       self.set_with.grid_forget()
880       self.set_grouplist.grid_forget()     
881
882       self.comon_ex.grid(row=1, column=1, sticky=NW)
883       self.comon_label.grid(row=2, column=0, sticky=NE)
884       self.comon_query.grid(row=2, column=1, sticky=NW)
885       self.node_buttons.grid(row=3, column=1, sticky=NE)
886       self.comon_query.focus()
887
888    def use_set(self):
889       self.node_buttons.grid_forget()
890       self.singlenode_ex.grid_forget()
891       self.node_label.grid_forget()
892       self.node_entry.grid_forget()
893
894       self.comon_ex.grid_forget()
895       self.comon_label.grid_forget()
896       self.comon_query.grid_forget()
897
898       self.set_ex.grid(row=1, column=1,  sticky=NW)
899       self.set_label.grid(row=2, column=0, sticky=NE)
900       self.set_menu.grid(row=2, column=1,sticky=NW)
901       self.set_with.grid(row=3, column=0, sticky=NE)
902       self.set_grouplist.grid(row=3, column=1, sticky=NW)
903       self.node_buttons.grid(row=4, column=1, sticky=NE)
904
905       try:
906          self.set_grouplist["menu"].delete(0,END)
907       except: pass
908
909       def nothing(junk): pass
910
911       for foo in groups:
912          if foo == 'All': continue
913          if foo!=self.group_name:
914             self.set_grouplist["menu"].insert('end','command', label=foo, command=_setit(self.set_groupvar, foo, nothing)) 
915             self.set_groupvar.set(foo)
916       
917    
918    # this function will add a node to the current group,
919    # how that node or nodes is/are added depends on the
920    # method the user choose, eg: single node, comon, set op
921    def add_node(self):
922       method = self.addmethodvar.get()
923
924       if method == "single node":
925          node = self.node_entry.get().strip()
926          if len(node) == 0: return
927          # TODO separately check valid for hostname, IPv4, or IPv6
928          if re.search(r"[^a-zA-Z0-9:.-]", node):
929             showwarning("Invalid group name", "Node names can only contain the characters a-z, A-Z, 0-9, periods, hyphens, and colons.")
930             return
931          print "about to add single node: ",node
932          if node != None and node != "" and node not in nodes[self.group_name]: 
933             # add this node to the group
934             i = len(nodes[self.group_name])
935             nodes[self.group_name].append(node)
936
937             n = Label(self.lbox,text=node, font=self.helv9)
938             x = Button(self.lbox, image=self.ximage, borderwidth=0 )
939             x['command'] = lambda param=(x,n): self.remove_node(param)
940             
941             self.nodelist.append(n)
942             x.grid(column=0, row=i, sticky=W, pady=0 ) 
943             n.grid(column=1, row=i, sticky=W, pady=0 ) 
944             #update label
945             self.group.config(text=self.group_name+" ("+str(len(nodes[self.group_name]))+")" )
946             global statusframe
947             statusframe.set_in_synch(False)
948
949          pass #TODO stub
950       elif method == "CoMon query":
951          pass #TODO stub
952       elif method == "set operation":
953          pass #TODO stub
954
955       self.hide_addnodes(); 
956
957
958    def hide_actionbuttons(self):
959       self.add_action_area.grid_forget()
960       self.add_action_label.grid(column=0)
961
962        
963    def show_actionbuttons(self):
964       self.add_action_label.grid_forget()
965       self.add_action_area.grid(column=0, columnspan=2 )
966       self.add_action.focus()
967       self.add_action.delete(0, len(self.add_action.get()))
968
969
970    def add_action_to_list(self):
971        #param is (package, type)
972        param = ( self.add_action.get().strip(), self.var.get() )
973        if len(param[0]) == 0:
974           return
975        for existingparam in actions[self.group_name]:
976           if param[0] == existingparam[0]:
977              showwarning("Action already exists", "An action already exists for this package. To change the action for this package, remove the current action and then add the new one.")
978              return
979        if re.search(r"[^a-zA-Z0-9._-]", param[0]):
980           showwarning("Invalid package name", "Package names can only contain the characters a-z, A-Z, 0-9, periods, underscores, and hyphens.")
981           return
982       
983        print "addactin param=",param
984        print "actions[self.group_name]: ",actions[self.group_name]
985        print "going to add new action to row=",self.next_action_row
986
987        n = Label(self.actionbox, text="    "+param[0]+" ("+param[1]+")", font=self.helv9)
988        x = Button(self.actionbox, image=self.ximage, borderwidth=0)
989        x["command"]= lambda param=(n,x,param): self.remove_action(param)
990        self.actions.append(n)
991        x.grid(column=0, row=self.next_action_row, sticky=NW)
992        n.grid(column=1, row=self.next_action_row, sticky=NW)
993        self.next_action_row += 1
994
995        actions[self.group_name].append( param )
996        self.hide_actionbuttons()
997        global statusframe
998        statusframe.set_in_synch(False)
999     
1000        # see if we have to add this to the trusted packages file (that is,
1001        # if the user just selected a package on their file system
1002        if self.tempfile != None:
1003          global to_upload
1004          if self.tempfile not in to_upload:
1005             trustPackage(self.tempfile)
1006             to_upload.append(self.tempfile) 
1007
1008        self.tempfile = None
1009
1010
1011        
1012
1013    def remove_action(self, param):
1014       self.actions.remove(param[0])
1015       param[0].destroy()
1016       param[1].destroy()
1017       #TODO Error, the tuple is not being removed from the action list correctly
1018       print "remove_action param=", param
1019
1020       actiontuple = param[2]
1021       for foo in actions[self.group_name]:
1022          if foo[0] == actiontuple[0] and foo[1] == actiontuple[1]:
1023             print "removing: ",foo
1024             actions[self.group_name].remove(foo)
1025             global statusframe
1026             statusframe.set_in_synch(False)
1027
1028    def remove_node(self, param):
1029       node = param[1]["text"]
1030       if node in nodes[self.group_name]:
1031          nodes[self.group_name].remove(node)
1032       param[0].destroy()
1033       param[1].grid_forget()
1034       param[1].destroy()
1035       self.group.config(text=self.group_name+" ("+str(len(nodes[self.group_name]))+")" )
1036       global statusframe
1037       statusframe.set_in_synch(False)
1038       
1039    def collapse_group(self, event):
1040       #do everything needed to make most items "hidden"
1041       if self.collapsed: return
1042
1043       self.lbox.grid_forget()
1044       
1045       self.collapsed = 1       
1046       self.expandgroup.bind('<Button-1>', self.expand_group)
1047       self.expandgroup.config(text="expand") 
1048   
1049
1050    def expand_group(self, event):
1051       #do everything needed to make things visible again
1052       if not self.collapsed: return
1053
1054       self.lbox.grid( column=0, row=2, sticky='nw', pady=0 ) 
1055
1056       self.collapsed = 0
1057       self.expandgroup.bind('<Button-1>', self.collapse_group )
1058       self.expandgroup.config(text="collapse")
1059
1060    def remove_group(self):
1061       #TODO put hook into storkutil to remove this group
1062       groups.remove(self.group_name)
1063       nodes[self.group_name] = []
1064       self.destroy()
1065       global statusframe
1066       statusframe.set_in_synch(False)
1067
1068 def authenticate(username, password, site):
1069    #TODO finish authenticate stub by making a call to the repository via curl
1070    return sc.login(username, password,site)
1071  
1072 authenticated = False
1073 def makeloginwindow():
1074    login = Tk()
1075    login.title('Stork Slice Manager')
1076    login.width=1100
1077    login.height=800
1078    helv10 = tkFont.Font ( family="Helvetica",\
1079         size=10, weight="bold" )
1080    
1081    ex_label       = Label(login,width=60, height=3, justify=LEFT, text="Please enter your Planetlab username/password. \nThis information will only be transmitted over a secure connection \nand is needed to identify the slices you have access to")
1082
1083    username_label = Label(login,width=20,text="Planetlab Username:")
1084    password_label = Label(login,width=20,text="Planetlab Password:")
1085    storkusername_l = Label(login,width=20,text="Stork Username:")
1086
1087    username_field = Entry(login)
1088    password_field = Entry(login,show="*")
1089    storkusername_e= Entry(login) #TODO try to search the current directory to see if we can fill this field out
1090    wheretologin_label = Label(login,width=20,text="PlanetLab account type:")
1091    wheretologin_field = Entry(login)
1092    wheretologin_field.insert(0,"www.planet-lab.org")
1093
1094         
1095
1096    def trylogin():
1097       global storkusername
1098       global username
1099       global password
1100       global wheretologin
1101       username = username_field.get().strip()
1102       password = password_field.get()
1103       storkusername = storkusername_e.get().strip()
1104       wheretologin = wheretologin_field.get()
1105       if len(username) == 0:
1106          ex_label.config(text="Please enter your PlanetLab username.", fg="red")
1107          return
1108       if len(password) == 0:
1109          ex_label.config(text="Please enter your PlanetLab password.", fg="red")
1110          return
1111       if len(storkusername) == 0:
1112          ex_label.config(text="Please enter your Stork username.", fg="red")
1113          return         
1114       if wheretologin != "www.planet-lab.org" and wheretologin != "www.planet-lab.eu" and wheretologin != "local":
1115          ex_label.config(text="Invalid authentication site\nPlease use www.planetlab-org or www.planet-lab.eu", fg="red")
1116          return
1117       if re.search(r"[^a-zA-Z0-9_-]", storkusername):
1118          ex_label.config(text="Stork usernames can only contain the characters \na-z, A-Z, 0-9, underscores, and hyphens.", fg="red")
1119          return
1120      
1121       if authenticate(username_field.get(), password_field.get(), wheretologin_field.get() ):
1122          authenticated=True
1123          # fetch any possible pacman files (and tpfiles?) for the current storkustname
1124          pubkey = getPubKey()
1125          if pubkey == None:
1126             #ask them if they want a new key
1127             result = askyesno("New User","Key for user '"+storkusername+"' not found. Create a new key for this user?")
1128             if result == True:
1129                 makeKey(storkusername)
1130             else:
1131                 print "No user key present. Please create a user key or use a recognized username."
1132                 return
1133             pubkey = getPubKey()
1134             if pubkey == None:
1135                print "Error: could not create/read public key for: "+storkusername
1136                return
1137
1138          # user is now logged in and has a valid key
1139          global storkutiluserflags
1140          storkutiluserflags = '--username='+storkusername+' --publickey='+storkusername+'.publickey --privatekey='+storkusername+'.privatekey'
1141
1142          sc.get_file("user-upload/pacman/"+storkusername+"."+pubkey+".packages.pacman", storkusername+"."+pubkey+".packages.pacman")
1143          sc.get_file("user-upload/pacman/"+storkusername+"."+pubkey+".groups.pacman", storkusername+"."+pubkey+".groups.pacman")
1144          sc.get_file("user-upload/tpfiles/"+storkusername+"."+pubkey+".tpfile", storkusername+"."+pubkey+".tpfile")
1145
1146          #initialize the nodes/groups/packages
1147          global groups
1148          global nodes
1149          global actions
1150          groups = getGroups()
1151          for group in groups:
1152             nodes[group] = getNodes(group)
1153             actions[group] = getActions(group)
1154
1155          login.destroy()
1156       else:
1157          ex_label.config(text="Invalid username/password. Please try again", fg="red")
1158
1159    button_frame   = Frame(login)
1160    ok_button      = Button(button_frame,text="Ok", command=trylogin)
1161    cancel_button  = Button(button_frame,text="Cancel",command=lambda: sys.exit(0) )
1162    cancel_button.grid(row=0, column=0, sticky=E)
1163    ok_button.grid(row=0, column=1, sticky=E)
1164
1165    ex_label.grid(row=0, column=0, columnspan=2, sticky=NW)
1166    username_label.grid(row=1, column=0, sticky=W)
1167    username_field.grid(row=1, column=1, sticky=W)
1168    password_label.grid(row=2, column=0, sticky=W)
1169    password_field.grid(row=2, column=1, sticky=W)
1170    storkusername_l.grid(row=3, column=0,sticky=W)
1171    storkusername_e.grid(row=3, column=1,sticky=W)
1172    wheretologin_label.grid(row=4, column=0, sticky=W)
1173    wheretologin_field.grid(row=4, column=1, sticky=W)
1174    button_frame.grid(row=5, column=0, columnspan=2, sticky=E)
1175
1176    username_field.bind("<Return>", lambda event: password_field.focus() )
1177    password_field.bind("<Return>", lambda event: storkusername_e.focus() )
1178    storkusername_e.bind("<Return>",lambda event: wheretologin_field.focus() )
1179    wheretologin_field.bind("<Return>", lambda event: ok_button.invoke() )
1180    username_field.focus()
1181
1182    login.geometry("+%d+%d" % (300, 300))
1183
1184    print "about to check for update"
1185    check_for_update()
1186
1187    login.mainloop() 
1188
1189 def close_window_callback():
1190    global synched
1191    if synched or askokcancel("Exit without synching?", "You have changes that are not synched with the repository. These changes will be lost if you exit. \n\nClick OK to exit without synching.", default=CANCEL):
1192       global root
1193       root.destroy()
1194
1195 def check_for_update():
1196    global checkedforupdate
1197    if not checkedforupdate:
1198       uptodate, version, component = sc.is_latest_version(guiversion)
1199
1200       if not uptodate and version != "unknown":
1201          showstring = "The "+str(component)+" module is currently at version \n"
1202          if component == "storkslicemanager":
1203             showstring = showstring + guiversion
1204          elif component == "storkcurlfuncs": 
1205             showstring = showstring + sc.scversion
1206
1207          showstring = showstring + "\nand can be autoupdated to\n"+version+"\n"
1208          showstring = showstring + "Would you like to update?\n"
1209
1210          if askyesno("Out of date", showstring):
1211             sc.update_gui(scriptpath)
1212
1213       checkedforupdate = True
1214
1215
1216
1217 def main():
1218    #if we have not checked for an update yet, do so
1219    if not os.path.isfile( os.path.expanduser("~/.storkmanager.conf") ):
1220       try:
1221          f = open( os.path.expanduser("~/.storkmanager.conf"), "w")
1222          f.write("tarpackinfo = /tmp/tarinfo\n")
1223          f.close()
1224       except:
1225          pass
1226
1227    args = arizonaconfig.init_options('storkslicemanager.py', usage="", configfile_optvar='managerconf', version='2.0')
1228
1229    if not debug:
1230       makeloginwindow()
1231      
1232    global switch_user
1233    switch_user = False
1234
1235    global root
1236    root = Tk()
1237    root.title('Stork Slice Manager')
1238    root.width=800
1239    root.height=600
1240    root.grid_propagate(True)
1241
1242
1243    #create main scrollable canvas
1244    cnv = Canvas(root, width=root.width-20, height=600)
1245    cnv.grid(row=0, column=0, sticky='nswe')
1246    vScroll = Scrollbar(root, orient=VERTICAL, command=cnv.yview)
1247    vScroll.grid(row=0, column=1, sticky='ns')
1248    cnv.configure(yscrollcommand=vScroll.set)
1249    #make a frame to put in the canvas
1250    frm = Frame(cnv)
1251    #put the frame in the canvas's scrollable area
1252    cnv.create_window(0,0, window=frm, anchor='nw')
1253
1254
1255    top   = TopBlock(frm)
1256
1257    global topoptions
1258    topoptions = TopOptions(frm,root)
1259    global statusframe
1260    statusframe= StatusFrame(frm)
1261    #groupframes.append(group)
1262    #group.config(bd=2, relief=GROOVE)
1263    
1264
1265    # place it somewhere on the screen that makes sense
1266    #frm.geometry("%dx%d" % (300, 500) )
1267    root.geometry("%dx%d+%d+%d" % (800, 600, 50, 50))
1268    
1269    # setup some event handlers
1270
1271    def somethinghappened(event):
1272       frm.update_idletasks()
1273       cnv.configure(scrollregion=(0, 0, frm.winfo_width(), frm.winfo_height()))
1274
1275    def scrollDown(event):
1276       cnv.yview_scroll(2, 'units')
1277    def scrollUp(event):
1278       cnv.yview_scroll(-2, 'units')
1279
1280    root.bind('<Button-4>', scrollUp)
1281    root.bind('<Button-5>', scrollDown)
1282
1283    frm.bind("<Configure>", somethinghappened)
1284
1285    frm.grid_propagate(True)
1286    frm.update_idletasks()
1287    cnv.configure(scrollregion=(0, 0, 800, frm.winfo_height()))
1288
1289    
1290    topoptions.grid(pady=10,row=0, column=0, sticky=NW)
1291    statusframe.grid(pady=10,row=0,column=1, sticky=NW)
1292   
1293    group = Group(frm, "All", top)
1294    group.grid(pady=3, columnspan=2, sticky=NW)
1295    groupframes["All"] = group
1296
1297    for foo in groups:
1298       if foo == "All": continue
1299       group = Group(frm, foo, top)
1300       group.config(relief=GROOVE)
1301       group.grid(pady=3,columnspan=2, sticky=NW) 
1302       groupframes[foo] = group
1303
1304    groups.append("All")
1305
1306    if len(groups) > 0:
1307       top.set_group( groups[-1] )  
1308  
1309    top.grid(pady=20, sticky=NW)
1310    
1311    # assign a callback function for when the user closes the window        
1312    root.protocol("WM_DELETE_WINDOW", close_window_callback)
1313
1314    # setup the menu bar
1315    menubar = Menu(root)
1316    # File menu
1317    filemenu = Menu(menubar, tearoff=0)
1318    filemenu.add_command(label="Exit", command=close_window_callback)
1319    menubar.add_cascade(label="File", menu=filemenu)
1320    # User menu
1321    usermenu = Menu(menubar, tearoff=0)
1322    usermenu.add_command(label="Switch user", command=topoptions.switchuser)
1323    menubar.add_cascade(label="User", menu=usermenu)
1324    # Repository menu
1325    repositorymenu = Menu(menubar, tearoff=0)
1326    repositorymenu.add_command(label="Synch with repository", command=lambda: statusframe.set_in_synch(True))
1327    menubar.add_cascade(label="Repository", menu=repositorymenu)
1328    # Help menu
1329    helpmenu = Menu(menubar, tearoff=0)
1330    helpmenu.add_command(label="Stork website", command=lambda: webbrowser.open("http://www.cs.arizona.edu/stork/"))
1331    helpmenu.add_command(label="Stork forum", command=lambda: webbrowser.open("http://cgi.cs.arizona.edu/projects/stork/forum/"))
1332    menubar.add_cascade(label="Help", menu=helpmenu)
1333    # display the menu
1334    root.config(menu=menubar)
1335
1336    root.mainloop()
1337
1338
1339
1340 if __name__ == "__main__":
1341
1342    while True:  
1343       main()
1344       print "about to exit or restart, switch_user=",switch_user
1345       if not switch_user:
1346          break