From tk at edam.speech.cs.cmu.edu Sun Dec 10 20:15:59 2006 From: tk at edam.speech.cs.cmu.edu (tk@edam.speech.cs.cmu.edu) Date: Sun, 10 Dec 2006 20:15:59 -0500 Subject: [RavenclawDev 203] [1] src: Message-ID: <200612110115.kBB1Fx4t003965@edam.speech.cs.cmu.edu> An HTML attachment was scrubbed... URL: http://mailman.srv.cs.cmu.edu/pipermail/ravenclaw-developers/attachments/20061210/e0f006cb/attachment-0001.html -------------- next part -------------- Added: src/basic_process_monitor.py =================================================================== --- src/basic_process_monitor.py (rev 0) +++ src/basic_process_monitor.py 2006-12-11 01:15:58 UTC (rev 1) @@ -0,0 +1,778 @@ +# This file (c) Copyright 1998 - 2002 The MITRE Corporation +# +# This file is part of the Galaxy Communicator system. It is licensed +# under the conditions described in the file LICENSE in the root +# directory of the Galaxy Communicator system. + +import os, string, re, tempfile + +if os.name == 'posix': + import unix_pm_core + from unix_pm_core import _MAYBE_ALIVE, _DEFINITELY_ALIVE, _DEFINITELY_DEAD + pm_core = unix_pm_core +elif os.name in ['dos', 'nt']: + import win_pm_core + from win_pm_core import _MAYBE_ALIVE, _DEFINITELY_ALIVE, _DEFINITELY_DEAD + pm_core = win_pm_core +else: + raise "Can't run process monitor on", os.name + +# This class controls the subprocess. + +# This class has the following basic behavior: +# PROCESS_TITLE: or -T is the title +# PAUSE: or --sleep is how long to sleep after startup +# PROCESS_KEEP_ALIVE: --keep_alive is whether to restart after shutdown +# PROCESS: or -c is the command line +# PROCESS_WORKDIR: or --workdir is the working directory of a process (defaults to current directory) + +# Tk has --input_line, --input_return, --start, --open + +# 5-tuples are (config_file_entry, cmdline, needs arg, method name, block starter). + +# ONE BACKWARD COMPATIBILITY GLITCH: for PROCESS:, the keyword +# STARTS the block, while the command line ENDS the block. All the +# others optionally start the block in the command line, while +# they can't in the configuration file case. Sorry about that. +# See the MustEndBlock() method on ProcessContainerSet. + +# For run_test_suite.py, ProcessContainer needs to be able to +# stand alone. + +ProcessContainerError = "ProcessContainerError" + +class ProcessContainer(pm_core.ProcessContainer): + config_table = [("PROCESS_TITLE:", "-T", 1, "AddTitle", 0), + ("PROCESS_WORKDIR:", "--workdir", 1, "AddWorkDir", 0), + ("PAUSE:", "--sleep", 1, "AddPause", 0), + ("PROCESS_KEEP_ALIVE:", "--keep_alive", 0, + "AddKeepAlive", 0), + ("PROCESS:", "-c", 1, "AddCommandLine", 1)] + + def __init__(self, process_set = None): + if process_set is not None: + process_set.AddProcess(self) + self.process_set = process_set + self.title = "" + self.workdir = "." + self.pause = 0 + self.keepalive = 0 + self.cmdline = None + # Doing away with the file descriptor objects. + self._SetupHandles() + self.alive = 0 + self.history_buffer = "" + self.error_buffer = "" + self.interleave_error = 1 + + # Configuration methods. + + def AddTitle(self, title): + if self.title: + raise ConfigurationError, "title already specified" + self.title = title + + def AddPause(self, pause): + if self.pause: + raise ConfigurationError, "pause already specified" + try: + self.pause = string.atoi(pause) + except: + raise ConfigurationError, \ + ("bad pause specification `%s'" % pause) + + def AddKeepAlive(self): + self.keepalive = 1 + + def AddCommandLine(self, command_line): + if self.process_set is not None: + env = self.process_set.process_environment + if not env.suppress_expansions: + print("command_line: `%s'" % command_line) + command_line = env.ExpandFile(command_line) + print("expanded command_line: `%s'" % command_line) + if self.cmdline: + raise ConfigurationError, "command line already specified" + self.cmdline = command_line + + def AddWorkDir(self, workdir): + env = self.process_set.process_environment + if not env.suppress_expansions: + workdir = env.ExpandFile(workdir) + self.workdir = workdir + + # Runtime behavior. + + def ConfigDone(self): + if not self.cmdline: + raise ConfigurationError, "no command line" + + def _SetupHandles(self): + self.in_handle = None + self.err_handle = None + self.out_handle = None + self.child_handle = None + + def KeepAlive(self): + self.keepalive = 1 + + def LetDie(self): + self.keepalive = 0 + + def UpdateIdle(self): + pass + + def ConsiderShutdown(self): + if not (self.in_handle or self.err_handle): + self.Shutdown() + + def ConsiderRestart(self): + if self.keepalive: + # If we're supposed to keep this process alive, + # then restart the damn thing. + self.Restart() + + def WriteHistory(self, str, fd): + # Keep everything yourself. + if (fd is not None) and (fd == self.err_handle): + self.error_buffer = self.error_buffer + str + self.history_buffer = self.history_buffer + str + + def ClearHistory(self): + self.history_buffer = "" + self.error_buffer = "" + + def ReadCallback(self, handle, num_bytes): + # num_bytes should be -1 when you can read as + # much as you want. + global _DEFINITELY_ALIVE, _MAYBE_ALIVE, _DEFINITELY_DEAD + status, data = self._ReadBytes(handle, num_bytes) + if status == _DEFINITELY_ALIVE: + if data: + self.WriteHistory(data, handle) + self.UpdateIdle() + elif ((status == _MAYBE_ALIVE) and self.Dead()) or \ + (status == _DEFINITELY_DEAD): + # If it might have been alive, there may be data. + if data: + self.WriteHistory(data, handle) + # Try one last read, just in case something + # was written before it was definitely dead. + final_status, final_data = self._ReadBytes(handle, -1) + if final_data: + self.WriteHistory(final_data, handle) + self._ShutdownHandle(handle) + self.ConsiderShutdown() + self.ConsiderRestart() + + def WriteCallback(self, handle): + raise ProcessContainerError, "not implemented" + + def _ShutdownHandle(self, handle): + if handle == self.in_handle: + self._ForceShutdown(handle) + self.in_handle = None + elif handle == self.out_handle: + self._ForceShutdown(handle) + self.out_handle = None + elif handle == self.err_handle: + self._ForceShutdown(handle) + self.err_handle = None + + def _ShutdownHandles(self): + if self.in_handle is not None: + self._ShutdownHandle(self.in_handle) + if self.out_handle is not None: + self._ShutdownHandle(self.out_handle) + if self.err_handle is not None: + self._ShutdownHandle(self.err_handle) + + def Shutdown(self, really_kill = 1): + # Unmonitor the output, and set the pause button + # to "resting" state. + # Returns the child pid of the killed element. + # If really_kill is 1, make sure it's dead, by sleeping + # for a brief time and then really killing it + # if it's not dead. + # Return the handle of the process if it's still alive. + self._ShutdownHandles() + p = self.child_handle + if self.child_handle: + # It may already be dead... + p = self._ShutdownSubprocess(self.child_handle, really_kill) + self.child_handle = None + self.WriteHistory("======================================\n", None) + self.alive = 0 + return p + + def Dead(self): + if not self.alive: + return 1 + return self._CheckDead(self.child_handle) + + # In order to interleave stdout and stderr better, I've + # redirected stderr to stdout in the subshell. The problem is + # that under this configuration, the shell does not return + # a valid fd for the error stream. So I need not to monitor it. + + def MonitorOutput(self): + # I need this for the child classes, I think. + pass + + def Restart(self): + self.Shutdown() + # Don't buffer anything! If you do, the filehandler + # stuff won't work... + cmdline = self.GetCmdlineForExecution() + # Restart may fail. In Unix, this will just print + # something to stderr of an apparently live subprocess, + # but in Windows, it will raise an error. This is + # because we're running a shell in one case, but not + # in the other. + # Problem: we have to make sure that the right things + # happen when we get a failure. In particular, the + # folks built on top of this (e.g., the process monitor + # itself) assumes restart was successful, and that + # failure and shutdown will be processed as a subsequent + # read. After careful consideration, I decided to + # prepare fake handles. + info = self._RestartChildProcess(self.workdir, cmdline, self.interleave_error) + self.in_handle, self.out_handle, self.err_handle, self.child_handle = info + self.MonitorOutput() + self.alive = 1 + + def GetCmdlineForDisplay(self): + return self.cmdline + + def GetCmdlineForExecution(self): + cmdline = self.cmdline + if self.process_set is not None: + env = self.process_set.process_environment + if not env.initialized: + env.Initialize() + # If expansions are suppressed, the command line + # needs to be expanded when it's executed. + if env.suppress_expansions or env.tempfiles_created: + cmdline = env.ExpandFile(self.cmdline) + return cmdline + + def SetCmdline(self, str): + if self.process_set is not None: + env = self.process_set.process_environment + if not env.suppress_expansions: + str = env.ExpandFile(str) + self.cmdline = str + + def WriteString(self, str): + if self.out_handle: + self._WriteString(self.out_handle, str) + else: + self.WriteHistory("[no process]\n", None) + + def TokenizeCmdlineArgs(self, args): + in_double_quote = 0 + in_single_quote = 0 + in_escape = 0 + cur_token = [] + cmdline_args = [] + # Don't forget, we also need to compute escapes... + for c in args: + if in_escape: + cur_token.append(c) + in_escape = 0 +# elif c == "\\": +# in_escape = 1 + elif in_double_quote: + if c == '"': + in_double_quote = 0 + else: + cur_token.append(c) + elif in_single_quote: + if c == "'": + in_single_quote = 0 + else: + cur_token.append(c) + elif c == "'": + in_single_quote = 1 + elif c == '"': + in_double_quote = 1 + elif c in string.whitespace: + if cur_token: + cmdline_args.append(string.join(cur_token, "")) + cur_token = [] + else: + cur_token.append(c) + if cur_token: + cmdline_args.append(string.join(cur_token, "")) + return cmdline_args + +# This class controls a set of processes. It can be +# subclassed as an individual test (for the test harness) +# or a process monitor window (for the interactive +# controller). + +class ProcessContainerSet(pm_core.ProcessContainerSet): + config_table = [("TITLE:", "--master_title", 1, "AddTitle", 1), + ("REQUIRES:", None, 1, "AddRequirements", 0)] + + def __init__(self, environment = None): + self.processes = [] + if environment is not None: + environment.AddProcessSet(self) + self.process_environment = environment + self.title = "" + self.requirements = [] + + def AddProcess(self, process): + self.processes.append(process) + + def AddTitle(self, title): + self.title = title + + def AddRequirements(self, remainder): + # Requirements can now have values. + reqs = string.split(remainder) + for r in reqs: + res = string.split(r, "=", 1) + if len(res) == 2: + self.requirements.append((res[0], res[1])) + else: + self.requirements.append((res[0], None)) + + def UnmetRequirements(self): + # Return the missing requirements + failed = [] + for req, value in self.requirements: + if string.upper(req) not in self.process_environment.requirements_met: + failed.append(req) + elif (value is not None) and \ + (self.process_environment.ExpandFile("$"+string.upper(req)) != value): + failed.append(req+"="+value) + return failed + + def CanStartBlock(self, config_entry, from_cmdline): + # Anything can start a process block from the command line. + if from_cmdline: + return 1 + else: + return config_entry[-2] + + def MustStartBlock(self, config_entry, from_cmdline): + # Nothing MUST start a block on the command line. + if from_cmdline: + return 0 + else: + return config_entry[-2] + + def MustEndBlock(self, config_entry, from_cmdline): + # However, on the command line, -c must end a block. + # See the backward compatibiliy notes for the ProcessContainer class. + if from_cmdline: + return config_entry[1] == "-c" + else: + return 0 + + def Run(self): + some_alive = 1 + while some_alive: + p_info = [] + for p in self.processes: + if p.alive: + if p.in_handle is not None: + p_info.append((p, p.in_handle)) + if p.err_handle is not None: + p_info.append((p, p.err_handle)) + if not p_info: + break + else: + res_info = self._CheckInput(p_info) + for p, handle, num_bytes in res_info: + p.ReadCallback(handle, num_bytes) + + def EnforceShutdown(self, process_handles): + self._EnforceShutdown(process_handles, really_kill = 1) + + def ExecutableExists(self, executable): + return self._ExecutableExists(executable) + + +# This class controls a set of process sets. This +# contains the global keyword/value substitution environment. +# It also figures out how to process the command line and +# configuration file. + +import getopt + +ConfigurationError = "ConfigurationError" + +# INCLUDE: is also an option. + +class ProcessEnvironment(pm_core.ProcessEnvironment): + + config_table = [("EXPAND:", None, 1, "AddDirectoryExpansion", 0), + ("TEMPFILE:", None, 1, "AddTempFile", 0), + ("ARGUMENTS:", None, 1, "AddArguments", 0), + ("SUPPRESS_EXPANSIONS:", None, 0, "SuppressExpansions", 0)] + + def __init__(self, container_set_class = ProcessContainerSet, + container_class = ProcessContainer): + self.container_set_class = container_set_class + self.container_class = container_class + self.expansions = [] + self.process_sets = [] + self.config_choice = None + self.file = "" + self.suppress_expansions = 0 + self.files_to_remove = [] + self.initialized = 0 + self.tempfiles_created = 0 + self.num_conforming_process_sets = 0 + + self.AddDirectoryExpansion("$GC_HOME " + os.environ["GC_HOME"]) + archos = self.ComputeArchos(os.environ["GC_HOME"]) + self.AddDirectoryExpansion("$ARCHOS " + archos) + # Finally, I want to add the OSTYPE, so I can distinguish + # coarsely between platform types. Values will be + # values of os.name, e.g., posix, nt. + self.AddDirectoryExpansion("$OSTYPE " + os.name) + # Add config file data. We're now generating a values + self.requirements_met = ["GC_HOME", "ARCHOS", "OSTYPE"] + # file, which means we don't have to parse the Makefile. + try: + value_fp = open(os.path.join(os.environ["GC_HOME"], "templates", archos, "config.values"), "r") + for line in value_fp.readlines(): + l = string.strip(line) + if l and l[0] != "#" and (len(string.split(line)) > 1): + # Don't record it if there's no value. This can happen + # in config.make. + self.AddDirectoryExpansion("$"+l) + self.requirements_met.append(string.upper(string.split(l)[0])) + except IOError: + pass + + def ComputeArchos(self, gc_home): + return self._ComputeArchos(gc_home) + + def DigestCommandLine(self, command_line, + accept_args = 1, accept_config_file = 1): + expanded_config = self._AssembleConfiguration() + optlist, from_cmdline = self._DigestConfiguration(command_line, + expanded_config, + accept_args, + accept_config_file) + self._Configure(optlist, from_cmdline, expanded_config) + for s in self.process_sets: + for p in s.processes: + p.ConfigDone() + for p_set in self.process_sets: + if not p_set.UnmetRequirements(): + self.num_conforming_process_sets = 1 + self.num_conforming_process_sets + p_set.meets_requirements = 1 + else: + p_set.meets_requirements = 0 + + # Config table method. + + def AddDirectoryExpansion(self, instructions): + # SAM 11/30/01: Special case where instructions + # of length 1 generate a temp file which is + # instantiated at the beginning and removed + # at the end. + # SAM 7/23/02: I've decided to allow values + # with whitespace. + l = string.split(instructions, None, 1) + # Also, make sure that when you record the expansion, + # YOU USE THE PREVIOUS EXPANSIONS. + if len(l) > 2: + raise ConfigurationError, "Ill-formed EXPAND: directive" + elif len(l) == 2: + self._IAddDirectoryExpansion(l[0], l[1]) + else: + self.expansions.append((l[0], "")) + + def AddTempFile(self, instructions): + l = string.split(instructions, None, 1) + if len(l) != 1: + raise ConfigurationError, "Ill-formed TEMPFILE: directive" + self.expansions.append((l[0], None)) + + # For tempfiles. + + def Initialize(self): + if self.initialized: return + new_exps = [] + for key, val in self.expansions: + if val is None: + self.tempfiles_created = 1 + f = tempfile.mktemp() + self.files_to_remove.append(f) + new_exps.append((key, self._CleanupExpansion(f))) + else: + new_exps.append((key, val)) + self.expansions = new_exps + self.initialized = 1 + + def Cleanup(self): + for f in self.files_to_remove: + try: + os.unlink(f) + except: pass + self.files_to_remove = [] + + def _IAddDirectoryExpansion(self, entry, segment): + self.expansions.append((entry, self.ExpandFile(segment))) + + def ExpandFile(self, file_string): + for key, val in self.expansions: + # val is None if we haven't generated a + # temporary file yet. + if val is not None: + file_string = string.replace(file_string, key, val) + return file_string + + def AddProcessSet(self, set): + self.process_sets.append(set) + + def SuppressExpansions(self): + # If expansions are suppressed, then the command which + # is shown will be expanded only when we execute. + # Otherwise, it's expanded before it's ever saved + # as a command line. + self.suppress_expansions = 1 + + def AddArguments(self, instructions): + # If there are args, and they don't match the + # available remaining arguments, then it's an error. + # No optional arguments permitted. + config_args = string.split(instructions) + # Well, we've already recorded the arguments in the + # directory expansion table. So for each argument, + # we should be able to find an entry. If we + # can't, do usage and raise an error. + num_args = len(config_args) + j = 1 + while j <= num_args: + num_str = "$%d" % j + expansion = self.ExpandFile(num_str) + if expansion == num_str: + # No expansion. + raise ConfigurationError, ("missing some config args: -- %s" % instructions) + # Otherwise, record the expansion. + self._IAddDirectoryExpansion(("$"+config_args[j - 1]), expansion) + j = j + 1 + # Now, see if there's an expansion for the next index. + num_str = "$%d" % j + expansion = self.ExpandFile(num_str) + if expansion != num_str: + print "Ignoring extra config arguments" + + # Configuration processing. + + def _AssembleConfiguration(self): + expanded_configs_dict = {} + self._AddConfigTable(self.__class__, expanded_configs_dict) + self._AddConfigTable(self.container_set_class, expanded_configs_dict) + self._AddConfigTable(self.container_class, expanded_configs_dict) + return expanded_configs_dict + + def _AddConfigTable(self, config_class, expanded_configs): + for c in config_class.config_table: + if c[0] is not None: + expanded_configs[c[0]] = c + (config_class,) + if c[1] is not None: + expanded_configs[c[1]] = c + (config_class,) + + def _DigestConfiguration(self, command_line, expanded_config_dict, + accept_args = 1, accept_config_file = 1): + short_args = "" + long_args = [] + # Just look at the entries which are the command line. + for key, e in expanded_config_dict.items(): + # e[1] is the command line. + if e[1] is None: continue + if key != e[1]: continue + if (len(key) == 2) and \ + (key[0] == "-") and \ + (key[1] != "-"): + # Short arg. + if e[2]: short_args = short_args + key[1] + ":" + else: short_args = short_args + key[1] + elif (len(key) > 2) and \ + (key[0:2] == "--"): + # Long arg. + if e[2]: long_args.append(key[2:] + "=") + else: long_args.append(key[2:]) + else: + print "Unknown arg type %s; skipping." % key + try: + optlist, args = getopt.getopt(command_line, short_args, long_args) + except getopt.error: + raise ConfigurationError, "error parsing command line" + if optlist and args: + raise ConfigurationError, "can't have both command line arguments and configuration file" + if optlist: + if (not accept_args): + raise ConfigurationError, "can't use command line arguments" + else: + return optlist, 1 + elif args: + if (not accept_config_file): + raise ConfigurationError, "can't use configuration file" + # Now, we "parse" the file. + if '--' in args: + i = args.index('--') + # Add these things as directory expansions. + j = 1 + for elt in args[i+1:]: + self._IAddDirectoryExpansion(("$%d" % j), "'"+self._CleanupExpansion(elt)+"'") + j = j + 1 + args = args[:i] + if len(args) == 2: + self.config_choice = args[1] + self.file = args[0] + return self._DigestConfigurationFile(self.file), 0 + else: + raise ConfigurationError, "neither args nor configuration file" + + def _DigestConfigurationFile(self, test_file): + try: + fp = open(test_file, "r") + except: + raise ConfigurationError, "couldn't find configuration file "+ test_file + lines = fp.readlines() + fp.close() + directives = [] + for line in lines: + l = string.strip(line) + if l and l[0] != '#': + # Empty lines don't count, # is comment character. + try: + d = string.split(l, None, 1) + # Handle INCLUDE: + if d[0] == "INCLUDE:": + if len(d) != 2: + raise ConfigurationError, "INCLUDE: requires exactly one argument" + else: + directives = directives + self._DigestConfigurationFile(self.ExpandFile(d[1])) + elif len(d) == 1: + directives.append((d[0], "")) + else: + directives.append((d[0], d[1])) + print("appending `%s', `%s'" % (d[0], d[1])) + except ConfigurationError, msg: + raise ConfigurationError, msg + except: + raise ConfigurationError, ("ill-formed line `%s'" % l) + return directives + + def CanStartBlock(self, config_entry, from_cmdline): + # From the command line, anything can start a + # set block. + if from_cmdline: + return 1 + else: + return (config_entry[-1] == self.container_set_class) and \ + (config_entry[-2] == 1) + + def MustStartBlock(self, config_entry, from_cmdline): + # From the command line, nothing MUST start a set block. + if from_cmdline: + return 0 + else: + return (config_entry[-1] == self.container_set_class) and \ + (config_entry[-2] == 1) + + def _Configure(self, optlist, from_cmdline, config_dict): + # So now we work our way through the optlist. + cur_set = None + cur_container = None + for key, val in optlist: + cur_set, cur_container = self._ConfigureOption(cur_set, cur_container, key, val, from_cmdline, config_dict) + + def _ConfigureOption(self, cur_set, cur_container, + key, val, from_cmdline, config_dict): + # We ignore directives we don't know about. + try: + config_entry = config_dict[key] + except: + return cur_set, cur_container + config_key, cmd_key, need_arg, \ + mname, start_block, kclass = config_entry + kmethod = None + if kclass is self.__class__: + kmethod = eval("self." + mname) + # Treat locally. + elif kclass is self.container_set_class: + if cur_set is None: + if self.CanStartBlock(config_entry, from_cmdline): + cur_set = self.container_set_class(self) + elif self.MustStartBlock(config_entry, from_cmdline): + cur_set = self.container_set_class(self) + if cur_set is None: + raise ConfigurationError, \ + ("no current set for %s %s" % (key, val)) + else: + kmethod = eval("cur_set." + mname) + elif kclass is self.container_class: + if cur_set is None: + if self.CanStartBlock(config_entry, from_cmdline): + cur_set = self.container_set_class(self) + else: + raise ConfigurationError, \ + "no current set for container" + if cur_container is None: + if cur_set.CanStartBlock(config_entry, from_cmdline): + # This needs to be a method because of + # backward compatibility problems with + # -c vs. PROCESS:. + cur_container = self.container_class(cur_set) + elif cur_set.MustStartBlock(config_entry, from_cmdline): + cur_container = self.container_class(cur_set) + if cur_container is None: + raise ConfigurationError, \ + ("no current container for %s %s" % (key, val)) + else: + kmethod = eval("cur_container." + mname) + if cur_set.MustEndBlock(config_entry, from_cmdline): + cur_container = None + # The method object now embodies the instance, + # so I don't care about cur_set or cur_container. + if kmethod: + if need_arg: + kmethod(val) + else: + kmethod() + return cur_set, cur_container + + def ChooseConfig(self): + p_set = None + if self.num_conforming_process_sets == 0: + raise ConfigurationError, "No eligible process sets." + elif self.num_conforming_process_sets == 1: + for p in self.process_sets: + if p.meets_requirements: + p_set = p + break + elif (self.num_conforming_process_sets > 1) and \ + (self.config_choice is None): + pass + else: + # We should be able to choose the config now based + # on the config choice. + try: + index = string.atoi(self.config_choice) + try: + p_set = self.process_sets[index - 1] + except IndexError: + raise ConfigurationError, "List index out of range." + except ValueError: + # Try by name. + for p in self.process_sets: + if p.title == self.config_choice: + p_set = p + break + if p_set is None: + raise ConfigurationError, "Couldn't find set named `%s'." % self.config_choice + if p_set and (p_set.meets_requirements == 0): + raise ConfigurationError, "Requirements failed: " + string.join(p_set.UnmetRequirements()) + return p_set Added: src/process_monitor.py =================================================================== --- src/process_monitor.py (rev 0) +++ src/process_monitor.py 2006-12-11 01:15:58 UTC (rev 1) @@ -0,0 +1,1187 @@ +# This file (c) Copyright 1998 - 2002 The MITRE Corporation +# +# This file is part of the Galaxy Communicator system. It is licensed +# under the conditions described in the file LICENSE in the root +# directory of the Galaxy Communicator system. + +"""Process monitor + +The process monitor controls an arbitrary number of processes. It +permits a nuber of arguments for each process, of which -c +terminates each argument sequence. + +--compressed: start in compressed configuration +--ncols : how many columns to display the panes in + (also the number of rows of buttons in compressed mode) +-T : title of process +--open: start the monitor with this process's window open +--start: start the monitor with this process started +--keep_alive: restart this process if it dies +--input_line: provide a line input pane +--input_return: provide a button to send a newline +-c <cmdline>: command line for this process + +The first element of the command line must be something that can +be preceded by 'exec'. Example: + +process_monitor -T "Add and multiply" --open --start \ + -c "run_server bin/addmultiply" -T "Hub" --open \ + -c "bin/hub -pgm_file addmultiply.pgm" +""" + +import os, sys, socket, string, signal, select, math, time + +import Tkinter +from Tkinter import * + +######################################## +# +# GUI units +# +######################################## + +BadLabelJustification = 'BadLabelJustification' + +HEADERFONT = '-*-lucida-bold-r-normal-sans-14-140-*' +CONTENTSFONT = '-*-lucida-medium-r-normal-sans-12-120-*' + +class LabeledEntry(Frame): + def __init__(self, master, label_text, + entry_value = '', + entry_width = 20, + return_cmd = None, + label_width = None): + Frame.__init__(self, master) + if label_width: + fontvar = StringVar() + fontvar.set(label_text) + self.label = Entry(self, relief = 'flat', + borderwidth = 0, + font = HEADERFONT, justify = 'left', + textvariable = fontvar, + width = label_width, state = 'disabled') + else: + self.label = Label(self, text=label_text, font= HEADERFONT) + self.entry = Entry(self, borderwidth=2, relief='sunken', + font=CONTENTSFONT, width=entry_width) + if type(entry_value) is StringType: + self.textvar = StringVar() + else: + self.textvar = IntVar() + self.textvar.set(entry_value) + self.entry['textvariable']=self.textvar + if return_cmd: + self.entry.bind('<KeyPress-Return>', + lambda event, cmd = return_cmd, s = self: \ + cmd(s.GetValue())) + else: + self.entry['state'] = 'disabled' + self.label.pack(side = 'left') + self.entry.pack(side = 'left', fill = 'x', expand = 1) + def Enable(self): + self.entry['state'] = 'normal' + def GetValue(self): + return self.textvar.get() + def SetValue(self, val): + self.textvar.set(val) + +# Sam 11/5/99: We need to enforce line breaks, lest the +# process monitor choke on extra-long lines. The Hub does exactly +# this, by printing out its wait dots without line breaks. +# After each print, I'll probe the current position, and if it's greater +# than the width of the widget, I'll print a line break. + +class ScrollPane(Frame): + def __init__(self, master, height = 12, width = 60, + label = '', label_justification = 'left', + action_button_label = None, action_button_action = None): + Frame.__init__(self, master) + if label: + self.labelframe = Frame(self) + self.labelframe.pack(side = 'top', expand = 0, fill = 'x') + self.label = Label(self.labelframe, text=label, font= HEADERFONT) + if label_justification == 'left': + # self.label.pack(side = 'top', anchor = 'w') + self.label.pack(side = 'left') + if action_button_label or action_button_action: + self.button = Button(self.labelframe, + text = action_button_label, + font = HEADERFONT, + command = action_button_action) + self.button.pack(side = 'right') + elif label_justification == 'right': + # self.label.pack(side = 'top', anchor = 'e') + self.label.pack(side = 'right') + if action_button_label or action_button_action: + self.button = Button(self.labelframe, + text = action_button_label, + font = HEADERFONT, + command = action_button_action) + self.button.pack(side = 'left') + elif label_justification == 'center': + self.label.pack(side = 'top') + else: + raise BadLabelJustification, label_justification + self.textframe = Frame(self) + self.textframe.pack(side = 'top', expand = 1, fill = 'both') + if (action_button_label or action_button_action) and \ + label_justification == 'center': + self.button = Button(self, + text = action_button_label, + font = HEADERFONT, + command = action_button_action) + self.button.pack(side = 'top') + else: + self.textframe = self + self.textbox = Text(self.textframe, borderwidth=1, relief='sunken', + state='disabled', height=height, width=width, + font= CONTENTSFONT + ) + self.scrollbar = Scrollbar(self.textframe, borderwidth=1, + relief='sunken', + command=self.textbox.yview) + self.scrollbar.pack(side = "right", fill = "y") + self.textbox['yscrollcommand'] = self.scrollbar.set + self.textbox.pack(side='left', expand=1, fill='both') + def write(self, text): + self.Write(text) + def Write(self, text): + # For some bizarre reason, when I send Windows output (\r\n) + # to the widget, I get \r showing up as a string character + # (i.e., Tkinter doesn't seem to be dealing with this + # very well). So we need to replace it. + # Actually, it appears that this is a general problem, + # when we read from something that isn't a file descriptor; + # it appears that \r\n is converted to \n when we read + # in a file in Python using open(). So I think I need + # to move this to the Windows specific stuff. + self.textbox['state']='normal' + self.textbox.insert('end', text) + self.textbox.yview_pickplace('end') + # Get the current end. + line, pos = string.splitfields(self.textbox.index('end - 1 chars'), ".") + # We'll stick with the requested width, because + # figuring out the actual width is too damn hard. + if string.atoi(pos) >= string.atoi(self.textbox['width']): + self.textbox.insert('end', '\n') + self.textbox.yview_pickplace('end') + self.textbox['state']='disabled' + return string.atoi(line) + def Clear(self): + self.textbox['state']='normal' + self.textbox.delete('0.0', 'end') + self.textbox['state']='disabled' + +UnknownMenuType = 'UnknownMenuType' + +class MyMenu(Menu): + def __init__(self, master, vals_pairs): + Menu.__init__(self, master, font= CONTENTSFONT, tearoff=0) + for key, val in vals_pairs: + type, trueval = val + if type == 'command': + self.add('command', label = key, + command = lambda com = trueval: com()) + elif type == 'cascade': + self.add('cascade', label = key, + menu = MyMenu(self, trueval)) + else: + raise UnknownMenuType + +class MenuBarEntry(Menubutton): + def __init__(self, master, text, vals_pairs): + Menubutton.__init__(self, master, font = CONTENTSFONT, + text = text) + self.menu = MyMenu(self, vals_pairs) + self['menu'] = self.menu + +class Error(Frame): + def __init__(self, master, error_string): + self.master = master + + Frame.__init__(self, master) + self['borderwidth'] = 2 + self['relief'] = 'ridge' + self.pack(side='top') + self.master.title('You should know...') + + self.msg = Message(self, text=error_string, width=200, + font= CONTENTSFONT, + justify='center') + self.msg.pack(side = 'top') + self.dismiss_row = Frame(self) + self.dismiss_row.pack(side='top') + self.dismiss_row['borderwidth'] = 2 + self.CLOSE = Button(self.dismiss_row, text='OK', + font= HEADERFONT, + command=self.close_cmd) + self.CLOSE.pack(side='left') + self.pack(fill = 'both') + + def close_cmd(self): + self.master.destroy() + +################################### +# +# Toplevel monitor window +# +################################### + +import popen2 + +NoCmdlineError = "NoCmdlineError" + +HISTORYFONT = '-*-courier-medium-r-normal-sans-12-120-*' + +class StatusButton(Label): + def __init__(self, *args, **kw): + apply(Label.__init__, (self,) + args, kw) + self.visible = 1 + self.configure_text = "(stopped)" + Label.configure(self, text = self.configure_text) + def configure(self, text = None, command = None): + if text == "Start": + self.configure_text = "(stopped)" + elif text == "Stop": + self.configure_text = "(running)" + if self.visible: + self.MakeVisible() + def MakeVisible(self): + Label.configure(self, text = self.configure_text) + self.visible = 1 + def MakeInvisible(self): + Label.configure(self, text = (9 * " ")) + self.visible = 0 + +# This is the window which displays the process. It contains +# a frame which has all the controls in it. In addition, +# it has a header for the command and a show/hide button. + +class ReferenceEntry(Frame): + def __init__(self, master, entry_host, process): + Frame.__init__(self, master) + self.entry_host = entry_host + header_host = Frame(self) + header_host.pack(side = 'top', anchor = 'w') + self.header = Frame(header_host) + self.empty_header = Frame(header_host) + self.show_button = Button(self.header, text = "Show", + relief = "groove", + command = lambda s = self: s.Enlarge()) + self.show_button.pack(side = 'left') + cmd = process.title + # This is now 71, because the other 9 will come from + # the run button. + label = Label(self.header, text = cmd + ((71 - len(cmd)) * " "), + font = HISTORYFONT, + justify = 'left', + relief = "flat") + label.pack(side = 'left') + self.header.pack(side = 'top', anchor = 'w') + self.pframe = SingleProcessFrame(self, process = process) + self.run_button = StatusButton(self.header, relief = "flat") + self.pframe.run_buttons.append(self.run_button) + self.run_button.pack(side = "right") + + def Enlarge(self): + # Reset the geometry. + self.entry_host.master.geometry("") + self.pframe.pack(side = 'top', anchor = 'w', + fill = 'both', expand = 1) + # Remove the text for the run buttons. + self.run_button.MakeInvisible() + self.show_button.configure(text = "Hide", + command = self.Reduce) + + def Reduce(self): + # Reset the geometry. + self.entry_host.master.geometry("") + self.pframe.forget() + # Show the text for the run button. + self.run_button.MakeVisible() + self.show_button.configure(text = "Show", + command = self.Enlarge) + + def UseCompressedConfig(self): + # Convert to the compressed configuration. Enlarge and + # get rid of the header line. + self.header.forget() + self.empty_header.pack(side = 'top', anchor = 'w') + # Make sure that it's enlarged. + self.Enlarge() + + def UseColumnConfig(self): + self.empty_header.forget() + self.header.pack(side = 'top', anchor = 'w') + +class ProcessFrame(Frame): + def __init__(self, process_env, process_set, master = None): + Frame.__init__(self, master) + self['borderwidth'] = 2 + # Default title. + self.master.title("Process monitor") + self.pack(fill = 'both', expand = 1) + self.top = Frame(self) + self.top.pack(side = 'top', fill = 'x', anchor = 'w') + self.topmenubar = Frame(self.top, relief = 'groove', borderwidth = 2) + self.topmenubar.pack(side = 'top', fill = 'x', anchor = 'w') + b1 = MenuBarEntry(self.topmenubar, "File", + [("Quit", ('command', self.quit))]) + b1.pack(side = 'left') + b2 = MenuBarEntry(self.topmenubar, "Process Control", + [("Stop all", ('command', self.Shutdown)), + ("Clear all", ('command', self.Clear)), + ("Restart all", ('command', self.Restart))]) + b2.pack(side = "left") + b3 = MenuBarEntry(self.topmenubar, "Configuration", + [("Column configuration", + ('cascade', + [("One column", + ('command', + lambda s = self: s.UseColumnConfig(1))), + ("Two columns", + ('command', + lambda s = self: s.UseColumnConfig(2))), + ("Three columns", + ('command', + lambda s = self: s.UseColumnConfig(3)))])), + ("Compressed configuration", + ('cascade', + [("One button row", + ('command', + lambda s = self: s.UseCompressedConfig(1))), + ("Two button rows", + ('command', + lambda s = self: s.UseCompressedConfig(2))), + ("Three button rows", + ('command', + lambda s = self: s.UseCompressedConfig(3)))]))]) + b3.pack(side = "left") + all_sets = [] + for p in process_env.process_sets: + if p.meets_requirements: + f = lambda s = self, p_set = p: s.UseProcessSet(p_set) + all_sets.append((p.title, ('command', f))) + if len(all_sets) > 1: + b4 = MenuBarEntry(self.topmenubar, "Process Set", all_sets) + b4.pack(side = "left") + bottom = Frame(self) + bottom.pack(side = 'left', anchor = 'n', + fill = 'both', expand = 1) + self.pane_frame = Frame(bottom) + self.pane_frame.pack(side = 'top', anchor = 'w', + fill = 'both', expand = 1) + # Don't pack the clone button, either, until the + # compressed config is used. + self.clone_button = Button(bottom, + text = "Detach this pane", + relief = 'groove', + font = HEADERFONT, + command = self.Detach) + # Now, we build the buttons, but we don't pack them. + self.button_row = Frame(self.top, relief = 'groove', borderwidth = 2) + self.processes = [] + self.num_nondetached_processes = 0 + self.active_process = None + self.separator_list = [] + self.process_env = process_env + # For temporary files. + self.process_env.Initialize() + self.process_set = None + self.UseProcessSet(process_set) + + def UseProcessSet(self, new_set): + + # First, shut down and delete all windows, if + # appropriate. + if self.process_set: + # Shutdown the process set. + self.Shutdown() + # Unuse all the currently used elements. + if self.compressed: + self.UnuseCompressedConfig() + else: + self.UnuseColumnConfig() + # Destroy all the currently used elements. + for s in self.separator_list: + s.destroy() + self.separator_list = [] + for p in self.process_set.processes: + if p.reference_entry: + p.reference_entry.destroy() + p.reference_entry = None + p.compressed_button.destroy() + p.compressed_button = None + + # Now, set up the new configuration. + + if new_set: + self.process_set = new_set + self.master.title(self.process_set.title) + # Now, we build the individual panes and buttons. + for p in self.process_set.processes: + # Build a frame which has the lower pane as a hidden + # component. + # Set up the reference line. + p.AddTk(self.tk) + self.AttachProcess(p) + if p.open: + p.reference_entry.Enlarge() + if p.start: + p.window_host.Restart() + if p.keepalive: + p.window_host.KeepAlive() + # Now, build the button. + b = Button(self.button_row, + text = p.title, + relief = 'raised', + font = HEADERFONT) + p.compressed_button = b + b.configure(command = lambda p = p, s = self: + s.MakeButtonActive(p)) + for i in range(len(self.process_set.processes) - 1): + # Build as many separators as we need. + self.separator_list.append(Frame(self.pane_frame, + relief = 'sunken', + height = 2, + borderwidth = 1)) + self.detached_entries = [] + self.numcols = 0 + self.compressed = 0 + if self.process_env.compressed: + self.UseCompressedConfig(self.process_env.ncols) + else: + self.UseColumnConfig(self.process_env.ncols) + + def AttachProcess(self, p): + ref = ReferenceEntry(self.pane_frame, self, p) + p.SetReferenceEntry(ref) + self.num_nondetached_processes = self.num_nondetached_processes + 1 + + def UnuseColumnConfig(self): + for s in self.separator_list: + s.grid_forget() + for p in self.process_set.processes: + if p.window_host and p.Nondetached(): + p.reference_entry.grid_forget() + + def UseCompressedConfig(self, ncols, force = 0): + if self.compressed and (ncols == self.numcols) and (not force): + return + if (not self.compressed): + if not force: self.UnuseColumnConfig() + self.compressed = 1 + # We want to use the gridder instead of the + # packer for the buttons. + cols, rows = self.button_row.grid_size() + # First, get rid of all the weights. + for i in range(cols): + self.button_row.grid_columnconfigure(i, weight = '0') + for i in range(rows): + self.button_row.grid_rowconfigure(i, weight = '0') + # Now, compute the buttons per button row. + buttons_per_button_row = int(math.ceil(float(self.num_nondetached_processes)/float(ncols))) + i = j = 0 + for p in self.process_set.processes: + if p.Nondetached(): + p.reference_entry.UseCompressedConfig() + # Pack every compressed button. + p.compressed_button.grid(row = j, column = i, + sticky = 'news') + i = i + 1 + if (i % buttons_per_button_row) == 0: + j = j + 1 + i = 0 + cols, rows = self.button_row.grid_size() + # Now, restore the weights. + for i in range(cols): + self.button_row.grid_columnconfigure(i, weight = '1') + for i in range(rows): + self.button_row.grid_rowconfigure(i, weight = '1') + self.numcols = rows + + cols, rows = self.pane_frame.grid_size() + for i in range(cols): + self.pane_frame.grid_columnconfigure(i, weight = '0') + for i in range(rows): + self.pane_frame.grid_rowconfigure(i, weight = '0') + self.MakeButtonActive(self.process_set.processes[0]) + self.button_row.pack(side = 'top', anchor = 'w', fill = 'x') + # We only pack the clone button if there is more than + # one pane. + if self.num_nondetached_processes > 1: + self.clone_button.pack(side = 'bottom', anchor = 'w', fill = 'x') + + # These two methods are specific to the compressed configuration. + def MakeButtonEntryInactive(self, p): + if p.Nondetached(): + if p.reference_entry: + p.reference_entry.grid_forget() + p.compressed_button.configure(relief = 'raised') + + def MakeButtonActive(self, process): + for entry in self.process_set.processes: + self.MakeButtonEntryInactive(entry) + if process: + self.active_process = process + process.compressed_button.configure(relief = 'sunken') + process.reference_entry.grid(row = '0', + column = '0', sticky = 'news') + self.pane_frame.grid_columnconfigure(0, weight = '1') + self.pane_frame.grid_rowconfigure(0, weight = '1') + + def Reattach(self, p, old_toplevel): + # Reattaching is tricky, because you have to regenerate + # using the current configuration. First, you build + # a new reference entry which has the right pane as + # its parent, Then, you have to + # unforget the button and regenerate the grid. Because + # the processes remain in order, they'll get regenerated + # in the same order. + self.AttachProcess(p) + p.SetDetached(0) + old_toplevel.destroy() + p.reference_entry.Enlarge() + # Now, we need to regenerate things. + if self.compressed: + # Regenerate the compressed list. + self.UnuseCompressedConfig() + self.UseCompressedConfig(self.numcols, force = 1) + else: + # Regenerate the uncompressed structure. + self.UnuseColumnConfig() + self.UseColumnConfig(self.numcols, force = 1) + + def Detach(self): + # First, make another button active. + if self.active_process: + # Get rid of the button. + p = self.active_process + p.compressed_button.grid_forget() + self.update() + # Deactivate the button entry. + self.MakeButtonEntryInactive(p) + # Make it detached. + p.SetDetached(1) + self.num_nondetached_processes = self.num_nondetached_processes - 1 + # Activate the first nondetached element. + for next_p in self.process_set.processes: + if next_p.Nondetached(): + self.MakeButtonActive(next_p) + break + # Now,the complicated part. We want to detach the + # process from the old ref_entry and give it + # to a new reference entry in a new toplevel. + # There will be no reattach option at this point. + # The window reattachment doesn't work quite + # right yet. + new_top = Toplevel() + new_top.protocol('WM_DELETE_WINDOW', lambda s = self, p = p, t = new_top: s.Reattach(p, t)) + f = Frame(new_top) + f.pack(fill = 'both', expand = '1') + new_ref_entry = ReferenceEntry(f, new_top, p) + p.SetReferenceEntry(new_ref_entry) + new_ref_entry.pack(side = 'top', fill = 'both', expand = '1') + reattach_button = Button(f, + text = "Reattach this pane", + relief = 'groove', + font = HEADERFONT, + command = lambda s = self, p = p, t = new_top: s.Reattach(p, t)) + reattach_button.pack(side = 'top', fill = 'x') + new_top.title(p.title) + new_ref_entry.UseCompressedConfig() + if self.num_nondetached_processes == 1: + # If we only have one pane left, don't + # allow it to be detached. + self.clone_button.forget() + + def UnuseCompressedConfig(self): + self.button_row.forget() + self.clone_button.forget() + self.active_process = None + for p in self.process_set.processes: + p.compressed_button.grid_forget() + if p.reference_entry and p.Nondetached(): + p.reference_entry.grid_forget() + for s in self.separator_list: + s.grid_forget() + + def UseColumnConfig(self, ncols, force = 0): + if self.compressed == 0 and ncols == self.numcols and (not force): + return + # Make it uncompressed + if self.compressed: + if not force: self.UnuseCompressedConfig() + self.compressed = 0 + # First, get rid of all the weights. + cols, rows = self.pane_frame.grid_size() + for i in range(cols): + self.pane_frame.grid_columnconfigure(i, weight = '0') + for i in range(rows): + self.pane_frame.grid_rowconfigure(i, weight = '0') + panes_per_column = int(math.ceil(float(self.num_nondetached_processes)/float(ncols))) + i = j = 0 + for p in self.process_set.processes: + if not p.Nondetached(): + continue + p.reference_entry.UseColumnConfig() + # Add a separator above every nonzeroth element. + if i > 0: + self.separator_list[i - 1].grid(row = ((i * 2) - 1), column = j, sticky = 'ew') + p.reference_entry.grid(row = (i * 2), column = `j`, + sticky = 'news') + i = i + 1 + if (i % panes_per_column) == 0: + j = j + 1 + i = 0 + cols, rows = self.pane_frame.grid_size() + for i in range(cols): + self.pane_frame.grid_columnconfigure(i, weight = '1') + for i in range(rows): + self.pane_frame.grid_rowconfigure(i, weight = '1') + self.numcols = ncols + + def Shutdown(self): + # Don't really kill. Collect the pids and then do the really kill + # afterward. + if self.process_set: + all_handles = [] + for p in self.process_set.processes: + handle = p.Shutdown(really_kill = 0) + if handle is not None: all_handles.append(handle) + if all_handles: + self.process_set.EnforceShutdown(all_handles) + + def Restart(self): + for p in self.process_set.processes: + p.Restart() + + def Clear(self): + for p in self.process_set.processes: + p.Clear() + + def quit(self): + # self.timer.deletetimerhandler() + self.Shutdown() + # For temporary files. + self.process_env.Cleanup() + self.master.destroy() + self.master.quit() + + def force_quit(self): + self.quit() + + def Poll(self): + for p in self.process_set.processes: + p.Poll() + self.timer = self.tk.createtimerhandler(500, self.Poll) + + def Run(self): + # self.timer = self.tk.createtimerhandler(500, self.Poll) + self.mainloop() + +# We adopt a more complex model, where all the info about +# the process lives in a ProcessContainer, which can be +# made to point to different windows (looking forward to the +# point where we reuse a single pane for multiple processes). + +# This callback structure has to tolerate the possibility +# that we'll have either timer callbacks or file +# callbacks. + +def _CreateHandleCallback(handle, process_container): + if process_container.FDCallbackAvailable(): + return FDHandleCallback(handle, process_container) + else: + return TimerHandleCallback(handle, process_container) + +class HandleCallback: + def __init__(self, handle, process_container): + self.handle = handle + self.process = process_container + +# I don't think the writes are every used. + +class FDHandleCallback(HandleCallback): + def __init__(self, handle, process_container): + HandleCallback.__init__(self, handle, process_container) + if handle == process_container.out_handle: + self.fh_mask = Tkinter.WRITABLE + self.cb = lambda s, m, h = handle, o = self: o.process.WriteCallback(h) + else: + self.fh_mask = Tkinter.READABLE + # Read as much as you can. + self.cb = lambda s, m, h = handle, o = self: o.process.ReadCallback(h, -1) + self.process.tk.createfilehandler(handle, self.fh_mask, self.cb) + self.has_filehandler = 1 + def Remove(self): + if self.has_filehandler: + self.process.tk.deletefilehandler(self.handle.fileno()) + self.has_filehandler = 0 + +class TimerHandleCallback(HandleCallback): + def __init__(self, handle, process_container): + HandleCallback.__init__(self, handle, process_container) + if handle == process_container.out_handle: + self.cb = self.WriteTimer + else: + self.cb = self.ReadTimer + self.timer = self.process.tk.createtimerhandler(10, self.cb) + def WriteTimer(self): + self.process.WriteCallback(self.handle) + self.timer = self.process.tk.createtimerhandler(10, self.WriteTimer) + def ReadTimer(self): + self.process.ReadCallback(self.handle, -1) + if self.timer is not None: + # It may have been removed as a result of shutting down. + self.timer = self.process.tk.createtimerhandler(10, self.cb) + def Remove(self): + if self.timer: + self.timer.deletetimerhandler() + self.timer = None + +# The process container is the object which houses the +# process and all the information associated with it. The +# window host for the process is where the output goes. +# When we set a new window host, we destroy the old one; +# window hosts are never reused. There will be no need +# for a dummy process, because windows without processes +# won't exist. It may be possible to have processes without +# windows, but we still need to track the process. Crucial +# information about the process includes the title, the +# command line, and the input_mode. + +from basic_process_monitor import ProcessContainer, \ + ProcessContainerSet, ProcessEnvironment, ConfigurationError + +class TkProcessContainer(ProcessContainer): + config_table = ProcessContainer.config_table + \ + [(None, "--input_line", 0, "LineInputMode", 0), + (None, "--input_return", 0, "ReturnInputMode", 0), + (None, "--start", 0, "SetAutoStart", 0), + (None, "--open", 0, "SetVisible", 0), + ("PROCESS_MONITOR_ARGS:", None, 1, \ + "AddProcessMonitorArgs", 0)] + + def __init__(self, set): + ProcessContainer.__init__(self, set) + self.input_mode = None + self.open = 0 + self.start = 0 + self.window_host = None + self.reference_entry = None + self.detached = 0 + + # Configuration methods. + + def LineInputMode(self): + if self.input_mode is not None: + raise ConfigurationError, "input mode already specified" + self.input_mode = "input_line" + + def ReturnInputMode(self): + if self.input_mode is not None: + raise ConfigurationError, "input mode already specified" + self.input_mode = "input_return" + + def SetAutoStart(self): + self.start = 1 + + def SetVisible(self): + self.open = 1 + + def AddCommandLine(self, cmdline): + ProcessContainer.AddCommandLine(self, cmdline) + self.WriteCommandLine() + + def ConfigDone(self): + ProcessContainer.ConfigDone(self) + if not self.title: + if len(self.cmdline) > 60: + self.title = self.cmdline[:57] + "..." + else: + self.title = self.cmdline + + def AddProcessMonitorArgs(self, args): + # Just splitting the args isn't good enough, because + # the args are expecting to be in a command line. That + # means that there may be quoted material in the lists. + # Grrrr. + cmdline_args = self.TokenizeCmdlineArgs(args) + env = self.process_set.process_environment + # Just use the local configuration. + expanded_dict = {} + env._AddConfigTable(self.__class__, expanded_dict) + optlist, ignore = env._DigestConfiguration(cmdline_args, + expanded_dict, + 1, 0) + for key, value in optlist: + cur_set, cur_container = env._ConfigureOption(self.process_set, + self, key, value, + 1, expanded_dict) + # If we ever reach the end, barf. + if cur_container is None: + raise ConfigurationError, "process monitor args can't close block" + + # Runtime methods. + + def _SetupHandles(self): + self.callback_dict = {} + ProcessContainer._SetupHandles(self) + + def AddTk(self, tk): + self.tk = tk + + def SetDetached(self, detached): + self.detached = detached + + def Nondetached(self): + return self.detached == 0 + + def Clear(self): + if self.window_host is not None: + self.window_host.Clear() + + def UpdateIdle(self): + if self.window_host is not None: + # Don't update(), just update_idletasks(). + # If you update(), constant reads will + # lock the display. + self.window_host.update_idletasks() + + def SetReferenceEntry(self, ref): + old_ref = self.reference_entry + self.SetWindowHost(ref.pframe) + if old_ref is not None: + old_ref.destroy() + self.reference_entry = ref + + def SetWindowHost(self, host): + old_host = self.window_host + if old_host is not None: + self.UnmonitorOutput() + # And destroy it. + old_host.destroy() + self.window_host = host + if (old_host is not self.window_host) and \ + (host is not None): + # Update the new host. + self.window_host.SetProcess(self) + # Also, monitor the output, I think. + self.MonitorOutput() + + def Pause(self): + self.UnmonitorOutput() + if self.window_host: + self.window_host.LocalPause() + + def Resume(self): + self.MonitorOutput() + if self.window_host: + self.window_host.LocalResume() + + def KeepAlive(self): + ProcessContainer.KeepAlive(self) + if self.window_host: + self.window_host.LocalKeepAlive() + + def LetDie(self): + ProcessContainer.LetDie(self) + if self.window_host: + self.window_host.LocalLetDie() + + def UnmonitorOutput(self): + for cb in self.callback_dict.values(): + cb.Remove() + self.callback_dict = {} + + def MonitorOutput(self): + if self.in_handle: + cb = _CreateHandleCallback(self.in_handle, self) + self.callback_dict[self.in_handle] = cb + if self.err_handle: + cb = _CreateHandleCallback(self.err_handle, self) + self.callback_dict[self.err_handle] = cb + + def WriteHistory(self, str, fd): + ProcessContainer.WriteHistory(self, str, fd) + if self.window_host is not None: + cur_line = self.window_host.history.Write(str) + # If the clear history instruction is a number, + # treat it as the number of lines to save. + if (self.process_set.process_environment.clear_history is not None) and \ + cur_line > self.process_set.process_environment.clear_history: + self.ClearHistory() + + def WriteCommandLine(self): + self.WriteHistory(("[%s]\n" % self.WrapCmdlineForExecution(self.GetCmdlineForDisplay(), self.interleave_error)), None) + + def ClearHistory(self): + ProcessContainer.ClearHistory(self) + if self.window_host is not None: + self.window_host.LocalClearHistory() + + def Shutdown(self, really_kill = 1): + p = ProcessContainer.Shutdown(self, really_kill) + if self.window_host: + self.window_host.LocalShutdown() + return p + + # In order to interleave stdout and stderr better, I've + # redirected stderr to stdout in the subshell. The problem is + # that under this configuration, the shell does not return + # a valid fd for the error stream. So I need not to monitor it. + + def Restart(self): + ProcessContainer.Restart(self) + if self.window_host: + self.window_host.LocalRestart() + + def GetCmdlineForDisplay(self): + if self.window_host: + self.window_host.UpdateCmdline() + return ProcessContainer.GetCmdlineForDisplay(self) + + def GetCmdlineForExecution(self): + if self.window_host: + self.window_host.UpdateCmdline() + return ProcessContainer.GetCmdlineForExecution(self) + + def SetCmdline(self, str): + ProcessContainer.SetCmdline(self, str) @@ Diff output truncated at 60000 characters. @@ From tk at edam.speech.cs.cmu.edu Sun Dec 10 20:18:13 2006 From: tk at edam.speech.cs.cmu.edu (tk@edam.speech.cs.cmu.edu) Date: Sun, 10 Dec 2006 20:18:13 -0500 Subject: [RavenclawDev 204] [2] Pythia/: Created folder remotely Message-ID: <200612110118.kBB1IDOO003975@edam.speech.cs.cmu.edu> An HTML attachment was scrubbed... URL: http://mailman.srv.cs.cmu.edu/pipermail/ravenclaw-developers/attachments/20061210/8c74882c/attachment.html -------------- next part -------------- From tk at edam.speech.cs.cmu.edu Sun Dec 10 20:18:23 2006 From: tk at edam.speech.cs.cmu.edu (tk@edam.speech.cs.cmu.edu) Date: Sun, 10 Dec 2006 20:18:23 -0500 Subject: [RavenclawDev 205] [3] Pythia: Moved remotely Message-ID: <200612110118.kBB1INBj003985@edam.speech.cs.cmu.edu> An HTML attachment was scrubbed... URL: http://mailman.srv.cs.cmu.edu/pipermail/ravenclaw-developers/attachments/20061210/9a482501/attachment.html -------------- next part -------------- Copied: Pythia/src (from rev 2, src) From tk at edam.speech.cs.cmu.edu Thu Dec 14 13:04:21 2006 From: tk at edam.speech.cs.cmu.edu (tk@edam.speech.cs.cmu.edu) Date: Thu, 14 Dec 2006 13:04:21 -0500 Subject: [RavenclawDev 206] [4] Pythia/src: Removed some debugging messages. Message-ID: <200612141804.kBEI4LGD012110@edam.speech.cs.cmu.edu> An HTML attachment was scrubbed... URL: http://mailman.srv.cs.cmu.edu/pipermail/ravenclaw-developers/attachments/20061214/b9449367/attachment.html -------------- next part -------------- Modified: Pythia/src/basic_process_monitor.py =================================================================== --- Pythia/src/basic_process_monitor.py 2006-12-11 01:18:23 UTC (rev 3) +++ Pythia/src/basic_process_monitor.py 2006-12-14 18:04:20 UTC (rev 4) @@ -88,9 +88,7 @@ if self.process_set is not None: env = self.process_set.process_environment if not env.suppress_expansions: - print("command_line: `%s'" % command_line) command_line = env.ExpandFile(command_line) - print("expanded command_line: `%s'" % command_line) if self.cmdline: raise ConfigurationError, "command line already specified" self.cmdline = command_line @@ -658,7 +656,6 @@ directives.append((d[0], "")) else: directives.append((d[0], d[1])) - print("appending `%s', `%s'" % (d[0], d[1])) except ConfigurationError, msg: raise ConfigurationError, msg except: Modified: Pythia/src/win_pm_core.py =================================================================== --- Pythia/src/win_pm_core.py 2006-12-11 01:18:23 UTC (rev 3) +++ Pythia/src/win_pm_core.py 2006-12-14 18:04:20 UTC (rev 4) @@ -269,15 +269,12 @@ # tokenizer on the cmd, too, just to get the # escapes right. - print(cmdline) tokenized_cmdline = self.TokenizeCmdlineArgs(cmdline) cmd = tokenized_cmdline[0] args = tokenized_cmdline[1:] - print(cmd) modified_cmd = self._mkcommand(cmd, args) - print(modified_cmd) try: process_info = win32process.CreateProcess(None, modified_cmd, From svetastenchikova at gmail.com Sun Dec 17 13:28:20 2006 From: svetastenchikova at gmail.com (Svetlana Stenchikova) Date: Sun, 17 Dec 2006 13:28:20 -0500 Subject: [RavenclawDev 207] ieee newsletter Message-ID: <31cecd6b0612171028l1f9b2cu609d230ead9b4f0a@mail.gmail.com> Hi, I will be writing a short article on RavenClaw/Olympus for the IEEE newsletter. If you are user of ravenclaw/olympus outside of the CMU , would you please respond to me with some elaboration on these questions: What kind of system do you use RavenClaw for? What components for ASR/parser/TTS/etc. do you use? What stage is your project at? What is your overall experience? Dan, Antoine and Thomas, let me know if there is anything that you want me to mention, or if you know other users that are not on this mailing list. thank you Svetlana -------------- next part -------------- An HTML attachment was scrubbed... URL: http://mailman.srv.cs.cmu.edu/pipermail/ravenclaw-developers/attachments/20061217/fbf08910/attachment.html