d1c3b9c7a9fd4d4a93e3294ae3bda41f705b2bfd
[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
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation, either
11 # version 3 of the License, or (at your option) any later version.
12 #
13 # Hooke is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU Lesser General 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 unittest
63 import StringIO
64 import sys
65
66 from . import engine as engine
67 from . import config as config_mod
68 from . import playlist as playlist
69 from . import plugin as plugin_mod
70 from . import driver as driver_mod
71 from . import ui as ui
72
73
74 class Hooke (object):
75     def __init__(self, config=None, debug=0):
76         self.debug = debug
77         default_settings = (config_mod.DEFAULT_SETTINGS
78                             + plugin_mod.default_settings()
79                             + driver_mod.default_settings()
80                             + ui.default_settings())
81         if config == None:
82             config = config_mod.HookeConfigParser(
83                 paths=config_mod.DEFAULT_PATHS,
84                 default_settings=default_settings)
85             config.read()
86         self.config = config
87         self.load_log()
88         self.load_plugins()
89         self.load_drivers()
90         self.load_ui()
91         self.command = engine.CommandEngine()
92         self.playlists = playlist.NoteIndexList()
93
94     def load_log(self):
95         config_file = StringIO.StringIO()
96         self.config.write(config_file)
97         logging.config.fileConfig(StringIO.StringIO(config_file.getvalue()))
98         # Don't attach the logger because it contains an unpicklable
99         # thread.lock.  Instead, grab it directly every time you need it.
100         #self.log = logging.getLogger('hooke')
101
102     def load_plugins(self):
103         self.plugins = plugin_mod.load_graph(
104             plugin_mod.PLUGIN_GRAPH, self.config, include_section='plugins')
105         self.commands = []
106         for plugin in self.plugins:
107             self.commands.extend(plugin.commands())
108
109     def load_drivers(self):
110         self.drivers = plugin_mod.load_graph(
111             driver_mod.DRIVER_GRAPH, self.config, include_section='drivers')
112
113     def load_ui(self):
114         self.ui = ui.load_ui(self.config)
115
116     def close(self):
117         self.config.write() # Does not preserve original comments
118
119 class HookeRunner (object):
120     def run(self, hooke):
121         """Run Hooke's main execution loop.
122
123         Spawns a :class:`hooke.engine.CommandEngine` subprocess and
124         then runs the UI, rejoining the `CommandEngine` process after
125         the UI exits.
126         """
127         ui_to_command,command_to_ui,command = self._setup_run(hooke)
128         try:
129             hooke.ui.run(hooke.commands, ui_to_command, command_to_ui)
130         finally:
131             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
132         return hooke
133
134     def run_lines(self, hooke, lines):
135         """Run the pre-set commands `lines` with the "command line" UI.
136
137         Allows for non-interactive sessions that are otherwise
138         equivalent to :meth:'.run'.
139         """
140         cmdline = ui.load_ui(hooke.config, 'command line')
141         ui_to_command,command_to_ui,command = self._setup_run(hooke)
142         try:
143             cmdline.run_lines(
144                 hooke.commands, ui_to_command, command_to_ui, lines)
145         finally:
146             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
147         return hooke
148
149     def _setup_run(self, hooke):
150         ui_to_command = multiprocessing.Queue()
151         command_to_ui = multiprocessing.Queue()
152         manager = multiprocessing.Manager()
153         command = multiprocessing.Process(name='command engine',
154             target=hooke.command.run, args=(hooke, ui_to_command, command_to_ui))
155         command.start()
156         return (ui_to_command, command_to_ui, command)
157
158     def _cleanup_run(self, ui_to_command, command_to_ui, command):
159         ui_to_command.put(ui.CloseEngine())
160         hooke = command_to_ui.get()
161         assert isinstance(hooke, Hooke)
162         command.join()
163         return hooke
164
165
166 def main():
167     p = optparse.OptionParser()
168     p.add_option(
169         '-s', '--script', dest='script', metavar='FILE',
170         help='Script of command line Hooke commands to run.')
171     p.add_option(
172         '-c', '--command', dest='commands', metavar='COMMAND',
173         action='append', default=[],
174         help='Add a command line Hooke command to run.')
175     options,arguments = p.parse_args()
176     if len(arguments) > 0:
177         print >> sys.stderr, 'More than 0 arguments to %s: %s' \
178             % (sys.argv[0], arguments)
179         p.print_help(sys.stderr)
180         sys.exit(1)
181
182     hooke = Hooke(debug=__debug__)
183     runner = HookeRunner()
184
185     if options.script != None:
186         f = open(os.path.expanduser(options.script), 'r')
187         options.commands.extend(f.readlines())
188         f.close
189     if len(options.commands) > 0:
190         try:
191             hooke = runner.run_lines(hooke, options.commands)
192         finally:
193             hooke.close()
194         sys.exit(0)
195
196     try:
197         hooke = runner.run(hooke)
198     finally:
199         hooke.close()