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 @@ ...@@ -14,12 +14,30 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import cmd import cmd
import os
import sys import sys
import logging 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 STATUS = protocol.STATUS
NOTIFICATION = protocol.COMMON['NOTIFICATION'] NOTIFICATION = protocol.COMMON['NOTIFICATION']
...@@ -110,10 +128,52 @@ class AgkyraCLI(cmd.Cmd): ...@@ -110,10 +128,52 @@ class AgkyraCLI(cmd.Cmd):
helper = protocol.SessionHelper() helper = protocol.SessionHelper()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.callback = kwargs.pop('callback', None) self.callback = kwargs.pop('callback', sys.argv[0])
self.debug = kwargs.pop('debug', None) self.args = kwargs.pop('parsed_args', None)
LOGGER.setLevel(logging.DEBUG if self.args.debug else logging.INFO)
cmd.Cmd.__init__(self, *args, **kwargs) 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 @property
def client(self): def client(self):
"""Return the helper client instace or None""" """Return the helper client instace or None"""
...@@ -126,17 +186,17 @@ class AgkyraCLI(cmd.Cmd): ...@@ -126,17 +186,17 @@ class AgkyraCLI(cmd.Cmd):
self._client.connect() self._client.connect()
return self._client return self._client
def preloop(self):
"""Prepare agkyra shell"""
self.prompt = '\xe2\x9a\x93 '
self.default('')
def do_help(self, line): 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 <cmd> for an individual command
help <--list | -l> for all commands 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_' prefix = 'do_'
for c in self.get_names(): for c in self.get_names():
if c.startswith(prefix): if c.startswith(prefix):
...@@ -149,6 +209,14 @@ class AgkyraCLI(cmd.Cmd): ...@@ -149,6 +209,14 @@ class AgkyraCLI(cmd.Cmd):
cmd.Cmd.do_help(self, 'help') cmd.Cmd.do_help(self, 'help')
cmd.Cmd.do_help(self, line) 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): def config_list(self, args):
"""List (all or some) options """List (all or some) options
list List all options list List all options
...@@ -165,7 +233,7 @@ class AgkyraCLI(cmd.Cmd): ...@@ -165,7 +233,7 @@ class AgkyraCLI(cmd.Cmd):
3: self.cnf_cmds.print_option 3: self.cnf_cmds.print_option
}[len(args)](*args) }[len(args)](*args)
except Exception as e: except Exception as e:
LOG.debug('%s\n' % e) LOGGER.debug('%s\n' % e)
sys.stderr.write(self.config_list.__doc__ + '\n') sys.stderr.write(self.config_list.__doc__ + '\n')
def config_set(self, args): def config_set(self, args):
...@@ -181,7 +249,7 @@ class AgkyraCLI(cmd.Cmd): ...@@ -181,7 +249,7 @@ class AgkyraCLI(cmd.Cmd):
4: self.cnf_cmds.set_setting 4: self.cnf_cmds.set_setting
}[len(args)](*args) }[len(args)](*args)
except Exception as e: except Exception as e:
LOG.debug('%s\n' % e) LOGGER.debug('%s\n' % e)
sys.stderr.write(self.config_set.__doc__ + '\n') sys.stderr.write(self.config_set.__doc__ + '\n')
def config_delete(self, args): def config_delete(self, args):
...@@ -190,11 +258,7 @@ class AgkyraCLI(cmd.Cmd): ...@@ -190,11 +258,7 @@ class AgkyraCLI(cmd.Cmd):
delete <cloud | sync> NAME [-y] Delete a sync or cloud delete <cloud | sync> NAME [-y] Delete a sync or cloud
delete <cloud |sync> NAME OPTION [-y] Delete a sync or cloud option delete <cloud |sync> NAME OPTION [-y] Delete a sync or cloud option
""" """
try: args.append(self.args.yes)
args.remove('-y')
args.append(True)
except ValueError:
args.append(False)
try: try:
{ {
3: self.cnf_cmds.delete_global_option if ( 3: self.cnf_cmds.delete_global_option if (
...@@ -202,7 +266,7 @@ class AgkyraCLI(cmd.Cmd): ...@@ -202,7 +266,7 @@ class AgkyraCLI(cmd.Cmd):
4: self.cnf_cmds.delete_section_option 4: self.cnf_cmds.delete_section_option
}[len(args)](*args) }[len(args)](*args)
except Exception as e: except Exception as e:
LOG.debug('%s\n' % e) LOGGER.debug('%s\n' % e)
sys.stderr.write(self.config_delete.__doc__ + '\n') sys.stderr.write(self.config_delete.__doc__ + '\n')
def do_config(self, line): def do_config(self, line):
...@@ -211,6 +275,8 @@ class AgkyraCLI(cmd.Cmd): ...@@ -211,6 +275,8 @@ class AgkyraCLI(cmd.Cmd):
set <global|cloud|sync> <setting> <value> Set a setting set <global|cloud|sync> <setting> <value> Set a setting
delete <global|cloud|sync> [setting] Delete a setting or group delete <global|cloud|sync> [setting] Delete a setting or group
""" """
if self.must_help('config'):
return
args = line.split(' ') args = line.split(' ')
try: try:
method = getattr(self, 'config_' + args[0]) method = getattr(self, 'config_' + args[0])
...@@ -220,10 +286,12 @@ class AgkyraCLI(cmd.Cmd): ...@@ -220,10 +286,12 @@ class AgkyraCLI(cmd.Cmd):
def do_status(self, line): def do_status(self, line):
"""Get Agkyra client status. Status may be one of the following: """Get Agkyra client status. Status may be one of the following:
Syncing There is a process syncing right now Syncing There is a process syncing right now
Paused Notifiers are active but syncing is paused Paused Notifiers are active but syncing is paused
Not running No active processes Not running No active processes
""" """
if self.must_help('status'):
return
client = self.client client = self.client
status, msg = client.get_status() if client else None, 'Not running' status, msg = client.get_status() if client else None, 'Not running'
if status: if status:
...@@ -234,24 +302,19 @@ class AgkyraCLI(cmd.Cmd): ...@@ -234,24 +302,19 @@ class AgkyraCLI(cmd.Cmd):
sys.stdout.write('%s\n' % msg) sys.stdout.write('%s\n' % msg)
sys.stdout.flush() 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): 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 client = self.client
if not client: if not client:
sys.stderr.write('No Agkyra daemons running, starting one') 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(' ... ') sys.stderr.write(' ... ')
self.helper.wait_session_to_load() self.helper.wait_session_to_load()
sys.stderr.write('OK\n') sys.stderr.write('OK\n')
...@@ -272,6 +335,8 @@ class AgkyraCLI(cmd.Cmd): ...@@ -272,6 +335,8 @@ class AgkyraCLI(cmd.Cmd):
def do_pause(self, line): def do_pause(self, line):
"""Pause a session (stop it from syncing, but keep it running)""" """Pause a session (stop it from syncing, but keep it running)"""
if self.must_help('pause'):
return
client = self.client client = self.client
if client: if client:
status = client.get_status() status = client.get_status()
...@@ -290,6 +355,8 @@ class AgkyraCLI(cmd.Cmd): ...@@ -290,6 +355,8 @@ class AgkyraCLI(cmd.Cmd):
def do_shutdown(self, line): def do_shutdown(self, line):
"""Shutdown Agkyra, if it is running""" """Shutdown Agkyra, if it is running"""
if self.must_help('shutdown'):
return
client = self.client client = self.client
if client: if client:
client.shutdown() client.shutdown()
...@@ -300,3 +367,14 @@ class AgkyraCLI(cmd.Cmd): ...@@ -300,3 +367,14 @@ class AgkyraCLI(cmd.Cmd):
else: else:
sys.stderr.write('Not running\n') sys.stderr.write('Not running\n')
sys.stderr.flush() 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): ...@@ -731,14 +731,13 @@ def launch_server(callback, debug):
command = [callback] command = [callback]
if debug: if debug:
command.append('-d') command.append('-d')
command.append("server") command.append("start daemon")
subprocess.Popen(command, subprocess.Popen(command, close_fds=True)
close_fds=True)
else: else:
pid = os.fork() pid = os.fork()
if not pid: if not pid:
command = [callback, callback] command = [callback, callback]
if debug: if debug:
command.append('-d') command.append('-d')
command.append("server") command.append("start daemon")
os.execlp(*command) os.execlp(*command)
...@@ -24,7 +24,7 @@ LOG = logging.getLogger(__name__) ...@@ -24,7 +24,7 @@ LOG = logging.getLogger(__name__)
class UIClient(WebSocketClient): class UIClient(WebSocketClient):
"""W Web Socket Client for Agkyra""" """Web Socket Client for Agkyra"""
buf, authenticated, ready = {}, False, False buf, authenticated, ready = {}, False, False
def __init__(self, session): def __init__(self, session):
......
...@@ -16,93 +16,21 @@ ...@@ -16,93 +16,21 @@
import os import os
import sys import sys
import argparse
CURPATH = os.path.dirname(os.path.realpath(__file__)) CURPATH = os.path.dirname(os.path.realpath(__file__))
try: try:
from agkyra import config from agkyra.cli import AgkyraCLI
except ImportError: except ImportError:
LIBPATH = os.path.join(CURPATH, "lib") LIBPATH = os.path.join(CURPATH, "lib")
sys.path.insert(0, LIBPATH) 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 from agkyra.cli import AgkyraCLI
agkcli = AgkyraCLI(callback=CALLBACK, debug=debug)
agkcli.onecmd(' '.join(extras or ['help', ]))
def run_test(debug, extras=None): CALLBACK = os.path.realpath(sys.argv[0])
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")
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__": 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. ...@@ -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 Agkyra CLI manages the agkyra back-end daemon (the module that
performs the actual syncing). 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 arguments, run it without any
.. code-block:: console .. code-block:: console
$ agkyra cli $ agkyra help
Get help Help on agkyra GUI and CLI
help <cmd> for an individual command agkyra Run agkyra with GUI (equivalent to "agkyra gui")
help <--list | -l> for all commands 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>): 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 The CLI can be used independently or in parallel with the GUI. See
...@@ -38,7 +42,7 @@ Commands and examples ...@@ -38,7 +42,7 @@ Commands and examples
List all settings List all settings
$ agkyra cli config list $ agkyra config list
global global
agkyra_dir: /home/user/.agkyra agkyra_dir: /home/user/.agkyra
default_sync: default default_sync: default
...@@ -64,7 +68,7 @@ Commands and examples ...@@ -64,7 +68,7 @@ Commands and examples
Set a new token for cloud "default" 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 :command:`config delete` - delete a setting or group of settings
...@@ -72,7 +76,7 @@ Commands and examples ...@@ -72,7 +76,7 @@ Commands and examples
Delete the "old_sync" sync 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: :command:`status` - print daemon status. Status may be one of the following:
...@@ -84,7 +88,7 @@ Commands and examples ...@@ -84,7 +88,7 @@ Commands and examples
Check if a daemon is running Check if a daemon is running
$ agkyra cli status $ agkyra status
Not running Not running
:command:`start` - launch a daemon if ``not running``, start syncing if ``paused`` :command:`start` - launch a daemon if ``not running``, start syncing if ``paused``
...@@ -93,17 +97,21 @@ Commands and examples ...@@ -93,17 +97,21 @@ Commands and examples
Launch the syncing daemon Launch the syncing daemon
$ agkyra cli start $ agkyra start
No Agkyra daemons running, starting one ... OK No Agkyra daemons running, starting one ... OK
Syncing 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 :command:`pause` - stop a daemon from ``syncing``, but keep it running
.. code-block:: console .. code-block:: console
Pause a syncing daemon Pause a syncing daemon
$ agkyra cli pause $ agkyra pause
Pausing syncer ... OK Pausing syncer ... OK
Paused Paused
...@@ -113,5 +121,5 @@ Commands and examples ...@@ -113,5 +121,5 @@ Commands and examples
Shutdown the daemon Shutdown the daemon
$ agkyra cli shutdown $ agkyra shutdown
Shutting down Agkyra ... Stopped Shutting down Agkyra ... Stopped
...@@ -96,24 +96,24 @@ closed without saving, all changes will be lost. ...@@ -96,24 +96,24 @@ closed without saving, all changes will be lost.
CLI 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 .. code-block:: console