"""
Author: Alan Loh
Module: Library of command callback methods. A large portion of the code were
        directly transferred here from the original seash. Each command function 
        is assigned to the appropriate levels of the respective command's 
        dictionary, and are called upon by command_parser.py's dispatch method 
        when executing a user's command input.

All callback methods should take in two arguments: 
-a command dictionary, input_dict, that holds and reflects the user's command input
-the environment_dict holding the following variables:
  host
  port
  expnum 
  filename 
  cmdargs 
  defaulttarget 
  defaultkeyname
  currenttarget
  currentkeyname
  handleinfo
  autosave

In order to pull user-inputed arguments from the command dictionary, the 
following code is used throughout for consistency and ease:

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename input
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

...and command_key will either contain the user's file name, found by searching 
for the appropriate command dictionary with the name 'filename' and taking the 
key associated with it, or it will hold the key of the last command dictionary 
in the input dictionary chain. It is important that the name of the argument 
command dictionary is unique and spelled correctly.

Method names should reflect the expected command string used to executed that 
command dictionary's command callback. For example, the command "show resources
to file"'s method should be called show_resources_to_filename.

Commands with slight variations in function depending on how the user inputted 
it should use a separate method for each variation in its command string. For 
example, 'browse' would have the method named 'browse', while 'browse [type]' 
would have a separate method called 'browse_args'
"""

# For access to additional methods in command handling
import seash_helper
# For access to various global variables kept track of throughout a seash session
import seash_global_variables
# To be able to modify the command dictionary
import seash_dictionary
# To be able to look up what a module commanddict contains (for modulehelp)
import seash_modules

# To be able to throw certain exceptions designed specifically for seash
import seash_exceptions
import seash_modules

import os.path
import sys

from repyportability import *
add_dy_support(locals())

# dy_import_module_symbols("nmclient.r2py")

# This is a temporary workaround for the problem where nmclient is being
# initialized multiple times by dylink.r2py when it is imported multiple
# times.  The handles returned by the nmclient imported here will be
# completely different from the set of handles that the nmclient
# imported in seash_helper knows about.  This needs to be removed once
# dylink is properly fixed. See #1319 for more information.

nmclient_createhandle = seash_helper.nmclient_createhandle
nmclient_listaccessiblevessels = seash_helper.nmclient_listaccessiblevessels
nmclient_duplicatehandle = seash_helper.nmclient_duplicatehandle
nmclient_get_handle_info = seash_helper.nmclient_get_handle_info
nmclient_set_handle_info = seash_helper.nmclient_set_handle_info

dy_import_module_symbols("time.r2py")

dy_import_module_symbols("rsa.r2py")

dy_import_module_symbols("listops.r2py")

dy_import_module_symbols("parallelize.r2py")

dy_import_module_symbols("domainnameinfo.r2py")

dy_import_module_symbols("advertise.r2py")   #  used to do OpenDHT lookups

dy_import_module_symbols("serialize.r2py") # used for loadstate and savestate

# The versions of Seattle that we officially support.
SUPPORTED_PROG_PLATFORMS = ["repyV1", "repyV2"]


