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