Commit 21a96eb6 authored by Stavros Sachtouris's avatar Stavros Sachtouris Committed by Giorgos Korfiatis
Browse files

Make CLI the starting point of the application

Move all the console parsing to the CLI code (e.g., argparse)
Modify CLI to be able to launch the session as a daemon, with
    $agkyra start daemon
Modify CLI to provide help for all commands
Implement "agkyra gui" which launches the GUI
Launch GUI when "agkyra" is called without arguments
Update documentation to reflect the changes above
parent 9d91053d
......@@ -14,12 +14,30 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import cmd
import os
import sys
import logging
from agkyra import config, protocol, protocol_client
import argparse
try:
from agkyra import config
except ImportError:
sys.path.insert(0, "lib")
from agkyra import config
LOG = logging.getLogger(__name__)
from agkyra import protocol, protocol_client, gui
AGKYRA_DIR = config.AGKYRA_DIR
LOGGERFILE = os.path.join(AGKYRA_DIR, 'agkyra.log')
AGKYRA_LOGGER = logging.getLogger('agkyra')
HANDLER = logging.FileHandler(LOGGERFILE)
FORMATTER = logging.Formatter(
"%(name)s:%(lineno)s %(levelname)s:%(asctime)s:%(message)s")
HANDLER.setFormatter(FORMATTER)
AGKYRA_LOGGER.addHandler(HANDLER)
LOGGER = logging.getLogger(__name__)
STATUS = protocol.STATUS
NOTIFICATION = protocol.COMMON['NOTIFICATION']
......@@ -110,10 +128,52 @@ class AgkyraCLI(cmd.Cmd):
helper = protocol.SessionHelper()
def __init__(self, *args, **kwargs):
self.callback = kwargs.pop('callback', None)
self.debug = kwargs.pop('debug', None)
self.callback = kwargs.pop('callback', sys.argv[0])
self.args = kwargs.pop('parsed_args', None)
LOGGER.setLevel(logging.DEBUG if self.args.debug else logging.INFO)
cmd.Cmd.__init__(self, *args, **kwargs)
@staticmethod
def parse_args():
parser = argparse.ArgumentParser(
description='Agkyra syncer launcher', add_help=False)
parser.add_argument(
'--help', '-h',
action='store_true', help='Help on agkyra syntax and usage')
parser.add_argument(
'--debug', '-d',
action='store_true', help='set logging level to "debug"')
parser.add_argument('cmd', nargs="*")
for terms in (['help', ], ['config', 'delete']):
if not set(terms).difference(sys.argv):
{
'help': lambda: parser.add_argument(
'--list', '-l',
action='store_true', help='List all commands'),
'config_delete': lambda: parser.add_argument(
'--yes', '-y',
action='store_true', help='Yes to all questions')
}['_'.join(terms)]()
return parser.parse_args()
def must_help(self, command):
if self.args.help:
self.do_help(command)
return True
return False
def launch_daemon(self):
"""Launch the agkyra protocol server"""
LOGGER.debug('Start the session helper')
if not self.helper.load_active_session():
self.helper.create_session()
self.helper.start()
else:
LOGGER.info('Another session is running, aborting')
LOGGER.debug('Session Helper is now down')
@property
def client(self):
"""Return the helper client instace or None"""
......@@ -126,17 +186,17 @@ class AgkyraCLI(cmd.Cmd):
self._client.connect()
return self._client
def preloop(self):
"""Prepare agkyra shell"""
self.prompt = '\xe2\x9a\x93 '
self.default('')
def do_help(self, line):
"""Get help
"""Help on agkyra GUI and CLI
agkyra Run agkyra with GUI (equivalent to "agkyra gui")
agkyra <cmd> Run a command through agkyra CLI
To get help on agkyra commands:
help <cmd> for an individual command
help <--list | -l> for all commands
"""
if line and line in ('-l', '--list'):
if getattr(self.args, 'list', None):
self.args.list = None
prefix = 'do_'
for c in self.get_names():
if c.startswith(prefix):
......@@ -149,6 +209,14 @@ class AgkyraCLI(cmd.Cmd):
cmd.Cmd.do_help(self, 'help')
cmd.Cmd.do_help(self, line)
def emptyline(self):
if self.must_help(''):
return
return self.do_gui('')
def default(self, line):
self.do_help(line)
def config_list(self, args):
"""List (all or some) options
list List all options
......@@ -165,7 +233,7 @@ class AgkyraCLI(cmd.Cmd):
3: self.cnf_cmds.print_option
}[len(args)](*args)
except Exception as e:
LOG.debug('%s\n' % e)
LOGGER.debug('%s\n' % e)
sys.stderr.write(self.config_list.__doc__ + '\n')
def config_set(self, args):
......@@ -181,7 +249,7 @@ class AgkyraCLI(cmd.Cmd):
4: self.cnf_cmds.set_setting
}[len(args)](*args)
except Exception as e:
LOG.debug('%s\n' % e)
LOGGER.debug('%s\n' % e)
sys.stderr.write(self.config_set.__doc__ + '\n')
def config_delete(self, args):
......@@ -190,11 +258,7 @@ class AgkyraCLI(cmd.Cmd):
delete <cloud | sync> NAME [-y] Delete a sync or cloud
delete <cloud |sync> NAME OPTION [-y] Delete a sync or cloud option
"""
try:
args.remove('-y')
args.append(True)
except ValueError:
args.append(False)
args.append(self.args.yes)
try:
{
3: self.cnf_cmds.delete_global_option if (
......@@ -202,7 +266,7 @@ class AgkyraCLI(cmd.Cmd):
4: self.cnf_cmds.delete_section_option
}[len(args)](*args)
except Exception as e:
LOG.debug('%s\n' % e)
LOGGER.debug('%s\n' % e)
sys.stderr.write(self.config_delete.__doc__ + '\n')
def do_config(self, line):
......@@ -211,6 +275,8 @@ class AgkyraCLI(cmd.Cmd):
set <global|cloud|sync> <setting> <value> Set a setting
delete <global|cloud|sync> [setting] Delete a setting or group
"""
if self.must_help('config'):
return
args = line.split(' ')
try:
method = getattr(self, 'config_' + args[0])
......@@ -220,10 +286,12 @@ class AgkyraCLI(cmd.Cmd):
def do_status(self, line):
"""Get Agkyra client status. Status may be one of the following:
Syncing There is a process syncing right now
Paused Notifiers are active but syncing is paused
Not running No active processes
Syncing There is a process syncing right now
Paused Notifiers are active but syncing is paused
Not running No active processes
"""
if self.must_help('status'):
return
client = self.client
status, msg = client.get_status() if client else None, 'Not running'
if status:
......@@ -234,24 +302,19 @@ class AgkyraCLI(cmd.Cmd):
sys.stdout.write('%s\n' % msg)
sys.stdout.flush()
# def do_start_daemon(self, line):
# """Start the Agkyra daemon if it is not running"""
# if self.client:
# sys.stderr.write('An Agkyra daemon is already running\n')
# else:
# sys.stderr.write('Launcing a new Agkyra daemon\n')
# protocol.launch_server()
# sys.stderr.write('Waiting for the deamon to load\n')
# self.helper.wait_session_to_load()
# self.do_status('')
# sys.stderr.flush()
def do_start(self, line):
"""Start the session. If no daemons are running, start one first"""
"""Start the session, set it in syncing mode
start Start syncing. If daemon is down, start it up
start daemon Start the agkyra daemon and wait
"""
if self.must_help('start'):
return
if line in ['daemon']:
return self.launch_daemon()
client = self.client
if not client:
sys.stderr.write('No Agkyra daemons running, starting one')
protocol.launch_server(self.callback, self.debug)
protocol.launch_server(self.callback, self.args.debug)
sys.stderr.write(' ... ')
self.helper.wait_session_to_load()
sys.stderr.write('OK\n')
......@@ -272,6 +335,8 @@ class AgkyraCLI(cmd.Cmd):
def do_pause(self, line):
"""Pause a session (stop it from syncing, but keep it running)"""
if self.must_help('pause'):
return
client = self.client
if client:
status = client.get_status()
......@@ -290,6 +355,8 @@ class AgkyraCLI(cmd.Cmd):
def do_shutdown(self, line):
"""Shutdown Agkyra, if it is running"""
if self.must_help('shutdown'):
return
client = self.client
if client:
client.shutdown()
......@@ -300,3 +367,14 @@ class AgkyraCLI(cmd.Cmd):
else:
sys.stderr.write('Not running\n')
sys.stderr.flush()
# Systemic commands
def do_gui(self, line):
"""Launch the agkyra GUI
Only one GUI instance can run at a time.
If an agkyra daemon is already running, the GUI will use it.
"""
if self.must_help('gui'):
return
gui.run(callback=self.callback, debug=self.args.debug)
......@@ -731,14 +731,13 @@ def launch_server(callback, debug):
command = [callback]
if debug:
command.append('-d')
command.append("server")
subprocess.Popen(command,
close_fds=True)
command.append("start daemon")
subprocess.Popen(command, close_fds=True)
else:
pid = os.fork()
if not pid:
command = [callback, callback]
if debug:
command.append('-d')
command.append("server")
command.append("start daemon")
os.execlp(*command)
......@@ -24,7 +24,7 @@ LOG = logging.getLogger(__name__)
class UIClient(WebSocketClient):
"""W Web Socket Client for Agkyra"""
"""Web Socket Client for Agkyra"""
buf, authenticated, ready = {}, False, False
def __init__(self, session):
......
......@@ -16,93 +16,21 @@
import os
import sys
import argparse
CURPATH = os.path.dirname(os.path.realpath(__file__))
try:
from agkyra import config
from agkyra.cli import AgkyraCLI
except ImportError:
LIBPATH = os.path.join(CURPATH, "lib")
sys.path.insert(0, LIBPATH)
from agkyra import config
AGKYRA_DIR = config.AGKYRA_DIR
import logging
LOGFILE = os.path.join(AGKYRA_DIR, 'agkyra.log')
LOGGER = logging.getLogger('agkyra')
HANDLER = logging.FileHandler(LOGFILE)
FORMATTER = logging.Formatter(
"%(name)s:%(lineno)s %(levelname)s:%(asctime)s:%(message)s")
HANDLER.setFormatter(FORMATTER)
LOGGER.addHandler(HANDLER)
CALLBACK = os.path.realpath(sys.argv[0])
def set_debug(debug):
level = logging.DEBUG if debug else logging.INFO
LOGGER.setLevel(level)
def run_server(debug, extras=None):
set_debug(debug)
from agkyra.protocol import SessionHelper
LOGGER.debug('Start the session helper')
helper = SessionHelper()
if not helper.load_active_session():
helper.create_session()
helper.start()
else:
LOGGER.info('Another session is running, aborting')
return
LOGGER.debug('Session Helper is now down')
def run_gui(debug, extras=None):
set_debug(debug)
from agkyra import gui
gui.run(callback=CALLBACK, debug=debug)
def run_cli(debug, extras=None):
set_debug(debug)
from agkyra.cli import AgkyraCLI
agkcli = AgkyraCLI(callback=CALLBACK, debug=debug)
agkcli.onecmd(' '.join(extras or ['help', ]))
def run_test(debug, extras=None):
LOGGER.removeHandler(HANDLER)
from agkyra.scripts import test
selected_test = extras[0] if extras else None
test.main(debug, selected_test)
DISPATCH = {
'server': run_server,
'gui': run_gui,
'cli': run_cli,
'test': run_test,
}
parser = argparse.ArgumentParser(description='Agkyra syncer launcher')
parser.add_argument('--debug', '-d', action='store_true',
help="set logging level to 'debug'")
parser.add_argument('component', nargs="?", default="gui",
help="run 'test', 'server', 'cli', or 'gui' (default)")
parser.add_argument('command', nargs="*", help="command in case of cli")
CALLBACK = os.path.realpath(sys.argv[0])
def main():
args = parser.parse_args()
debug = args.debug
set_debug(debug)
component = args.component
action = DISPATCH.get(component)
if action is None:
print "No such component '%s'." % component
return
action(debug, extras=args.command)
if __name__ == "__main__":
main()
args = AgkyraCLI.parse_args()
agkcli = AgkyraCLI(parsed_args=args)
agkcli.onecmd(' '.join(args.cmd))
......@@ -11,19 +11,23 @@ In this section it is assumed agkyra is installed and properly setup.
Agkyra CLI manages the agkyra back-end daemon (the module that
performs the actual syncing).
To run it, execute ``agkyra cli`` from the command line. To get a list of
To get help, execute ``agkyra help`` from the command line. To get a list of
arguments, run it without any
.. code-block:: console
$ agkyra cli
Get help
help <cmd> for an individual command
help <--list | -l> for all commands
$ agkyra help
Help on agkyra GUI and CLI
agkyra Run agkyra with GUI (equivalent to "agkyra gui")
agkyra <cmd> Run a command through agkyra CLI
To get help for agkyra commands:
help <cmd> for an individual command
help <--list | -l> for all commands
Documented commands (type help <topic>):
========================================
config help pause shutdown start status
config help pause shutdown start status gui
The CLI can be used independently or in parallel with the GUI. See
......@@ -38,7 +42,7 @@ Commands and examples
List all settings
$ agkyra cli config list
$ agkyra config list
global
agkyra_dir: /home/user/.agkyra
default_sync: default
......@@ -64,7 +68,7 @@ Commands and examples
Set a new token for cloud "default"
$ agkyra cli config set cloud default token n3w-us3r-t0k3n
$ agkyra config set cloud default token n3w-us3r-t0k3n
:command:`config delete` - delete a setting or group of settings
......@@ -72,7 +76,7 @@ Commands and examples
Delete the "old_sync" sync
$ agkyra cli config delete sync old_sync
$ agkyra config delete sync old_sync
:command:`status` - print daemon status. Status may be one of the following:
......@@ -84,7 +88,7 @@ Commands and examples
Check if a daemon is running
$ agkyra cli status
$ agkyra status
Not running
:command:`start` - launch a daemon if ``not running``, start syncing if ``paused``
......@@ -93,17 +97,21 @@ Commands and examples
Launch the syncing daemon
$ agkyra cli start
$ agkyra start
No Agkyra daemons running, starting one ... OK
Syncing
..note:: Run "agkyra start daemon" to start a session as a daemon. After that,
use the CLI from a separate console to manage the session, or launch a GUI.
The GUI will automatically connect to the running session.
:command:`pause` - stop a daemon from ``syncing``, but keep it running
.. code-block:: console
Pause a syncing daemon
$ agkyra cli pause
$ agkyra pause
Pausing syncer ... OK
Paused
......@@ -113,5 +121,5 @@ Commands and examples
Shutdown the daemon
$ agkyra cli shutdown
$ agkyra shutdown
Shutting down Agkyra ... Stopped
......@@ -96,24 +96,24 @@ closed without saving, all changes will be lost.
CLI
---
Use the **agkyra cli config** commands to set and update settings:
Use the **agkyra config** commands to set and update settings:
.. code-block:: console
--- Set up a cloud named CLD ---
$ agkyra cli config set cloud CLD url http://www.example.org/identity/v2.0
$ agkyra cli config set cloud CLD token ex4mpl3-t0k3n
$ agkyra config set cloud CLD url http://www.example.org/identity/v2.0
$ agkyra config set cloud CLD token ex4mpl3-t0k3n
--- Set up a sync (cloud, local directory, container) named SNC ---
$ agkyra cli config set sync SNC directory /my/local/directory
$ agkyra cli config set sync SNC cloud CLD
$ agkyra cli config set sync SNC container remote_container
$ agkyra config set sync SNC directory /my/local/directory
$ agkyra config set sync SNC cloud CLD
$ agkyra config set sync SNC container remote_container
--- Set the SNC sync as the default ---
$ agkyra cli config set default_sync CLD
$ agkyra config set default_sync CLD
.. note:: use the **agkyra cli config list** command for the current settings
.. note:: use the **agkyra config list** command for the current settings
Config file
......
......@@ -12,7 +12,7 @@ check if the application is still running:
.. code-block: console
$ agkyra cli status
$ agkyra status
If the status is ``Syncing``, ``Pausing`` or ``Paused``, then the application
is running. Try running agkyra again and see if everything is OK.
......@@ -25,13 +25,13 @@ Agkyra is not starting
The application has to be reset manually. We can do this without data loss.
You need to remove the session locks to enable the application to start again.
To do that, remove the files ``$agkyra_dir/session.db`` and
``agkyra_dir/session.info``.
``$agkyra_dir/session.info``.
.. note:: To check the exact value of ``agkyra_dir``
.. code-block:: console
$ agkyra cli config list global agkyra_dir
$ agkyra config list global agkyra_dir
Agkyra is still not starting
----------------------------
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment