1#!/usr/bin/env python
2"""
3$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/durus/durus $
4$Id: durus 30032 2007-09-02 18:30:57Z dbinger $
5"""
6from code import InteractiveConsole
7from durus.client_storage import ClientStorage
8from durus.connection import Connection
9from durus.file_storage import FileStorage, TempFileStorage
10from durus.logger import log, logger, direct_output
11from durus.storage_server import DEFAULT_PORT, DEFAULT_HOST, DEFAULT_GCBYTES
12from durus.storage_server import SocketAddress
13from durus.storage_server import StorageServer, wait_for_server
14from durus.utils import int8_to_str, str_to_int8, write
15from optparse import OptionParser
16from optparse import OptionParser
17from pprint import pprint
18from time import sleep
19from types import ModuleType
20import os
21import socket
22import sys
23
24
25def configure_readline(namespace, history_path):
26    try:
27        import readline, rlcompleter, atexit
28        readline.set_completer(
29            rlcompleter.Completer(namespace=namespace).complete)
30        readline.parse_and_bind("tab: complete")
31        def save_history(history_path=history_path):
32            readline.write_history_file(history_path)
33        atexit.register(save_history)
34        if os.path.exists(history_path):
35            readline.read_history_file(history_path)
36    except ImportError:
37        pass
38
39def interactive_client(file, address, cache_size, readonly, repair,
40                       startup):
41    if file:
42        storage = FileStorage(file, readonly=readonly, repair=repair)
43        description = file
44    else:
45        socket_address = SocketAddress.new(address)
46        wait_for_server(address=socket_address)
47        storage = ClientStorage(address=socket_address)
48        description = socket_address
49    connection = Connection(storage, cache_size=cache_size)
50    console_module = ModuleType('__console__')
51    sys.modules['__console__'] = console_module
52    namespace = {'connection': connection,
53                 'root': connection.get_root(),
54                 'get': connection.get,
55                 'sys': sys,
56                 'os': os,
57                 'int8_to_str': int8_to_str,
58                 'str_to_int8': str_to_int8,
59                 'pp': pprint}
60    vars(console_module).update(namespace)
61    configure_readline(
62        vars(console_module), os.path.expanduser("~/.durushistory"))
63    console = InteractiveConsole(vars(console_module))
64    if startup:
65        console.runsource('execfile("%s")' % os.path.expanduser(startup))
66    help = ('    connection -> the Connection\n'
67            '    root       -> the root instance')
68    console.interact('Durus %s\n%s' % (description, help))
69
70def client_main():
71    from optparse import OptionParser
72    parser = OptionParser()
73    parser.set_description("Opens a client connection to a Durus server.")
74    parser.add_option(
75        '--file', dest="file", default=None,
76        help="If this is not given, the storage is through a Durus server.")
77    parser.add_option(
78        '--port', dest="port", default=DEFAULT_PORT,
79        type="int",
80        help="Port the server is on. (default=%s)" % DEFAULT_PORT)
81    parser.add_option(
82        '--host', dest="host", default=DEFAULT_HOST,
83        help="Host of the server. (default=%s)" % DEFAULT_HOST)
84    parser.add_option(
85        '--address', dest="address", default=None,
86        help=(
87            "Address of the server.\n"
88            "If given, this is the path to a Unix domain socket for "
89            "the server."))
90    parser.add_option(
91        '--cache_size', dest="cache_size", default=10000, type="int",
92        help="Size of client cache (default=10000)")
93    parser.add_option(
94        '--repair', dest='repair', action='store_true',
95        help=('Repair the filestorage by truncating to remove anything '
96              'that is malformed.  Without this option, errors '
97              'will cause the program to report and terminate without '
98              'attempting any repair.'))
99    parser.add_option(
100        '--readonly', dest='readonly', action='store_true',
101        help='Open the file in read-only mode.')
102    parser.add_option(
103        '--startup', dest='startup',
104        default=os.environ.get('DURUSSTARTUP', ''),
105        help=('Full path to a python startup file to execute on startup.'
106              '(default=DURUSSTARTUP from environment, if set)')
107        )
108    (options, args) = parser.parse_args()
109    if options.address is None:
110        address = (options.host, options.port)
111    else:
112        address = options.address
113    interactive_client(options.file, address,
114                       options.cache_size, options.readonly, options.repair,
115                       options.startup)
116
117def get_storage(file, repair, readonly):
118    if file:
119        return FileStorage(file, repair=repair, readonly=readonly)
120    else:
121        return TempFileStorage()
122
123def start_durus(logfile, logginglevel, address, storage, gcbytes):
124    if logfile is None:
125        logfile = sys.stderr
126    else:
127        logfile = open(logfile, 'a+')
128    direct_output(logfile)
129    logger.setLevel(logginglevel)
130    socket_address = SocketAddress.new(address)
131    if isinstance(storage, FileStorage):
132        log(20, 'Storage file=%s address=%s',
133            storage.get_filename(), socket_address)
134    StorageServer(storage, address=socket_address, gcbytes=gcbytes).serve()
135
136def stop_durus(address):
137    socket_address = SocketAddress.new(address)
138    sock = socket_address.get_connected_socket()
139    if sock is None:
140        log(20, "Durus server %s doesn't seem to be running." %
141            str(address))
142        return False
143    write(sock, 'Q') # graceful exit message
144    sock.close()
145    # Try to wait until the address is free.
146    for attempt in range(20):
147        sleep(0.5)
148        sock = socket_address.get_connected_socket()
149        if sock is None:
150            break
151        sock.close()
152    return True
153
154def run_durus_main():
155    parser = OptionParser()
156    parser.set_description('Run a Durus Server')
157    parser.add_option(
158        '--port', dest='port', default=DEFAULT_PORT, type='int',
159        help='Port to listen on. (default=%s)' % DEFAULT_PORT)
160    parser.add_option(
161        '--file', dest='file', default=None,
162        help=('If not given, the storage is in a new temporary file.'))
163    parser.add_option(
164        '--host', dest='host', default=DEFAULT_HOST,
165        help='Host to listen on. (default=%s)' % DEFAULT_HOST)
166    parser.add_option(
167        '--gcbytes', dest='gcbytes', default=DEFAULT_GCBYTES, type='int',
168        help=('Trigger garbage collection after this many commits. (default=%s)' %
169            DEFAULT_GCBYTES))
170    if hasattr(socket, 'AF_UNIX'):
171        parser.add_option(
172            '--address', dest="address", default=None,
173            help=(
174                "Address of the server.\n"
175                "If given, this is the path to a Unix domain socket for "
176                "the server."))
177        parser.add_option(
178            '--owner', dest="owner", default=None,
179            help="Owner of the Unix domain socket (the --address value).")
180        parser.add_option(
181            '--group', dest="group", default=None,
182            help="group of the Unix domain socket (the --address value).")
183        parser.add_option(
184            '--umask', dest="umask", default=None, type="int",
185            help="umask for the Unix domain socket (the --address value).")
186    logginglevel = logger.getEffectiveLevel()
187    parser.add_option(
188        '--logginglevel', dest='logginglevel', default=logginglevel, type='int',
189        help=('Logging level. Lower positive numbers log more. (default=%s)' %
190              logginglevel))
191    parser.add_option(
192        '--logfile', dest='logfile', default=None,
193        help=('Log file. (default=stderr)'))
194    parser.add_option(
195        '--repair', dest='repair', action='store_true',
196        help=('Repair the filestorage by truncating to remove anything '
197              'that is malformed.  Without this option, errors '
198              'will cause the program to report and terminate without '
199              'attempting any repair.'))
200    parser.add_option(
201        '--readonly', dest='readonly', action='store_true',
202        help='Open the file in read-only mode.')
203    parser.add_option(
204        '--stop', dest='stop', action='store_true',
205        help='Instead of starting the server, try to stop a running one.')
206    (options, args) = parser.parse_args()
207    if getattr(options, 'address', None) is None:
208        address = SocketAddress.new((options.host, options.port))
209    else:
210        address = SocketAddress.new(address=options.address,
211            owner=options.owner, group=options.group, umask=options.umask)
212    if not options.stop:
213        start_durus(options.logfile,
214                    options.logginglevel,
215                    address,
216                    get_storage(options.file, options.repair, options.readonly),
217                    options.gcbytes)
218    else:
219        stop_durus(address)
220
221def pack_storage_main():
222    parser = OptionParser()
223    parser.set_description("Packs a Durus storage.")
224    parser.add_option(
225        '--file', dest="file", default=None,
226        help="If this is not given, the storage is through a Durus server.")
227    parser.add_option(
228        '--port', dest="port", default=DEFAULT_PORT,
229        type="int",
230        help="Port the server is on. (default=%s)" % DEFAULT_PORT)
231    parser.add_option(
232        '--host', dest="host", default=DEFAULT_HOST,
233        help="Host of the server. (default=%s)" % DEFAULT_HOST)
234    (options, args) = parser.parse_args()
235    if options.file is None:
236        wait_for_server(options.host, options.port)
237        storage = ClientStorage(host=options.host, port=options.port)
238    else:
239        storage = FileStorage(options.file)
240    connection = Connection(storage)
241    connection.pack()
242
243def usage():
244    sys.stdout.write(
245        'durus [ -c | -s | -p ] [ -h ] [<specific options>]\n'
246        '    -s   Start or stop a Durus storage server.\n'
247        '    -c   Start a low-level interactive client.\n'
248        '    -p   Pack a storage file.\n'
249        '    -h   Get help on specific options.\n')
250
251def main():
252    if len(sys.argv) == 1:
253        usage()
254    else:
255        arg = sys.argv[1]
256        sys.argv[1:] = sys.argv[2:]
257        if arg == '-c':
258            client_main()
259        elif arg == '-s':
260            run_durus_main()
261        elif arg == '-p':
262            pack_storage_main()
263        else:
264            usage()
265
266
267if __name__ == '__main__':
268    main()
269