# set the target, then handle other operations
def on_target(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's target name
  while input_dict[command_key]['name'] is not 'ontarget':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  if command_key not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error: Unknown target '"+command_key+"'")

  # set the target and strip the rest...
  environment_dict['currenttarget'] = command_key

  # Set default if there's no follow-up commands
  if not input_dict[command_key]['children']:
    environment_dict['defaulttarget'] = environment_dict['currenttarget']




# set the keys, then handle other operations
def as_keyname(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's keyname input
  while input_dict[command_key]['name'] is not 'askeyname':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]


  if command_key not in seash_global_variables.keys:
    raise seash_exceptions.UserError("Error: Unknown identity '"+command_key+"'")

            
  # set the target and strip the rest...
  environment_dict['currentkeyname'] = command_key

  # Set default if there's no follow-up commands
  if not input_dict[command_key]['children']:
    environment_dict['defaultkeyname'] = environment_dict['currentkeyname']



###Show Commands###

# show            -- Does nothing
def show(input_dict, environment_dict):
  pass




# show info       -- Display general information about the vessels
def show_info(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")


  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:

    if seash_global_variables.vesselinfo[longname]['information']:
      print longname, seash_global_variables.vesselinfo[longname]['information']

    else:
      print longname, "has no information (try 'update' or 'list')"




# show targets    -- Display a list of targets
def show_targets(input_dict, environment_dict):

  for target in seash_global_variables.targets:

    if len(seash_global_variables.targets[target]) == 0:
      print target, "(empty)"
      continue

    # this is a vesselentry
    if target == seash_global_variables.targets[target][0]:
      continue

    print target, seash_global_variables.targets[target]



# show groups    -- Display a list of groups 
def show_groups(input_dict, environment_dict):

  for target in seash_global_variables.targets:

    if target == "%all":
      print target, seash_global_variables.targets[target]
      continue
	
    # this is individual target number
    if target.startswith('%'):
      continue
	
    if len(seash_global_variables.targets[target]) == 0:
      print target, seash_global_variables.targets[target]
      continue
	  
    # this is a vesselentry
    if target == seash_global_variables.targets[target][0]:
      continue
	  
    print target, seash_global_variables.targets[target]



# show keys       -- Display the known keys
def show_keys(input_dict, environment_dict):

  for identity in seash_global_variables.keys:

    print identity,seash_global_variables.keys[identity]['publickey'],seash_global_variables.keys[identity]['privatekey']




# show identities -- Display the known identities
def show_identities(input_dict, environment_dict):

  for keyname in seash_global_variables.keys:

    print keyname,

    if seash_global_variables.keys[keyname]['publickey']:
      print "PUB",

    if seash_global_variables.keys[keyname]['privatekey']:
      print "PRIV",

    print




# show users      -- Display the user keys for the vessels
def show_users(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:

    if 'userkeys' in seash_global_variables.vesselinfo[longname]:
      if seash_global_variables.vesselinfo[longname]['userkeys'] == []:
        print longname,"(no keys)"
        continue

      print longname,

      # we'd like to say 'joe's public key' instead of '3453 2323...'
      for key in seash_global_variables.vesselinfo[longname]['userkeys']:

        for identity in seash_global_variables.keys:

          if seash_global_variables.keys[identity]['publickey'] == key:
            print identity,
            break

        else:
          print seash_helper.fit_string(rsa_publickey_to_string(key),15),

      print

    else:
      print longname, "has no information (try 'update' or 'list')"
    



# show ownerinfo  -- Display owner information for the vessels
def show_ownerinfo(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:

    if 'ownerinfo' in seash_global_variables.vesselinfo[longname]:
      print longname, "'"+seash_global_variables.vesselinfo[longname]['ownerinfo']+"'"
      # list all of the info...

    else:
      print longname, "has no information (try 'update' or 'list')"




# show advertise  -- Display advertisement information about the vessels
def show_advertise(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:

    if 'advertise' in seash_global_variables.vesselinfo[longname]:

      if seash_global_variables.vesselinfo[longname]['advertise']:
        print longname, "on"

      else:
        print longname, "off"

    # list all of the info...
    else:
      print longname, "has no information (try 'update' or 'list')"




# show owner      -- Display a vessel's owner
def show_owner(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:

    if 'ownerkey' in seash_global_variables.vesselinfo[longname]:
      # we'd like to say 'joe public key' instead of '3453 2323...'
      ownerkey = seash_global_variables.vesselinfo[longname]['ownerkey']

      for identity in seash_global_variables.keys:

        if seash_global_variables.keys[identity]['publickey'] == ownerkey:
          print longname, identity+" pubkey"
          break

      else:
        print longname, seash_helper.fit_string(rsa_publickey_to_string(ownerkey),15)

    else:
      print longname, "has no information (try 'update' or 'list')"




# show files      -- Display a list of files in the vessel (*)
def show_files(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  # print the list of files in the vessels (seash method)
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']], seash_helper.showfiles_target)

  goodlist = []
  faillist = []

  for longname in retdict:

    # True means it worked
    if retdict[longname][0]:
      # let's sort the files...
      fileliststring = retdict[longname][1]
      filelist = fileliststring.split()
      filelist.sort()
      fileliststring = " ".join(filelist)
      print "Files on '"+longname+"': '"+fileliststring+"'"
      goodlist.append(longname)

    else:
      faillist.append(longname)
  
  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['filesgood'] = goodlist
    seash_global_variables.targets['filesfail'] = faillist
    print "Added group 'filesgood' with "+str(len(seash_global_variables.targets['filesgood']))+" targets and 'filesfail' with "+str(len(seash_global_variables.targets['filesfail']))+" targets"




# show log [to file] -- Display the log from the vessel (*)
def show_log(input_dict, environment_dict):

  writeoutputtofile = False


  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  # print the vessel logs...
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']], seash_helper.showlog_target)

  goodlist = []
  faillist = []

  for longname in retdict:
    # True means it worked
    if retdict[longname][0]:
      if writeoutputtofile:
        # write to a file if requested.
        outputfilename = outputfileprefix +'.'+ longname
        outputfo = file(outputfilename,'w')
        outputfo.write(retdict[longname][1])
        outputfo.close()
        print "Wrote log as '"+outputfilename+"'."

      else:
        # else print it to the terminal
        print "Log from '"+longname+"':"
        print retdict[longname][1]

      # regardless, this is a good node
      goodlist.append(longname)

    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['loggood'] = goodlist
    seash_global_variables.targets['logfail'] = faillist
    print "Added group 'loggood' with "+str(len(seash_global_variables.targets['loggood']))+" targets and 'logfail' with "+str(len(seash_global_variables.targets['logfail']))+" targets"




# show log to file
def show_log_to_file(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  writeoutputtofile = True
  # handle '~'
  fileandpath = os.path.expanduser(command_key)
  outputfileprefix = fileandpath


  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  # print the vessel logs...
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']], seash_helper.showlog_target)

  goodlist = []
  faillist = []

  for longname in retdict:
    # True means it worked
    if retdict[longname][0]:
      if writeoutputtofile:
        # write to a file if requested.
        outputfilename = outputfileprefix +'.'+ longname
        outputfo = file(outputfilename,'w')
        outputfo.write(retdict[longname][1])
        outputfo.close()
        print "Wrote log as '"+outputfilename+"'."

      else:
        # else print it to the terminal
        print "Log from '"+longname+"':"
        print retdict[longname][1]

      # regardless, this is a good node
      goodlist.append(longname)

    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['loggood'] = goodlist
    seash_global_variables.targets['logfail'] = faillist
    print "Added group 'loggood' with "+str(len(seash_global_variables.targets['loggood']))+" targets and 'logfail' with "+str(len(seash_global_variables.targets['logfail']))+" targets"




# show resources  -- Display the resources / restrictions for the vessel (*)
def show_resources(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']], seash_helper.showresources_target)
  faillist = []
  goodlist = []

  for longname in retdict:

    # True means it worked
    if retdict[longname][0]:
      print "Resource data for '"+longname+"':"
      print retdict[longname][1]
      goodlist.append(longname)

    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['resourcegood'] = goodlist
    seash_global_variables.targets['resourcefail'] = faillist
    print "Added group 'resourcegood' with "+str(len(seash_global_variables.targets['resourcegood']))+" targets and 'resourcefail' with "+str(len(seash_global_variables.targets['resourcefail']))+" targets"




# show offcut     -- Display the offcut resource for the node (*)
def show_offcut(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")


  # we should only visit a node once...
  nodelist = listops_uniq(seash_helper.longnamelist_to_nodelist(seash_global_variables.targets[environment_dict['currenttarget']]))
  retdict = seash_helper.contact_targets(nodelist, seash_helper.showoffcut_target)

  for nodename in retdict:

    if retdict[nodename][0]:
      print "Offcut resource data for '"+nodename+"':"
      print retdict[nodename][1]

  seash_helper.print_vessel_errors(retdict)




# show ip [to file] -- Display the ip addresses of the nodes
def show_ip(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  # print to the terminal (stdout)
  # write data here...
  outfo = sys.stdout


  # we should only visit a node once...
  printedIPlist = []

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:
    thisnodeIP = seash_global_variables.vesselinfo[longname]['IP']

    if thisnodeIP not in printedIPlist:
      printedIPlist.append(thisnodeIP)
      print >> outfo, thisnodeIP




# show ip to file
def show_ip_to_file(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  outfo = open(command_key,"w+")


  # we should only visit a node once...
  printedIPlist = []

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:
    thisnodeIP = seash_global_variables.vesselinfo[longname]['IP']

    if thisnodeIP not in printedIPlist:
      printedIPlist.append(thisnodeIP)
      print >> outfo, thisnodeIP

  # if it's a file, close it...
  outfo.close()




# show hostname        -- Display the hostnames of the nodes
def show_hostname(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Error, command requires a target")


  # we should only visit a node once...
  printedIPlist = []

  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:
    thisnodeIP = seash_global_variables.vesselinfo[longname]['IP']

    if thisnodeIP not in printedIPlist:
      printedIPlist.append(thisnodeIP)
      print thisnodeIP,

      try: 
        nodeinfo = socket.gethostbyaddr(thisnodeIP)

      except (socket.herror,socket.gaierror, socket.timeout, socket.error):
        print 'has unknown host information'

      else:
        print 'is known as',nodeinfo[0]







# show timeout    -- Display the timeout for nodes
def show_timeout(input_dict, environment_dict):
  print seash_global_variables.globalseashtimeout




# show uploadrate    -- Display the file upload rate
def show_uploadrate(input_dict, environment_dict):
  print seash_global_variables.globaluploadrate




# add target (to group)
def add_target(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's target name
  while input_dict[command_key]['name'] is not 'target':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  source = command_key
  dest = environment_dict['currenttarget']


  # okay, now source and dest are set.   Time to add the nodes in source
  # to the group dest...
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown target '"+source+"'")

  if dest not in seash_global_variables.targets:
    if not seash_helper.valid_targetname(dest):
      raise seash_exceptions.UserError("Error, invalid target name '"+dest+"'")
    seash_global_variables.targets[dest] = []

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+dest+"'")

  # source - dest has what we should add (items in source but not dest)
  addlist = listops_difference(seash_global_variables.targets[source],seash_global_variables.targets[dest])

  if len(addlist) == 0:
    raise seash_exceptions.UserError("No targets to add (the target is already in '"+dest+"')")

  for item in addlist:
    seash_global_variables.targets[dest].append(item)




# add target to group
def add_target_to_group(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's target name
  while input_dict[command_key]['name'] is not 'target':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  source = command_key

  # Iterates through the dictionary to retrieve the user's group argument
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  dest = command_key


  # okay, now source and dest are set.   Time to add the nodes in source
  # to the group dest...
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown target '"+source+"'")

  if dest not in seash_global_variables.targets:
    if not seash_helper.valid_targetname(dest):
      raise seash_exceptions.UserError("Error, invalid target name '"+dest+"'")

    seash_global_variables.targets[dest] = []

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+dest+"'")

  # source - dest has what we should add (items in source but not dest)
  addlist = listops_difference(seash_global_variables.targets[source],seash_global_variables.targets[dest])

  if len(addlist) == 0:
    raise seash_exceptions.UserError("No targets to add (the target is already in '"+dest+"')")

  for item in addlist:
    seash_global_variables.targets[dest].append(item)





# add to group
def add_to_group(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's group argument
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  source = environment_dict['currenttarget']
  dest = command_key


  # okay, now source and dest are set.   Time to add the nodes in source
  # to the group dest...
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown target '"+source+"'")

  if dest not in seash_global_variables.targets:
    if not seash_helper.valid_targetname(dest):
      raise seash_exceptions.UserError("Error, invalid target name '"+dest+"'")
    seash_global_variables.targets[dest] = []

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+dest+"'")

  # source - dest has what we should add (items in source but not dest)
  addlist = listops_difference(seash_global_variables.targets[source],seash_global_variables.targets[dest])

  if len(addlist) == 0:
    raise seash_exceptions.UserError("No targets to add (the target is already in '"+dest+"')")

  for item in addlist:
    seash_global_variables.targets[dest].append(item)





# remove target (from group)
def remove_target(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's target name
  while input_dict[command_key]['name'] is not 'target':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  source = command_key
  dest = environment_dict['currenttarget']



  # time to check args and do the ops
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown target '"+source+"'")

  if dest not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown group '"+dest+"'")

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+dest+"'")

  # find the items to remove (the items in both dest and source)
  removelist = listops_intersect(seash_global_variables.targets[dest],seash_global_variables.targets[source])

  if len(removelist) == 0:
    raise seash_exceptions.UserError("No targets to remove (no items from '"+source+"' are in '"+dest+"')")

  # it's okay to end up with an empty group.   We'll leave it...
  for item in removelist:
    seash_global_variables.targets[dest].remove(item)




# remove target from group
def remove_target_from_group(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's target name
  while input_dict[command_key]['name'] is not 'target':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  source = command_key

  # Iterates through the dictionary to retrieve the user's group name
  while input_dict[command_key]['name'] is not 'group':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  dest = command_key


  # time to check args and do the ops
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown target '"+source+"'")

  if dest not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown group '"+dest+"'")

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+dest+"'")

  # find the items to remove (the items in both dest and source)
  removelist = listops_intersect(seash_global_variables.targets[dest],seash_global_variables.targets[source])

  if len(removelist) == 0:
    raise seash_exceptions.UserError("No targets to remove (no items from '"+source+"' are in '"+dest+"')")

  # it's okay to end up with an empty group.   We'll leave it...
  for item in removelist:
    seash_global_variables.targets[dest].remove(item)




# remove from group
def remove_from_group(input_dict, environment_dict):

  source = environment_dict['currenttarget']

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's group name
  while input_dict[command_key]['name'] is not 'group':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  dest = command_key


  # time to check args and do the ops
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown target '"+source+"'")

  if dest not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown group '"+dest+"'")

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+dest+"'")

  # find the items to remove (the items in both dest and source)
  removelist = listops_intersect(seash_global_variables.targets[dest],seash_global_variables.targets[source])

  if len(removelist) == 0:
    raise seash_exceptions.UserError("No targets to remove (no items from '"+source+"' are in '"+dest+"')")

  # it's okay to end up with an empty group.   We'll leave it...
  for item in removelist:
    seash_global_variables.targets[dest].remove(item)




# move target to group
def move_target_to_group(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's target name
  while input_dict[command_key]['name'] is not 'target':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  moving = command_key
  source = environment_dict['currenttarget']

  # Iterates through the dictionary to retrieve the user's group name
  while input_dict[command_key]['name'] is not 'group':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  dest = command_key


  # check args...
  if source not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown group '"+source+"'")

  if moving not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown group '"+moving+"'")

  if dest not in seash_global_variables.targets:
    raise seash_exceptions.UserError("Error, unknown group '"+dest+"'")


  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError("Can't modify the contents of '"+source+"'")

  if seash_helper.is_immutable_targetname(dest):
    raise seash_exceptions.UserError( "Can't modify the contents of '"+dest+"'")

  removelist = listops_intersect(seash_global_variables.targets[source], seash_global_variables.targets[moving])
  if len(removelist) == 0:
    raise seash_exceptions.UserError("Error, '"+moving+"' is not in '"+source+"'")

  addlist = listops_difference(removelist, seash_global_variables.targets[dest])
  if len(addlist) == 0:
    raise seash_exceptions.UserError("Error, the common items between '"+source+"' and '"+moving+"' are already in '"+dest+"'")

  for item in removelist:
    seash_global_variables.targets[source].remove(item)

  for item in addlist:
    seash_global_variables.targets[dest].append(item)








# contact host:port[:vessel] -- Communicate with a node
def contact(input_dict, environment_dict):

  if environment_dict['currentkeyname'] == None or not seash_global_variables.keys[environment_dict['currentkeyname']]['publickey']:
    raise seash_exceptions.UserError("Error, must contact as an identity")

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  if len(command_key.split(':')) == 2:
    environment_dict['host'], portstring = command_key.split(':')
    environment_dict['port'] = int(portstring)
    vesselname = None

  elif len(command_key.split(':')) == 3:
    environment_dict['host'], portstring,vesselname = command_key.split(':')
    environment_dict['port'] = int(portstring)

  else:
    raise seash_exceptions.UserError("Error, usage is contact host:port[:vessel]")


  # get information about the node's vessels
  thishandle = nmclient_createhandle(environment_dict['host'], environment_dict['port'], privatekey = seash_global_variables.keys[environment_dict['currentkeyname']]['privatekey'], publickey = seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'], vesselid = vesselname, timeout = seash_global_variables.globalseashtimeout)
  ownervessels, uservessels = nmclient_listaccessiblevessels(thishandle,seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'])

  newidlist = []
  # determine if we control the specified vessel...
  if vesselname:
    if vesselname in ownervessels or vesselname in uservessels:
      longname = environment_dict['host']+":"+str(environment_dict['port'])+":"+vesselname
      # no need to set the vesselname, we did so above...
      id = seash_helper.add_vessel(longname,environment_dict['currentkeyname'],thishandle)
      newidlist.append('%'+str(id)+"("+longname+")")

    else:
      raise seash_exceptions.UserError("Error, cannot access vessel '"+vesselname+"'")

  # we should add anything we can access
  else:
    for vesselname in ownervessels:

      longname = environment_dict['host']+":"+str(environment_dict['port'])+":"+vesselname

      if longname not in seash_global_variables.targets:
        # set the vesselname
        # NOTE: we leak handles (no cleanup of thishandle).   
        # I think we don't care...
        newhandle = nmclient_duplicatehandle(thishandle)
        environment_dict['handleinfo'] = nmclient_get_handle_info(newhandle)
        environment_dict['handleinfo']['vesselname'] = vesselname
        nmclient_set_handle_info(newhandle, environment_dict['handleinfo'])

        id = seash_helper.add_vessel(longname,environment_dict['currentkeyname'],newhandle)
        newidlist.append('%'+str(id)+"("+longname+")")


    for vesselname in uservessels:

      longname = environment_dict['host']+":"+str(environment_dict['port'])+":"+vesselname

      if longname not in seash_global_variables.targets:
        # set the vesselname
        # NOTE: we leak handles (no cleanup of thishandle).   
        # I think we don't care...
        newhandle = nmclient_duplicatehandle(thishandle)
        environment_dict['handleinfo'] = nmclient_get_handle_info(newhandle)
        environment_dict['handleinfo']['vesselname'] = vesselname
        nmclient_set_handle_info(newhandle, environment_dict['handleinfo'])

        id = seash_helper.add_vessel(longname,environment_dict['currentkeyname'],newhandle)
        newidlist.append('%'+str(id)+"("+longname+")")


  # tell the user what we did...
  if len(newidlist) == 0:
    print "Could not add any targets."

  else:
    print "Added targets: "+", ".join(newidlist)
            
  





# browse                               -- Find experiments I can control
def browse(input_dict, environment_dict):
 
  if environment_dict['currentkeyname'] == None or not seash_global_variables.keys[environment_dict['currentkeyname']]['publickey']:
    raise seash_exceptions.UserError("Error, must browse as an identity with a public key")


  try:
    nodelist = advertise_lookup(seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'], graceperiod = 3)
  except (AdvertiseError, TimeoutError), e:
    # print the error and return to the user.   Let them decide what to do.
    print "Error:",e
    print "Retry or check network connection."
    return
    


  # If there are no vessels for a user, the lookup may return ''
  for nodename in nodelist[:]:
    if nodename == '':
      nodelist.remove(nodename)

  # we'll output a message about the new keys later...
  newidlist = []

  faillist = []

  seash_global_variables.targets['browsegood'] = []

  print nodelist

  # currently, if I browse more than once, I look up everything again...
  retdict = seash_helper.contact_targets(nodelist,seash_helper.browse_target, environment_dict['currentkeyname'])

  # parse the output so we can print out something intelligible
  for nodename in retdict:

    if retdict[nodename][0]:
      newidlist = newidlist + retdict[nodename][1]

    else:
      faillist.append(nodename)

  seash_helper.print_vessel_errors(retdict)

  if len(newidlist) == 0:
    print "Could not add any new targets."
  else:
    print "Added targets: "+", ".join(newidlist)

  if len(seash_global_variables.targets['browsegood']) > 0:
    print "Added group 'browsegood' with "+str(len(seash_global_variables.targets['browsegood']))+" targets"



# browse [arg]
def browse_arg(input_dict, environment_dict):

  if environment_dict['currentkeyname'] == None or not seash_global_variables.keys[environment_dict['currentkeyname']]['publickey']:
    raise seash_exceptions.UserError("Error, must browse as an identity with a public key")


  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument input
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  type_list = command_key.split()


  try:
    # they are trying to only do some types of lookup...
    nodelist = advertise_lookup(seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'],lookuptype=type_list)
  except (AdvertiseError, TimeoutError), e:
    # print the error and return to the user.   Let them decide what to do.
    print "Error:",e
    print "Retry or check network connection."
    return
    


  # If there are no vessels for a user, the lookup may return ''
  for nodename in nodelist[:]:
    if nodename == '':
      nodelist.remove(nodename)

  # we'll output a message about the new keys later...
  newidlist = []

  faillist = []

  seash_global_variables.targets['browsegood'] = []

  print nodelist
  # currently, if I browse more than once, I look up everything again...
  retdict = seash_helper.contact_targets(nodelist,seash_helper.browse_target, environment_dict['currentkeyname'])

  # parse the output so we can print out something intelligible
  for nodename in retdict:

    if retdict[nodename][0]:
      newidlist = newidlist + retdict[nodename][1]

  seash_helper.print_vessel_errors(retdict)

  if len(newidlist) == 0:
    print "Could not add any new targets."
  else:
    print "Added targets: "+", ".join(newidlist)

  if len(seash_global_variables.targets['browsegood']) > 0:
    print "Added group 'browsegood' with "+str(len(seash_global_variables.targets['browsegood']))+" targets"




# genkeys filename                             -- creates keys
def genkeys_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename input
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  keylength = 1024
  # expand '~'
  fileandpath = os.path.expanduser(command_key)
  keyname = os.path.basename(fileandpath)
  pubkeyfn = fileandpath+'.publickey'
  privkeyfn = fileandpath+'.privatekey'

  # RepyV2's API does not allow us to truncate a file in-place.
  # Therefore, we need to make sure we're creating a new file, otherwise
  # the contents of the public/private key files that we create will be
  # corrupt.

  seash_helper.remove_files([pubkeyfn, privkeyfn])

  # do the actual generation (will take a while)
  newkeys = rsa_gen_pubpriv_keys(keylength)

  rsa_privatekey_to_file(newkeys[1],privkeyfn)
  rsa_publickey_to_file(newkeys[0],pubkeyfn)
  seash_global_variables.keys[keyname] = {'publickey':newkeys[0], 'privatekey':newkeys[1]}

  print "Created identity '"+keyname+"'"




# genkeys filename [len]
def genkeys_filename_len(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename input
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  # expand '~'
  fileandpath = os.path.expanduser(command_key)
  keyname = os.path.basename(fileandpath)
  pubkeyfn = fileandpath+'.publickey'
  privkeyfn = fileandpath+'.privatekey'


  # Iterates through the dictionary to retrieve the user's length argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  keylength = int(command_key)


  # do the actual generation (will take a while)
  newkeys = rsa_gen_pubpriv_keys(keylength)

  # RepyV2's API does not allow us to truncate a file in-place.
  # Therefore, we need to make sure we're creating a new file, otherwise
  # the contents of the public/private key files that we create will be
  # corrupt.

  seash_helper.remove_files([pubkeyfn, privkeyfn])

  rsa_privatekey_to_file(newkeys[1],privkeyfn)
  rsa_publickey_to_file(newkeys[0],pubkeyfn)
  seash_global_variables.keys[keyname] = {'publickey':newkeys[0], 'privatekey':newkeys[1]}

  print "Created identity '"+keyname+"'"




# genkeys filename [len] [as identity]
def genkeys_filename_len_as_identity(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename input
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  pubkeyfn = command_key+'.publickey'
  privkeyfn = command_key+'.privatekey'


  # Iterates through the dictionary to retrieve the user's length argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  keylength = int(command_key)


  # Iterates through the dictionary to retrieve the user's keyname argument
  while input_dict[command_key]['name'] is not 'keyname':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  keyname = command_key


  # do the actual generation (will take a while)
  newkeys = rsa_gen_pubpriv_keys(keylength)

  # RepyV2's API does not allow us to truncate a file in-place.
  # Therefore, we need to make sure we're creating a new file, otherwise
  # the contents of the public/private key files that we create will be
  # corrupt.

  seash_helper.remove_files([pubkeyfn, privkeyfn])

  rsa_privatekey_to_file(newkeys[1],privkeyfn)
  rsa_publickey_to_file(newkeys[0],pubkeyfn)
  seash_global_variables.keys[keyname] = {'publickey':newkeys[0], 'privatekey':newkeys[1]}

  print "Created identity '"+keyname+"'"




# genkeys filename [as identity]
def genkeys_filename_as_identity(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename input
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  keylength = 1024
  pubkeyfn = command_key + '.publickey'
  privkeyfn = command_key + '.privatekey'


  # Iterates through the dictionary to retrieve the user's keyname argument
  while input_dict[command_key]['name'] is not 'keyname':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  keyname = command_key

  # do the actual generation (will take a while)
  newkeys = rsa_gen_pubpriv_keys(keylength)

  # RepyV2's API does not allow us to truncate a file in-place.
  # Therefore, we need to make sure we're creating a new file, otherwise
  # the contents of the public/private key files that we create will be
  # corrupt.

  seash_helper.remove_files([pubkeyfn, privkeyfn])

  rsa_privatekey_to_file(newkeys[1],privkeyfn)
  rsa_publickey_to_file(newkeys[0],pubkeyfn)
  seash_global_variables.keys[keyname] = {'publickey':newkeys[0], 'privatekey':newkeys[1]}

  print "Created identity '"+keyname+"'"
  




# loadpub filename [as identity]                    -- loads a public key
def loadpub_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  # they typed 'loadpub foo.publickey'
  if command_key.endswith('.publickey'):
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    keyname = os.path.basename(fileandpath[:len('.publickey')])
    pubkeyfn = fileandpath
  else:
    # they typed 'loadpub foo'
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    keyname = os.path.basename(fileandpath)
    pubkeyfn = fileandpath+'.publickey'

  # load the key and update the table...
  pubkey = rsa_file_to_publickey(pubkeyfn)

  if keyname not in seash_global_variables.keys:
    seash_global_variables.keys[keyname] = {'publickey':pubkey, 'privatekey':None}
  else:
    seash_global_variables.keys[keyname]['publickey'] = pubkey

    # Check the keys, on error reverse the change and re-raise
    try:
      seash_helper.check_key_set(keyname)

    except:
      seash_global_variables.keys[keyname]['publickey'] = None
      raise




# loadpub keyname as identity
def loadpub_filename_as(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  # they typed 'loadpub foo.publickey'
  if command_key.endswith('.publickey'):
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    pubkeyfn = fileandpath
  else:
    # they typed 'loadpub foo'
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    pubkeyfn = fileandpath+'.publickey'

# Iterates through the dictionary to retrieve the user's keyname argument
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  keyname = command_key

  # load the key and update the table...
  pubkey = rsa_file_to_publickey(pubkeyfn)
  if keyname not in seash_global_variables.keys:
    seash_global_variables.keys[keyname] = {'publickey':pubkey, 'privatekey':None}
  else:
    seash_global_variables.keys[keyname]['publickey'] = pubkey

    # Check the keys, on error reverse the change and re-raise
    try:
      seash_helper.check_key_set(keyname)
    except:
      seash_global_variables.keys[keyname]['publickey'] = None
      raise




# loadpriv filename [as identity]                    -- loads a private key
def loadpriv_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  # they typed 'loadpriv foo.privatekey'
  if command_key.endswith('.privatekey'):
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    privkeyfn = fileandpath+'.privatekey'
    keyname = os.path.basename(fileandpath[:len('.privatekey')])
  else:
    # they typed 'loadpriv foo'
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    privkeyfn = fileandpath+'.privatekey'
    keyname = os.path.basename(fileandpath)


  # load the key and update the table...
  privkey = rsa_file_to_privatekey(privkeyfn)
  if keyname not in seash_global_variables.keys:
    seash_global_variables.keys[keyname] = {'privatekey':privkey, 'publickey':None}
  else:
    seash_global_variables.keys[keyname]['privatekey'] = privkey

    # Check the keys, on error reverse the change and re-raise
    try:
      seash_helper.check_key_set(keyname)
    except:
      seash_global_variables.keys[keyname]['privatekey'] = None
      raise
          



# loadpriv filename as identity
def loadpriv_filename_as(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  # they typed 'loadpriv foo.privatekey'
  if command_key.endswith('.privatekey'):
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    privkeyfn = fileandpath
  else:
    # they typed 'loadpriv foo'
    # handle '~'
    fileandpath = os.path.expanduser(command_key)
    privkeyfn = fileandpath+'.publickey'

  # Iterates through the dictionary to retrieve the user's keyname argument
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  keyname = command_key


  # load the key and update the table...
  privkey = rsa_file_to_privatekey(privkeyfn)
  if keyname not in seash_global_variables.keys:
    seash_global_variables.keys[keyname] = {'privatekey':privkey, 'publickey':None}
  else:
    seash_global_variables.keys[keyname]['privatekey'] = privkey

    # Check the keys, on error reverse the change and re-raise
    try:
      seash_helper.check_key_set(keyname)
    except:
      seash_global_variables.keys[keyname]['privatekey'] = None
      raise




# loadkeys filename [as identity]                    -- loads a private key
def loadkeys_keyname(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  if command_key.endswith('publickey') or command_key.endswith('privatekey'):
    print 'Warning: Trying to load a keypair named "'+command_key+'.publickey" and "'+command_key+'.privatekey"'


  # the user input may have a directory or tilde in it.   The key name 
  # shouldn't have either.
  fileandpath = os.path.expanduser(command_key)
  keyname = os.path.basename(fileandpath)
  privkeyfn = fileandpath+'.privatekey'
  pubkeyfn = fileandpath+'.publickey'




  # load the keys and update the table...
  try:
    privkey = rsa_file_to_privatekey(privkeyfn)
  except (OSError, IOError), e:
    raise seash_exceptions.UserError("Cannot locate private key '"+privkeyfn+"'.\nDetailed error: '"+str(e)+"'.")

  try:
    pubkey = rsa_file_to_publickey(pubkeyfn)
  except (OSError, IOError), e:
    raise seash_exceptions.UserError("Cannot locate private key '"+privkeyfn+"'.\nDetailed error: '"+str(e)+"'.")

  seash_global_variables.keys[keyname] = {'privatekey':privkey, 'publickey':pubkey}


  # Check the keys, on error reverse the change and re-raise
  try:
    seash_helper.check_key_set(keyname)
  except:
    del seash_global_variables.keys[keyname]
    raise




# loadkeys keyname as identity
def loadkeys_keyname_as(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  if command_key.endswith('publickey') or command_key.endswith('privatekey'):
    print 'Warning: Trying to load a keypair named "'+command_key+'.publickey" and "'+command_key+'.privatekey"'


  # the user input may have a directory or tilde in it.   The key name 
  # shouldn't have either.
  fileandpath = os.path.expanduser(command_key)
  privkeyfn = fileandpath+'.privatekey'
  pubkeyfn = fileandpath+'.publickey'
  

  # Iterates through the dictionary to retrieve the user's keyname argument
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  keyname = command_key


  # load the keys and update the table...
  try:
    privkey = rsa_file_to_privatekey(privkeyfn)
  except (OSError, IOError), e:
    raise seash_exceptions.UserError("Cannot locate private key '"+privkeyfn+"'.\nDetailed error: '"+str(e)+"'.")

  try:
    pubkey = rsa_file_to_publickey(pubkeyfn)
  except (OSError, IOError), e:
    raise seash_exceptions.UserError("Cannot locate private key '"+privkeyfn+"'.\nDetailed error: '"+str(e)+"'.")

  seash_global_variables.keys[keyname] = {'privatekey':privkey, 'publickey':pubkey}


  # Check the keys, on error reverse the change and re-raise
  try:
    seash_helper.check_key_set(keyname)
  except:
    del seash_global_variables.keys[keyname]
    raise



"""
 list               -- Update and display information about the vessels

 output looks similar to:
  ID Own                       Name     Status              Owner Information
  %1  *       128.208.3.173:1224:v5      Fresh                               
  %2  *        128.208.3.86:1224:v2      Fresh                               
  %3          234.17.98.23:53322:v5    Stopped               Chord experiment

"""
def list(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  # update information about the vessels...
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.list_or_update_target)

  for longname in retdict:

    if retdict[longname][0]:
      goodlist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if goodlist:
    print "%4s %3s %25s %10s %30s" % ('ID','Own','Name','Status','Owner Information')

  # walk through target to print instead of the good list so that the
  # names are printed in order...
  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:

    if longname in goodlist:  
      if seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'] == seash_global_variables.vesselinfo[longname]['ownerkey']:
        owner = '*'
      else:
        owner = ''
      print "%4s  %1s  %25s %10s %30s" % (seash_global_variables.vesselinfo[longname]['ID'],owner,seash_helper.fit_string(longname,25),seash_global_variables.vesselinfo[longname]['status'],seash_helper.fit_string(seash_global_variables.vesselinfo[longname]['ownerinfo'],30))

  # add groups for fail and good (if there is a difference in what nodes do)
  if goodlist and faillist:
    seash_global_variables.targets['listgood'] = goodlist
    seash_global_variables.targets['listfail'] = faillist
    print "Added group 'listgood' with "+str(len(seash_global_variables.targets['listgood']))+" targets and 'listfail' with "+str(len(seash_global_variables.targets['listfail']))+" targets"


  statusdict = {}
  # add status groups (if there is a difference in vessel state)
  for longname in goodlist:
    if seash_global_variables.vesselinfo[longname]['status'] not in statusdict:
      # create a list with this element...
      statusdict[seash_global_variables.vesselinfo[longname]['status']] = []
    statusdict[seash_global_variables.vesselinfo[longname]['status']].append(longname)

  if len(statusdict) > 1:
    print "Added group",
    for statusname in statusdict:
      seash_global_variables.targets['list'+statusname] = statusdict[statusname]
      print "'list"+statusname+"' with "+str(len(seash_global_variables.targets['list'+statusname]))+" targets",
    print
          

  
# reset                  -- Reset the vessel (clear the log and files and stop)
def reset(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  # reset the vessels...
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.reset_target)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  # and display it...
  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['resetgood'] = goodlist
    seash_global_variables.targets['resetfail'] = faillist
    print "Added group 'resetgood' with "+str(len(seash_global_variables.targets['resetgood']))+" targets and 'resetfail' with "+str(len(seash_global_variables.targets['resetfail']))+" targets"


  



# update
def update(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  # update information about the vessels...
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.list_or_update_target)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['updategood'] = goodlist
    seash_global_variables.targets['updatefail'] = faillist
    print "Added group 'updategood' with "+str(len(seash_global_variables.targets['updategood']))+" targets and 'updatefail' with "+str(len(seash_global_variables.targets['updatefail']))+" targets"

  


# savestate localfile -- Save current states to file (Added by Danny Y. Huang)
def savestate_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  # expand ~
  fileandpath = os.path.expanduser(command_key) 

  try:
    seash_helper.savestate(fileandpath, environment_dict['handleinfo'], 
                           environment_dict['host'], environment_dict['port'], 
                           environment_dict['expnum'], environment_dict['filename'], 
                           environment_dict['cmdargs'], environment_dict['defaulttarget'], 
                           environment_dict['defaultkeyname'], environment_dict['autosave'], 
                           environment_dict['currentkeyname'])
  except Exception, error:
    raise seash_exceptions.UserError("Error saving state: '" + str(error) + "'.")





# loadstate localfile -- Load states from file (Added by Danny Y. Huang)
def loadstate_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  # expand ~
  fileandpath = os.path.expanduser(command_key) 

  if not environment_dict['currentkeyname']:
    raise seash_exceptions.UserError("Specify the key name by first typing 'as [username]'.")

  # reading encrypted serialized states from file
  state_obj = open(fileandpath, 'r')
  cypher = state_obj.read()
  state_obj.close()

  try:
    # decrypt states
    statestr = rsa_decrypt(cypher, seash_global_variables.keys[environment_dict['currentkeyname']]['privatekey'])

    # deserialize
    state = serialize_deserializedata(statestr)
  except Exception, error:
    error_msg = "Unable to correctly parse state file. Your private "
    error_msg += "key may be incorrect."
    raise seash_exceptions.UserError(error_msg)

  # restore variables
  seash_global_variables.targets = state['targets']
  seash_global_variables.keys = state['keys']
  seash_global_variables.vesselinfo = state['vesselinfo']
  environment_dict['handleinfo'] = state['handleinfo']
  seash_global_variables.nextid = state['nextid']
  environment_dict['host'] = state['host']
  environment_dict['port'] = state['port']
  environment_dict['expnum'] = state['expnum']
  environment_dict['filename'] = state['filename']
  environment_dict['cmdargs'] = state['cmdargs'] 
  environment_dict['defaulttarget'] = state['defaulttarget']
  environment_dict['defaultkeyname'] = state['defaultkeyname']
  environment_dict['autosave'] = state['autosave']
  seash_global_variables.globalseashtimeout = state['globalseashtimeout']
  seash_global_variables.globaluploadrate = state['globaluploadrate']

  # Reload node handles. Rogue nodes are deleted from the original targets
  # and vesselinfo dictionaries.
  retdict = seash_helper.contact_targets(seash_global_variables.targets['%all'], seash_helper.reload_target, environment_dict['handleinfo'])

  reloadgood = []
  reloadfail = []

  for longname in retdict:
    if not retdict[longname][0]:
      reloadfail.append(longname)
    else:
      reloadgood.append(longname)
      
  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if reloadfail and reloadgood:
    seash_global_variables.targets['reloadgood'] = reloadgood
    seash_global_variables.targets['reloadfail'] = reloadfail
    print("Added group 'reloadgood' with " +str(len(seash_global_variables.targets['reloadgood'])) + \
            " targets and 'reloadfail' with " + str(len(seash_global_variables.targets['reloadfail'])) + " targets")

  if environment_dict['autosave']:
    print "Autosave is on."






# upload localfn (remotefn)   -- Upload a file 
def upload_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]


  # expand '~'
  fileandpath = os.path.expanduser(command_key)
  remotefn = os.path.basename(fileandpath)
  localfn = fileandpath


  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")


  # read the local file...
  fileobj = open(localfn,"r")
  filedata = fileobj.read()
  fileobj.close()


  # to prevent timeouts during file uploads on slow connections,
  # temporarily sets the timeout count on all vessels to be the
  # time needed to upload the file with the current globaluploadrate
  seash_helper.set_upload_timeout(filedata)


  # add the file to the vessels...
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.upload_target, remotefn, filedata)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['uploadgood'] = goodlist
    seash_global_variables.targets['uploadfail'] = faillist
    print "Added group 'uploadgood' with "+str(len(seash_global_variables.targets['uploadgood']))+" targets and 'uploadfail' with "+str(len(seash_global_variables.targets['uploadfail']))+" targets"


  # resets the timeout count on all vessels to globalseashtimeout
  seash_helper.reset_vessel_timeout()




# upload localfn remotefn
def upload_filename_remotefn(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  # expand '~'
  fileandpath = os.path.expanduser(command_key)
  localfn = fileandpath

  # Iterates through the dictionary to retrieve the user's argument input
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  remotefn = command_key


  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")


  # read the local file...
  fileobj = open(localfn,"r")
  filedata = fileobj.read()
  fileobj.close()


  # to prevent timeouts during file uploads on slow connections,
  # temporarily sets the timeout count on all vessels to be the
  # time needed to upload the file with the current globaluploadrate
  seash_helper.set_upload_timeout(filedata)


  # add the file to the vessels...
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.upload_target, remotefn, filedata)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['uploadgood'] = goodlist
    seash_global_variables.targets['uploadfail'] = faillist
    print "Added group 'uploadgood' with "+str(len(seash_global_variables.targets['uploadgood']))+" targets and 'uploadfail' with "+str(len(seash_global_variables.targets['uploadfail']))+" targets"


  # resets the timeout count on all vessels to globalseashtimeout
  seash_helper.reset_vessel_timeout()
  



# download remotefn (localfn) -- Download a file 
def download_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  # handle '~'
  fileandpath = os.path.expanduser(command_key)
  remotefn = os.path.basename(fileandpath)
  localfn = fileandpath

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")



  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.download_target,localfn,remotefn)

  writestring = ''

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
      # for output...
      writestring = writestring + retdict[longname][1]+ " "
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if writestring:
    print "Wrote files: "+writestring

  # add groups if needed...
  if goodlist and faillist:
    seash_global_variables.targets['downloadgood'] = goodlist
    seash_global_variables.targets['downloadfail'] = faillist
    print "Added group 'downloadgood' with "+str(len(seash_global_variables.targets['downloadgood']))+" targets and 'downloadfail' with "+str(len(seash_global_variables.targets['downloadfail']))+" targets"




# download remotefn localfn
def download_filename_localfn(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  remotefn = command_key

  # Iterates through the dictionary to retrieve the user's argument input
  while input_dict[command_key]['name'] is not 'args':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  # handle '~'
  fileandpath = os.path.expanduser(command_key)
  localfn = fileandpath

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")



  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.download_target,localfn,remotefn)

  writestring = ''

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
      # for output...
      writestring = writestring + retdict[longname][1]+ " "
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if writestring:
    print "Wrote files: "+writestring

  # add groups if needed...
  if goodlist and faillist:
    seash_global_variables.targets['downloadgood'] = goodlist
    seash_global_variables.targets['downloadfail'] = faillist
    print "Added group 'downloadgood' with "+str(len(seash_global_variables.targets['downloadgood']))+" targets and 'downloadfail' with "+str(len(seash_global_variables.targets['downloadfail']))+" targets"
  
  



# delete remotefn             -- Delete a file
def delete_remotefn(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  remotefn = command_key

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")


  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.delete_target, remotefn)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else: 
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # add groups if needed...
  if goodlist and faillist:
    seash_global_variables.targets['deletegood'] = goodlist
    seash_global_variables.targets['deletefail'] = faillist
    print "Added group 'deletegood' with "+str(len(seash_global_variables.targets['deletegood']))+" targets and 'deletefail' with "+str(len(seash_global_variables.targets['deletefail']))+" targets"




# cat remotefn  -- Print a file to the screen
def cat_filename(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  remotefn = os.path.basename(command_key)

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")



  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.cat_target,remotefn)


  # here is where I list it out...
  for longname in retdict:

    # True means it worked
    if retdict[longname][0]:
      print
      print "File '"+remotefn+"' on '"+longname+"': "
      print retdict[longname][1]
      print
      goodlist.append(longname)

    else:
      print
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  if goodlist and faillist:
    seash_global_variables.targets['catgood'] = goodlist
    seash_global_variables.targets['catfail'] = faillist
    print "Added group 'catgood' with "+str(len(seash_global_variables.targets['catgood']))+" targets and 'catfail' with "+str(len(seash_global_variables.targets['catfail']))+" targets"




  
  

  
# start file [args ...]  -- Start an experiment
def start_remotefn(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the repy version to run
  while input_dict[command_key]['name'] is not 'start':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  command = command_key

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  argstring = ' '.join([command_key])

  prog_platform = seash_helper.get_execution_platform(command,
    command_key)

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  # need to get the status, etc (or do I just try to start them all?)
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(
    seash_global_variables.targets[environment_dict['currenttarget']],
    seash_helper.start_target, argstring, prog_platform)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # add groups if needed...
  if goodlist and faillist:
    seash_global_variables.targets['startgood'] = goodlist
    seash_global_variables.targets['startfail'] = faillist
    print "Added group 'startgood' with "+str(len(seash_global_variables.targets['startgood']))+" targets and 'startfail' with "+str(len(seash_global_variables.targets['startfail']))+" targets"





# start file arg
def start_remotefn_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the repy version to run
  while input_dict[command_key]['name'] is not 'start':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  command = command_key

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  filename = command_key

  # Iterates through the dictionary to retrieve the user's argument input
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  arguments = command_key

  argstring = ' '.join([filename] + [arguments])

  prog_platform = seash_helper.get_execution_platform(command,
    filename)

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  # need to get the status, etc (or do I just try to start them all?)
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(
    seash_global_variables.targets[environment_dict['currenttarget']],
    seash_helper.start_target, argstring, prog_platform)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # add groups if needed...
  if goodlist and faillist:
    seash_global_variables.targets['startgood'] = goodlist
    seash_global_variables.targets['startfail'] = faillist
    print "Added group 'startgood' with "+str(len(seash_global_variables.targets['startgood']))+" targets and 'startfail' with "+str(len(seash_global_variables.targets['startfail']))+" targets"





# stop               -- Stop an experiment
def stop(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  # need to get the status, etc (or do I just try to stop them all?)
  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.stop_target)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)


  # add groups if needed...
  if goodlist and faillist:
    seash_global_variables.targets['stopgood'] = goodlist
    seash_global_variables.targets['stopfail'] = faillist
    print "Added group 'stopgood' with "+str(len(seash_global_variables.targets['stopgood']))+" targets and 'stopfail' with "+str(len(seash_global_variables.targets['stopfail']))+" targets"




# run file [args...]    -- Shortcut for upload a file and start
def run_localfn(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the repy version to run
  while input_dict[command_key]['name'] is not 'run':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  command = command_key

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
     input_dict = input_dict[command_key]['children']
     command_key = input_dict.keys()[0]

  # Handle '~' in file names
  fileandpath = os.path.expanduser(command_key)
  onlyfilename = os.path.basename(fileandpath)

  argstring = " ".join([onlyfilename])

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  prog_platform = seash_helper.get_execution_platform(command,
    onlyfilename)

  # read the local file...
  fileobj = open(fileandpath,"r")
  filedata = fileobj.read()
  fileobj.close()


  # to prevent timeouts during file uploads on slow connections, 
  # temporarily sets the timeout count on all vessels to be the  
  # time needed upload the file with the current globaluploadrate 
  seash_helper.set_upload_timeout(filedata) 


  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(
    seash_global_variables.targets[environment_dict['currenttarget']],
    seash_helper.run_target, onlyfilename, filedata, argstring,
    prog_platform)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['rungood'] = goodlist
    seash_global_variables.targets['runfail'] = faillist
    print "Added group 'rungood' with "+str(len(seash_global_variables.targets['rungood']))+" targets and 'runfail' with "+str(len(seash_global_variables.targets['runfail']))+" targets"


  # resets the timeout count on all vessels to globalseashtimeout 
  seash_helper.reset_vessel_timeout() 





# run file arg
def run_localfn_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the repy version to run
  while input_dict[command_key]['name'] is not 'run':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  command = command_key

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  # Handle '~' in file names
  fileandpath = os.path.expanduser(command_key)
  onlyfilename = os.path.basename(fileandpath)

  # Iterates down one more level to retrieve the argument string
  input_dict = input_dict[command_key]['children']
  command_key = input_dict.keys()[0]

  argstring = " ".join([onlyfilename] + [command_key])

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  prog_platform = seash_helper.get_execution_platform(command,
    onlyfilename)

  # read the local file...
  fileobj = open(fileandpath,"r")
  filedata = fileobj.read()
  fileobj.close()


  # to prevent timeouts during file uploads on slow connections, 
  # temporarily sets the timeout count on all vessels to be the  
  # time needed upload the file with the current globaluploadrate 
  seash_helper.set_upload_timeout(filedata) 


  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(
    seash_global_variables.targets[environment_dict['currenttarget']],
    seash_helper.run_target, onlyfilename, filedata, argstring,
    prog_platform)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['rungood'] = goodlist
    seash_global_variables.targets['runfail'] = faillist
    print "Added group 'rungood' with "+str(len(seash_global_variables.targets['rungood']))+" targets and 'runfail' with "+str(len(seash_global_variables.targets['runfail']))+" targets"


  # resets the timeout count on all vessels to globalseashtimeout 
  seash_helper.reset_vessel_timeout() 




#split resourcefn            -- Split off of each vessel another vessel
def split_resourcefn(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's filename
  while input_dict[command_key]['name'] is not 'filename':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  # expand ~
  fileandpath = os.path.expanduser(command_key)
  resourcefn = fileandpath

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  resourcefo = open(resourcefn)
  resourcedata = resourcefo.read()
  resourcefo.close() 


  faillist = []
  goodlist1 = []
  goodlist2 = []

  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.split_target,resourcedata)

  for longname in retdict:
    if retdict[longname][0]:
      newname1, newname2 = retdict[longname][1]
      goodlist1.append(newname1)
      goodlist2.append(newname2)
      print longname+" -> ("+newname1+", "+newname2+")"
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist1 and goodlist2 and faillist:
    seash_global_variables.targets['split1'] = goodlist1
    seash_global_variables.targets['split2'] = goodlist2
    seash_global_variables.targets['splitfail'] = faillist
    print "Added group 'split1' with "+str(len(seash_global_variables.targets['split1']))+" targets, 'split2' with "+str(len(seash_global_variables.targets['split2']))+" targets and 'splitfail' with "+str(len(seash_global_variables.targets['splitfail']))+" targets"
  elif goodlist1 and goodlist2:
    seash_global_variables.targets['split1'] = goodlist1
    seash_global_variables.targets['split2'] = goodlist2
    print "Added group 'split1' with "+str(len(seash_global_variables.targets['split1']))+" targets and 'split2' with "+str(len(seash_global_variables.targets['split2']))+" targets"

  


#join                        -- Join vessels on the same node
def join(input_dict, environment_dict):

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  if not environment_dict['currentkeyname'] or not seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'] or not seash_global_variables.keys[environment_dict['currentkeyname']]['privatekey']:
    raise seash_exceptions.UserError("Must specify an identity with public and private keys...")

  nodedict = {}
  skipstring = ''

  # Need to group vessels by node...
  for longname in seash_global_variables.targets[environment_dict['currenttarget']]:
    if seash_global_variables.keys[environment_dict['currentkeyname']]['publickey'] != seash_global_variables.vesselinfo[longname]['ownerkey']:
      skipstring = skipstring + longname+" "
      continue

    nodename = seash_global_variables.vesselinfo[longname]['IP']+":"+str(seash_global_variables.vesselinfo[longname]['port'])

    if nodename not in nodedict:
      nodedict[nodename] = []

    nodedict[nodename].append(longname)

  # if we skip nodes, explain why
  if skipstring:
    print "Skipping "+skipstring+" because the current identity is not the owner."
    print "If you are trying to join vessels with different owners, you need"
    print "to change ownership to the same owner first"


  faillist = []
  goodlist = []

  retdict = seash_helper.contact_targets(nodedict.keys(),seash_helper.join_target,nodedict)

  for nodename in retdict:

    if retdict[nodename][0]:
      print retdict[nodename][1][0],"<- ("+", ".join(nodedict[nodename])+")"
      goodlist = goodlist + nodedict[nodename]
    else:
      if retdict[nodename][1]:
        faillist = faillist + nodedict[nodename]
      # Nodes that I only have one vessel on don't get added to a list...
  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['joingood'] = goodlist
    seash_global_variables.targets['joinfail'] = faillist
    print "Added group 'joingood' with "+str(len(seash_global_variables.targets['joingood']))+" targets and 'joinfail' with "+str(len(seash_global_variables.targets['joinfail']))+" targets"
  elif goodlist:
    seash_global_variables.targets['joingood'] = goodlist
    seash_global_variables.targets['joinfail'] = faillist
    print "Added group 'joingood' with "+str(len(seash_global_variables.targets['joingood']))+" targets"




###Set Commands###

# set                 -- Changes the state of the targets (use 'help set')
def set(input_dict, environment_dict):
  pass
        




# set owner identity        -- Change a vessel's owner
def set_owner_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  newowner = command_key

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  if newowner not in seash_global_variables.keys:
    raise seash_exceptions.UserError("Unknown identity: '"+newowner+"'")

  if not seash_global_variables.keys[newowner]['publickey']:
    raise seash_exceptions.UserError("No public key for '"+newowner+"'")

  faillist = []
  goodlist = []
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.setowner_target,newowner)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)


  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['ownergood'] = goodlist
    seash_global_variables.targets['ownerfail'] = faillist
    print "Added group 'ownergood' with "+str(len(seash_global_variables.targets['ownergood']))+" targets and 'ownerfail' with "+str(len(seash_global_variables.targets['ownerfail']))+" targets"
  




# set autosave [ on | off ] -- Set whether SeaSH automatically saves the last state.
def set_autosave_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  if command_key == 'on':
    environment_dict['autosave'] = True
  elif command_key == 'off':
    environment_dict['autosave'] = False
  else:
    raise seash_exceptions.UserError("Usage: set autosave [ on | off (default)]")





# set advertise [ on | off ] -- Change advertisement of vessels
def set_advertise_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  if command_key == 'on':
    newadvert = True
  elif command_key == 'off':
    newadvert = False
  else:
    raise seash_exceptions.UserError("Usage: set advertise [ on | off ]")

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")


  faillist = []
  goodlist = []
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.setadvertise_target,newadvert)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['advertisegood'] = goodlist
    seash_global_variables.targets['advertisefail'] = faillist
    print "Added group 'advertisegood' with "+str(len(seash_global_variables.targets['advertisegood']))+" targets and 'advertisefail' with "+str(len(seash_global_variables.targets['advertisefail']))+" targets"
  
    

  

# set ownerinfo [ data ... ]    -- Change owner information for the vessels
def set_ownerinfo_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  newdata = command_key

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")

  faillist = []
  goodlist = []
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.setownerinformation_target,newdata)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)
  seash_helper.print_vessel_errors(retdict)


  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['ownerinfogood'] = goodlist
    seash_global_variables.targets['ownerinfofail'] = faillist
    print "Added group 'ownerinfogood' with "+str(len(seash_global_variables.targets['ownerinfogood']))+" targets and 'ownerinfofail' with "+str(len(seash_global_variables.targets['ownerinfofail']))+" targets"


  

# set users [ identity ... ]  -- Change a vessel's users
def set_users_arg(input_dict, environment_dict):
  userkeys = []

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  userlist = command_key.split()

  for identity in userlist:
    if identity not in seash_global_variables.keys:
      raise seash_exceptions.UserError("Unknown identity: '"+identity+"'")

    if not seash_global_variables.keys[identity]['publickey']:
      raise seash_exceptions.UserError("No public key for '"+identity+"'")

    userkeys.append(rsa_publickey_to_string(seash_global_variables.keys[identity]['publickey']))
  # this is the format the NM expects...
  userkeystring = '|'.join(userkeys)

  if not environment_dict['currenttarget']:
    raise seash_exceptions.UserError("Must specify a target")


  faillist = []
  goodlist = []
  retdict = seash_helper.contact_targets(seash_global_variables.targets[environment_dict['currenttarget']],seash_helper.setusers_target,userkeystring)

  for longname in retdict:
    if retdict[longname][0]:
      goodlist.append(longname)
    else:
      faillist.append(longname)

  seash_helper.print_vessel_errors(retdict)

  # update the groups
  if goodlist and faillist:
    seash_global_variables.targets['usersgood'] = goodlist
    seash_global_variables.targets['usersfail'] = faillist
    print "Added group 'usersgood' with "+str(len(seash_global_variables.targets['usersgood']))+" targets and 'usersfail' with "+str(len(seash_global_variables.targets['usersfail']))+" targets"
  
    


# set timeout count  -- Sets the time that seash is willing to wait on a node
def set_timeout_arg(input_dict, environment_dict):

  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  # I need to set the timeout for new handles...
  try:
    seash_global_variables.globalseashtimeout = int(command_key)
  except ValueError:
    raise seash_exceptions.UserError("The timeout value must be a number")

  # let's reset the timeout for existing handles...
  for longname in seash_global_variables.vesselinfo:
    thisvesselhandle = seash_global_variables.vesselinfo[longname]['handle']
    thisvesselhandledict = nmclient_get_handle_info(thisvesselhandle)
    thisvesselhandledict['timeout'] = seash_global_variables.globalseashtimeout
    nmclient_set_handle_info(thisvesselhandle,thisvesselhandledict)


          
# set uploadrate speed     -- Sets the speed seash will modify the timeout count with when uploading files
def set_uploadrate_arg(input_dict, environment_dict):

  try:
    command_key = input_dict.keys()[0]

    # Iterates through the dictionary to retrieve the user's argument
    while input_dict[command_key]['name'] is not 'args':
      input_dict = input_dict[command_key]['children']
      command_key = input_dict.keys()[0]

    seash_global_variables.globaluploadrate = int(command_key)
  except ValueError:
    raise seash_exceptions.UserError("The speed value must be a number (in bytes per second)")


# set showparse [on | off] -- Toggles display of the parsed commandline input
def set_showparse_args(input_dict, environment_dict):
  command_key = input_dict.keys()[0]

  # Iterates through the dictionary to retrieve the user's argument
  while input_dict[command_key]['name'] is not 'args':
    input_dict = input_dict[command_key]['children']
    command_key = input_dict.keys()[0]

  command_key = command_key.lower()
  if command_key == 'on':
    environment_dict['showparse'] = True
  elif command_key == 'off':
    environment_dict['showparse'] = False
  else:
    raise seash_exceptions.UserError("Value can be either 'on' or 'off'") 


# enable modulename
def enable_module(input_dict, environment_dict):
  # Enables an installed module.

  # Get the modulename
  dict_mark = input_dict
  try:
    command = dict_mark.keys()[0]
    while dict_mark[command]['name'] != 'modulename':
      dict_mark = input_dict[command]['children']
      command = dict_mark.keys()[0]
    modulename = command
  except IndexError:
    raise seash_exceptions.UserError("Error, command requires a modulename")

  try:
    seash_modules.enable(seash_dictionary.seashcommanddict, modulename)
  except seash_exceptions.ModuleConflictError, e:
    print "Module cannot be imported due to the following conflicting command:"
    print str(e)


# disable modulename
def disable_module(input_dict, environment_dict):
  # Disables an enabled module.

  # Get the modulename
  dict_mark = input_dict
  try:
    command = dict_mark.keys()[0]
    while dict_mark[command]['name'] != 'modulename':
      dict_mark = input_dict[command]['children']
      command = dict_mark.keys()[0]
    modulename = command
  except IndexError:
    raise seash_exceptions.UserError("Error, command requires a modulename")

  seash_modules.disable(seash_dictionary.seashcommanddict, modulename)

# modulehelp modulename
def print_module_help(input_dict, environment_dict):
  # Prints the helptext for a module

  # Get the modulename
  dict_mark = input_dict
  try:
    command = dict_mark.keys()[0]
    while dict_mark[command]['name'] != 'modulename':
      dict_mark = input_dict[command]['children']
      command = dict_mark.keys()[0]
    modulename = command
  except IndexError:
    raise seash_exceptions.UserError("Error, command requires a modulename")

  # Is this module installed?
  if not modulename in seash_modules.module_data:
    raise seash_exceptions.UserError("Module is not installed.")

  print seash_modules.module_data[modulename]['help_text']

  # Now, print out all the commands under this module
  print "Commands in this module:"
  print '\n'.join(seash_helper.get_commands_from_commanddict(seash_modules.module_data[modulename]['command_dict']))
  


# show modules
def list_all_modules(input_dict, environment_dict):
  # Lists all modules and their status.
  print "Enabled Modules:"
  print ", ".join(seash_modules.get_enabled_modules())
  print
  print "Installed Modules:"

  # Now print the URLs...
  # Output format:
  # modulename - URL not available  # URL is set to None
  # modulename - https://seattle.poly.edu/seashplugins/modulename/ # URL is set
  for module in seash_modules.module_data:
    print module, '-',
    if seash_modules.module_data[module]['url'] is None:
      print "Install URL not available"
    else:
      print seash_modules.module_data[module]['url']
  print



# exit
def exit(input_dict, environment_dict):
  sys.exit()
