#!/usr/bin/env python
import os, sys, time
from glob import glob
from optparse import OptionParser
from exceptions import ImportError,ValueError
from sets import Set
from html import normalize
from database import Database,DatabaseError
from server import Server
from wiki import Wiki
from exed import external_edit
def get_pid( db ):
try:
return int( db.getConfig( 'pid' ) )or None
except:
return None
def stop_server_posix( db, signal=None ):
from signal import SIGTERM
if signal == None: signal = SIGTERM
pid = get_pid( db )
if pid is not None:
try: os.kill( pid, signal )
except:
print "The server appears to have been terminated abnormally. Removing"
print "pid entry from database."
else:
print "Done."
else:
print "There does not appear to be a server associated with this"
print "database."
def kill_server_posix( db, signal=None ):
from signal import SIGKILL
if signal == None: signal = SIGKILL
pid = get_pid( db )
if pid is not None:
print "Killing server.."
try: os.kill( pid, signal )
except: pass
else:
print "There does not appear to be a server associated with this"
print "database."
db.delConfig( "pid" )
def kill_server_win32( db ):
pid = get_pid( db )
if pid is not None:
print "Killing server.."
import win32api
handle = win32api.OpenProcess(1, 0, pid)
win32api.TerminateProcess(handle, 0)
else:
print "There does not appear to be a server associated with this"
print "database."
db.delConfig( "pid" )
def stop_server( db, signal=None ):
if sys.platform == 'win32':
kill_server_win32( db )
else:
stop_server_posix( db, signal )
def kill_server( db, signal=None ):
if sys.platform == 'win32':
kill_server_win32( db )
else:
kill_server_posix( db, signal )
def start_server( wiki ):
pid = get_pid( wiki.getDatabase() )
if pid:
print "There is already a cloud wiki server running on this database."
print "Try cloud-wiki stop, first, to terminate the old server. If"
print "the server will not respond to the stop command, use cloud-wiki"
print "kill."
else:
print "Starting server.."
run_server( wiki )
def restart_server( wiki ):
stop_server( wiki.getDatabase() )
time.sleep(2)
start_server( wiki )
def get_list( db ):
return db.getNodeKeys( )
def get_node( db, key ):
key = normalize( key )
node = db.fetchNode( key )
content = node.getContent()
if content is not None:
return content
else:
print " Node not found."
return None
def put_node( db, key, content, user ):
key = normalize( key )
node = db.fetchNode( key )
node.setContent( content, user )
def edit_node( db, key, user ):
key = normalize( key )
node = db.fetchNode( key )
content = node.getContent()
print "Launching editor for '%s'" % (key,)
content = external_edit( content )
if content is not None:
print "Updating content for '%s'" % (key,)
node.setContent( content, user )
def node_html( db, key ):
key = normalize( key )
node = db.fetchNode( key )
content = node.getStaticPage()
if content is not None:
return content
else:
return None
def rm_node( db, key ):
node = db.fetchNode( key )
if node.getContent() is not None:
db.removeNode( key )
else:
print " Node not found."
def halt_server( db, server ):
db.delConfig( "pid" )
sys.exit( 0 )
def run_server_posix( wiki ):
from signal import signal,SIGHUP,SIGTERM,SIG_IGN
pid = os.fork()
if pid > 0:
return ( 0 )
else:
os.setsid()
pid = os.getpid()
wiki.getDatabase().setConfig( "pid", pid )
signal( SIGHUP, SIG_IGN )
signal(
SIGTERM,
lambda sig, frame: halt_server(
wiki.getDatabase(), wiki.getServer()
)
)
wiki.genInterfaces() # It's important to do this before abandoning cwd
os.chdir( "/" ) # Prevent ourselves from locking the cwd.
wiki.runForever()
def run_server_win32( wiki ):
pid = os.getpid()
wiki.getDatabase().setConfig( "pid", pid )
wiki.genInterfaces() # It's important to do this before abandoning cwd
os.chdir( "/" ) # Prevent ourselves from locking the cwd.
wiki.runForever()
def run_server( wiki ):
if sys.platform == 'win32':
return run_server_win32( wiki )
else:
return run_server_posix( wiki )
def get_config( db, key ):
return db.getConfig( key )
def set_config( db, key, value ):
db.setConfig( key, value )
def rm_config( db, key ):
db.delConfig( key )
def ls_config( db ):
for key in db.getConfigKeys():
yield key
def set_passwd( db, key, value, cook = True ):
if cook and ( db.getConfig( "site-auth" ) == "md5" ):
from md5 import md5
db.setPassword( key, md5( value ).hexdigest() )
else:
db.setPassword( key, value )
def rm_passwd( db, key ):
db.delPassword( key )
def ls_users( db ):
for username in db.getUsernames():
yield username
def migrate( wiki ):
db = wiki.getDatabase()
dbMajor, dbMinor = db.getVersion()
svMajor, svMinor = wiki.getVersion()
if( dbMajor != svMajor )or( dbMinor != svMinor ):
from migration import migrate_database
migrate_database( db, svMajor, svMinor )
else:
print "No migration is required at this time."
def check_version( wiki ):
return wiki.getVersion() == wiki.getDatabase().getVersion()
def ls_refs( db, key ):
for ref in db.fetchReferencesTo( key ):
yield ref
def ls_links( db, key ):
for ref in db.fetchReferencesFrom( key ):
yield ref
topical_help = {
'summary':(
'Usage: cloud-wiki <option>* <command> <argument>*',
'',
'To view a list of global cloud-wiki options, see:',
' cloud-wiki help options.',
'To view a list of cloud-wiki commands, see:',
' cloud-wiki help commands.',
'For more information, visit http://cloudwiki.sourceforge.net for',
'up to date information.'
),
'commands':(
'Commands understood by cloud-wiki:',
'',
'backup Backs up all nodes in the database to the specified path.',
'config Manipulates wiki configuration settings.',
'edit Launches an external editor, specified by the EDIT ',
' environment variable.',
'get Retrieves listed nodes from the database.',
'links Returns a set of local links in the listed nodes.',
'ls Lists the titles of all the nodes in the database.',
'help Provides documentation for the cloud-wiki utility.',
'kill Forces a nonresponsive wiki server to terminate.',
'passwd Manipulates passwords in the wiki user table.',
'rawpasswd As passwd, but does not hash the password prior to storage.',
'refs Returns a set of references to the listed nodes.',
'restart Restarts the wiki server in the background.',
'restore Restores a list of previously backed up nodes.',
'rm Removes a node from the wiki database.',
'start Starts the wiki server in the background.',
'stop Stops the wiki server.',
'',
'For more information about a given command, see:',
' cloud-wiki help <command>',
'For information about other cloud-wiki topics, see:',
' cloud-wiki help'
),
'options':(
"Global options understood by cloud-wiki:",
"",
"-d, --database <path> Specifies a path to the wiki database.",
"-i, --initialize If database file does not exist, create one.",
"-l, --logfile <path> Specified the path to the logfile.",
"-p, --port <port> Overrides the wiki's default port. Usually",
" used with the start and restart commands.",
"-u, --user <user> Attribute all changes made to nodes in the",
" database to the specified user.",
"-v, --verbose Causes all logging output to be diverted to",
" the console.",
"",
'For information about other cloud-wiki topics, see:',
' cloud-wiki help'
),
'backup':(
'Usage: cloud-wiki <option>* backup <path>',
'',
'Backs up all nodes in the database in the specified directory. Node',
'files will be given a name equivalent to their title. This function',
'is intended for periodic full backups of the wiki as insurance',
'catastrophic failure, or hostile editor problems.'
),
'edit':(
'Usage: cloud-wiki <option>* edit <title>+',
'',
'Opens an external editor for each node in turn, permitting you to',
'use your favorite text editor to edit node content. The EDITOR ',
'environment variable is used to determine what editor to use. ',
'',
'On Mac OS X, the author recommends EDITOR=see -w, for example.',
'',
'When the editor process exits successfully, the node is updated with',
'the new content.'
),
'ls':(
'Usage: cloud-wiki <option>* ls',
'',
'Lists the titles of nodes in the wiki database, one per line.'
),
'get':(
'Usage: cloud-wiki <option>* get <path>+',
'',
'Gets each node specified by the last path component of path from ',
'the database, and stores its contents at the specified path.',
),
'restore':(
'Usage: cloud-wiki <option>* restore <path>+',
'',
'Loads each node specified by the last path component of path from ',
'from the filesystem and updates the database with it. Useful for ',
'restoring backed up nodes.'
),
'rm':(
'Usage: cloud-wiki <option>* rm <title>+',
'',
'Deletes each specified node from the database, and all change ',
'information corresponding with the node.'
),
'start':(
'Usage: cloud-wiki <option>* start',
'',
'Starts the wiki server, associating the process with the wiki ',
'database, then returns to the command prompt.'
),
'stop':(
'Usage: cloud-wiki <option>* stop',
'',
'Stops the wiki server associated with the database, or clears a ',
'stale PID from the database.'
),
'restart':(
'Usage: cloud-wiki <option>* restart',
'',
'Equivalent to invoking cloud-wiki stop, then cloud-wiki start'
),
'kill':(
'Usage: cloud-wiki <option>* kill',
'',
'Similar to stop, but instead of politely instructing the process to',
'terminate, sends a SIGKILL which will halt the process immediately.',
'',
'Only use this as a last resort -- cloud-wiki stop should be',
'sufficient for most situations.'
),
'config':(
'Usage: cloud-wiki <option>* config <key>(:(<value>?)?)*',
'',
'Manipulates configuration settings stored in the database. Cloud ',
'wiki uses a key:value system to handle site configuration, and ',
'stores the information in a table in the database. Documentation ',
'about configuration settings should be available at: ',
' http://cloudwiki.sourceforge.net',
'',
'Examples:',
'',
'cloud-wiki config',
' Returns a list of all non-default configuration settings in',
' the database.',
'cloud-wiki config site-title',
' Returns the current configuration setting of "site-title"',
'cloud-wiki config site-title:\'Cloud Wiki\'',
' Configures the title of the wiki site to "Cloud Wiki"'
),
'passwd':(
'Usage: cloud-wiki <option>* passwd <user-name>(:(<password>?)?)*',
'',
'Manipulates password entries in the database. This is only valid',
'on servers that use authentication modules that use the wiki\'s',
'user database, like "md5" and "cloud" authenicators."',
'',
'If Cloud Wiki is configured to use MD5 authentication, it will',
'automatically hash the supplied password.',
'',
'Examples:',
'',
'cloud-wiki passwd',
' Returns a list of all non-default configuration settings in',
' the database.',
'cloud-wiki passwd \'Joe User\'',
' Exits nonzero if Joe User is not in the database."',
'cloud-wiki passwd \'Joe User\':obvious-password',
' Assigns "obvious-password" as Joe User\'s password.'
),
'rawpasswd':(
'Usage: cloud-wiki <option>* rawpasswd <user-name>(:(<password>?)?)*',
'',
'Manipulates password entries in the database. This is only valid',
'on servers that use authentication modules that use the wiki\'s',
'user database, like "md5" and "cloud" authenicators."',
'',
'Unlike passwd, this command actually enters the supplied password',
'directly, instead of an MD5 hash. This is useful if you are',
'simply copying the password from another password table that uses',
'MD5, like PHPBB',
'',
'Examples:',
'',
'cloud-wiki passwd',
' Returns a list of all non-default configuration settings in',
' the database.',
'cloud-wiki passwd \'Joe User\'',
' Exits nonzero if Joe User is not in the database."',
'cloud-wiki passwd \'Joe User\':obvious-password',
' Assigns "obvious-password" as Joe User\'s password.'
),
'migrate':(
'Usage: cloud-wiki <option>* migrate',
'',
'Updates a Cloud Wiki database to match the current version of the',
'server. Normally invoked after an upgrade to regenerate node html.',
),
'refs':(
'Usage: cloud-wiki <option>* refs <key>+',
'',
'Produces a list of node keys that refer to the specified keys.'
),
'links':(
'Usage: cloud-wiki <option>* links <key>+',
'',
'Produces a list of local links contained by the listed nodes.'
)
}
topic_aliases = {
'command':'commands',
'option':'options'
}
def display_help( topic=None ):
if topic is None: topic = 'summary'
try:
print '\n'.join( topical_help[ topic_aliases.get( topic, topic ) ] )
except:
print 'Information on that topic is not available. Try cloud-wiki help'
def main( args ):
parser = OptionParser()
parser.add_option(
"-d", "--database", dest="database",
help="wiki database file path",
default="wiki.db",
metavar="FILE"
)
parser.add_option(
"-i", "--initialize", dest="initialize",
action='store_true',
help="if database file does not exist, create one",
default=False
)
parser.add_option(
"-l", "--logfile", dest="logfile",
help="use the specified logfile, instead of the default",
metavar="FILE",
default=None
)
parser.add_option(
"-p", "--port", dest="port",
type='int',
help="use the specified port, instead of the default",
metavar="PORT",
default=None
)
parser.add_option(
"-v", "--verbose", dest="verbose",
action='store_true',
help="output all SQL transactions to stdout.",
default=False
)
parser.add_option(
"-u", "--user", dest="user",
default="",
metavar="USER"
)
options, args = parser.parse_args( args )
if len(args) == 0:
print "Try cloud-wiki help to view a list of commands."
return ( 3 )
cmd = args[0].lower()
if options.verbose:
options.logfile = sys.stdout
try:
wiki = Wiki(
options.database,
options.logfile,
options.port,
options.initialize
)
except DatabaseError:
print "Could not access your wiki database. Aborting."
return (4)
user = options.user;
if cmd == 'migrate':
migrate( wiki )
return (0)
elif not check_version( wiki ):
print "Your database's version is out of sync with this server's version. You must use 'cloud-wiki migrate' to migrate the database."
return (5)
db = wiki.getDatabase()
if cmd == 'backup':
dest = args[1]
for key in get_list( db ):
print "Getting %s.." % (key,)
data = get_node( db, key )
if data is not None: open( dest + "/" + key, "w" ).write( data )
print "Done."
elif cmd == 'get-html':
#TODO: Does not support get-html with an extension..
for fn in args[1:]:
key = normalize( os.path.basename( fn ) )
print "Getting %s to %s.." % ( key, fn )
data = node_html( db, key )
if data is not None:
open( fn, "w" ).write( data )
print "Done."
elif cmd == 'backup-html':
dest = args[1]
for key in get_list( db ):
print "Getting %s.." % (key,)
data = node_html( db, key )
if data is not None:
open( dest + "/" + key + ".html", "w" ).write( data )
print "Copying Stylesheet.."
data = wiki.getStylesheet()
if data is not None: open( dest + "/style.css", "w" ).write( data )
print "Done."
elif cmd == 'ls':
for key in get_list( db ):
print key
elif cmd == 'get':
for fn in args[1:]:
key = normalize( os.path.basename( fn ) )
print "Getting %s to %s.." % ( key, fn )
data = get_node( db, key )
if data is not None:
open( fn, "w" ).write( data )
elif cmd == 'restore':
for pattern in args[1:]:
for fn in glob( pattern ):
key = normalize( os.path.basename( fn ) )
print "Putting %s in %s.." % ( fn, key )
put_node(
db, key, ''.join( open( fn, "r" ).readlines() ), user
)
print "Done."
elif cmd == 'edit':
for fn in args[1:]:
edit_node( db, fn, user )
elif cmd == 'rm':
for key in args[1:]:
print "Removing %s.." % (key,)
rm_node( db, normalize( key ) )
print "Done."
elif cmd == 'start':
start_server( wiki )
elif cmd == 'stop':
stop_server( db )
elif cmd == 'restart':
restart_server( wiki )
elif cmd == 'kill':
kill_server( db )
elif cmd == 'config':
ops = args[1:]
if ops:
for op in args[1:]:
op = op.strip()
try:
key, value = op.split( ':', 1 )
except ValueError:
value = get_config(db, op)
if value is None:
print '%s is not set.' % (op,)
else:
print '%s:%s' % (op, value)
continue
if value:
set_config( db, key, value )
print "%s:%s" % (key, value)
else:
rm_config( db, key )
print "Removed %s." % (key,)
else:
for key in ls_config( db ):
print "%s:%s" % (key, get_config( db, key ))
elif cmd == 'passwd':
ops = args[1:]
if ops:
for op in args[1:]:
op = op.strip()
key, value = op.split( ':', 1 )
if value:
set_passwd( db, key, value )
print key
else:
rm_passwd( db, key )
print "Removed %s." % (key,)
else:
for username in ls_users( db ):
print username
elif cmd == 'rawpasswd':
ops = args[1:]
if ops:
for op in args[1:]:
op = op.strip()
key, value = op.split( ':', 1 )
if value:
set_passwd( db, key, value, False )
print key
else:
rm_passwd( db, key )
print "Removed %s." % (key,)
else:
for username in ls_users( db ):
print username
elif cmd == 'refs':
results = Set()
for fn in args[1:]:
for ref in ls_refs( db, normalize( os.path.basename( fn ) ) ):
results.add( ref )
for ref in results:
print ref
elif cmd == 'links':
results = Set()
for fn in args[1:]:
for ref in ls_links( db, normalize( os.path.basename( fn ) ) ):
results.add( ref )
for ref in results:
print ref
elif cmd == 'help':
if len(args) > 1:
display_help( args[1].lower() )
else:
display_help( )
else:
print "Unrecognized command or option \"%s\"" % (cmd,)
return( 3 )
return( 0 )
|