c2ef159d8bccc4bbbfe5ac5b789ad52ae55132f1
[hooke.git] / hooke / hooke.py
1 # Copyright (C) 2008-2010 Fabrizio Benedetti
2 #                         Massimo Sandal <devicerandom@gmail.com>
3 #                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
4 #                         W. Trevor King <wking@drexel.edu>
5 #
6 # This file is part of Hooke.
7 #
8 # Hooke is free software: you can redistribute it and/or modify it
9 # under the terms of the GNU Lesser General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
12 #
13 # Hooke is distributed in the hope that it will be useful, but WITHOUT
14 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
16 # Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with Hooke.  If not, see
20 # <http://www.gnu.org/licenses/>.
21
22 """Hooke - A force spectroscopy review & analysis tool.
23 """
24
25 if False: # Queue pickle error debugging code
26     """The Hooke class is passed back from the CommandEngine process
27     to the main process via a :class:`multiprocessing.queues.Queue`,
28     which uses :mod:`pickle` for serialization.  There are a number of
29     objects that are unpicklable, and the error messages are not
30     always helpful.  This block of code hooks you into the Queue's
31     _feed method so you can print out useful tidbits to help find the
32     particular object that is gumming up the pickle works.
33     """
34     import multiprocessing.queues
35     import sys
36     feed = multiprocessing.queues.Queue._feed
37     def new_feed (buffer, notempty, send, writelock, close):
38         def s(obj):
39             print 'SEND:', obj, dir(obj)
40             for a in dir(obj):
41                 attr = getattr(obj, a)
42                 #print '  ', a, attr, type(attr)
43             if obj.__class__.__name__ == 'Hooke':
44                 # Set suspect attributes to None until you resolve the
45                 # PicklingError.  Then fix whatever is breaking the
46                 # pickling.
47                 #obj.commands = None
48                 #obj.drivers = None
49                 #obj.plugins = None
50                 #obj.ui = None
51                 pass
52             sys.stdout.flush()
53             send(obj)
54         feed(buffer, notempty, s, writelock, close)
55     multiprocessing.queues.Queue._feed = staticmethod(new_feed)
56
57 import logging
58 import logging.config
59 import multiprocessing
60 import optparse
61 import os.path
62 import Queue
63 import unittest
64 import StringIO
65 import sys
66
67 from . import engine as engine
68 from . import config as config_mod
69 from . import playlist as playlist
70 from . import plugin as plugin_mod
71 from . import driver as driver_mod
72 from . import ui as ui
73
74
75 class Hooke (object):
76     def __init__(self, config=None, debug=0):
77         self.debug = debug
78         default_settings = (config_mod.DEFAULT_SETTINGS
79                             + plugin_mod.default_settings()
80                             + driver_mod.default_settings()
81                             + ui.default_settings())
82         if config == None:
83             config = config_mod.HookeConfigParser(
84                 paths=config_mod.DEFAULT_PATHS,
85                 default_settings=default_settings)
86             config.read()
87         self.config = config
88         self.load_log()
89         self.load_plugins()
90         self.load_drivers()
91         self.load_ui()
92         self.command = engine.CommandEngine()
93         self.playlists = playlist.NoteIndexList()
94
95     def load_log(self):
96         config_file = StringIO.StringIO()
97         self.config.write(config_file)
98         logging.config.fileConfig(StringIO.StringIO(config_file.getvalue()))
99         # Don't attach the logger because it contains an unpicklable
100         # thread.lock.  Instead, grab it directly every time you need it.
101         #self.log = logging.getLogger('hooke')
102
103     def load_plugins(self):
104         self.plugins = plugin_mod.load_graph(
105             plugin_mod.PLUGIN_GRAPH, self.config, include_section='plugins')
106         self.commands = []
107         for plugin in self.plugins:
108             self.commands.extend(plugin.commands())
109
110     def load_drivers(self):
111         self.drivers = plugin_mod.load_graph(
112             driver_mod.DRIVER_GRAPH, self.config, include_section='drivers')
113
114     def load_ui(self):
115         self.ui = ui.load_ui(self.config)
116
117     def close(self):
118         self.config.write() # Does not preserve original comments
119
120 class HookeRunner (object):
121     def run(self, hooke):
122         """Run Hooke's main execution loop.
123
124         Spawns a :class:`hooke.engine.CommandEngine` subprocess and
125         then runs the UI, rejoining the `CommandEngine` process after
126         the UI exits.
127         """
128         ui_to_command,command_to_ui,command = self._setup_run(hooke)
129         try:
130             hooke.ui.run(hooke.commands, ui_to_command, command_to_ui)
131         finally:
132             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
133         return hooke
134
135     def run_lines(self, hooke, lines):
136         """Run the pre-set commands `lines` with the "command line" UI.
137
138         Allows for non-interactive sessions that are otherwise
139         equivalent to :meth:'.run'.
140         """
141         cmdline = ui.load_ui(hooke.config, 'command line')
142         ui_to_command,command_to_ui,command = self._setup_run(hooke)
143         try:
144             cmdline.run_lines(
145                 hooke.commands, ui_to_command, command_to_ui, lines)
146         finally:
147             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
148         return hooke
149
150     def _setup_run(self, hooke):
151         ui_to_command = multiprocessing.Queue()
152         command_to_ui = multiprocessing.Queue()
153         manager = multiprocessing.Manager()
154         command = multiprocessing.Process(name='command engine',
155             target=hooke.command.run, args=(hooke, ui_to_command, command_to_ui))
156         command.start()
157         return (ui_to_command, command_to_ui, command)
158
159     def _cleanup_run(self, ui_to_command, command_to_ui, command):
160         log = logging.getLogger('hooke')
161         log.debug('cleanup sending CloseEngine')
162         ui_to_command.put(ui.CloseEngine())
163         hooke = None
164         while not isinstance(hooke, Hooke):
165             log.debug('cleanup waiting for Hooke instance from the engine.')
166             hooke = command_to_ui.get(block=True)
167             log.debug('cleanup got %s instance' % type(hooke))
168         command.join()
169         return hooke
170
171
172 def main():
173     p = optparse.OptionParser()
174     p.add_option(
175         '-s', '--script', dest='script', metavar='FILE',
176         help='Script of command line Hooke commands to run.')
177     p.add_option(
178         '-c', '--command', dest='commands', metavar='COMMAND',
179         action='append', default=[],
180         help='Add a command line Hooke command to run.')
181     options,arguments = p.parse_args()
182     if len(arguments) > 0:
183         print >> sys.stderr, 'More than 0 arguments to %s: %s' \
184             % (sys.argv[0], arguments)
185         p.print_help(sys.stderr)
186         sys.exit(1)
187
188     hooke = Hooke(debug=__debug__)
189     runner = HookeRunner()
190
191     if options.script != None:
192         f = open(os.path.expanduser(options.script), 'r')
193         options.commands.extend(f.readlines())
194         f.close
195     if len(options.commands) > 0:
196         try:
197             hooke = runner.run_lines(hooke, options.commands)
198         finally:
199             hooke.close()
200         sys.exit(0)
201
202     try:
203         hooke = runner.run(hooke)
204     finally:
205         hooke.close()