mine.cli   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Test Coverage

Coverage 83.19%

Importance

Changes 0
Metric Value
wmc 28
eloc 168
dl 0
loc 248
rs 10
c 0
b 0
f 0
ccs 99
cts 119
cp 0.8319

3 Functions

Rating   Name   Duplication   Size   Complexity  
C main() 0 119 9
F run() 0 86 15
A _restart_daemon() 0 14 4
1
#!/usr/bin/env python
2
3 1
"""Command-line interface."""
4
5 1
import argparse
6 1
import subprocess
7 1
import sys
8 1
import time
9 1
10
import log
11 1
import yorm
12
13 1
from . import CLI, DESCRIPTION, VERSION, common, services
14 1
from .manager import get_manager
15 1
from .models import Application, Data
16 1
17 1
18
daemon = Application(CLI, filename=CLI)
19
20 1
21 1
def main(args=None):
22
    """Process command-line arguments and run the program."""
23
24 1
    # Shared options
25
    debug = argparse.ArgumentParser(add_help=False)
26
    debug.add_argument('-V', '--version', action='version', version=VERSION)
27
    group = debug.add_mutually_exclusive_group()
28 1
    group.add_argument(
29 1
        '-v', '--verbose', action='count', default=0, help="enable verbose logging"
30 1
    )
31 1
    group.add_argument(
32
        '-q',
33 1
        '--quiet',
34
        action='store_const',
35 1
        const=-1,
36
        dest='verbose',
37
        help="only display errors and prompts",
38
    )
39 1
40
    # Build main parser
41 1
    parser = argparse.ArgumentParser(
42
        prog=CLI,
43 1
        description=DESCRIPTION,
44 1
        formatter_class=common.HelpFormatter,
45
        parents=[debug],
46
    )
47 1
    parser.add_argument(
48 1
        '-d',
49
        '--daemon',
50 1
        metavar='DELAY',
51
        nargs='?',
52
        const=300,
53
        type=int,
54 1
        help="run continuously with delay [seconds]",
55 1
    )
56
    parser.add_argument('-f', '--file', help="custom settings file path")
57
    subs = parser.add_subparsers(help="", dest='command', metavar="<command>")
58
59 1
    # Build switch parser
60 1
    info = "start applications on another computer"
61
    sub = subs.add_parser(
62
        'switch',
63
        description=info.capitalize() + '.',
64 1
        help=info,
65 1
        formatter_class=common.HelpFormatter,
66
        parents=[debug],
67 1
    )
68
    sub.add_argument(
69
        'name', nargs='?', help="computer to queue for launch (default: current)"
70
    )
71 1
72 1
    # Build close parser
73 1
    info = "close applications on this computer"
74 1
    sub = subs.add_parser(
75 1
        'close',
76
        description=info.capitalize() + '.',
77 1
        help=info,
78 1
        formatter_class=common.HelpFormatter,
79 1
        parents=[debug],
80 1
    )
81 1
82
    # Build edit parser
83
    info = "launch the settings file for editing"
84 1
    sub = subs.add_parser(
85
        'edit',
86
        description=info.capitalize() + '.',
87 1
        help=info,
88 1
        formatter_class=common.HelpFormatter,
89 1
        parents=[debug],
90 1
    )
91 1
92 1
    # Build clean parser
93 1
    info = "display and delete conflicted files"
94
    sub = subs.add_parser(
95 1
        'clean',
96 1
        description=info.capitalize() + '.',
97 1
        help=info,
98 1
        formatter_class=common.HelpFormatter,
99
        parents=[debug],
100 1
    )
101 1
    sub.add_argument(
102
        '-f',
103
        '--force',
104 1
        action='store_true',
105
        help="actually delete the conflicted files",
106
    )
107
108
    # Parse arguments
109
    args = parser.parse_args(args=args)
110
    kwargs = {'delay': args.daemon}
111
    if args.command == 'switch':
112
        kwargs['switch'] = args.name if args.name else True
113
    elif args.command == 'close':
114
        kwargs['switch'] = False
115
    elif args.command == 'edit':
116
        kwargs['edit'] = True
117
    elif args.command == 'clean':
118
        kwargs['delete'] = True
119
        kwargs['force'] = args.force
120
121
    # Configure logging
122 1
    common.configure_logging(args.verbose)
123 1
124 1
    # Run the program
125
    try:
126 1
        log.debug("Running main command...")
127 1
        success = run(path=args.file, **kwargs)
128
    except KeyboardInterrupt:
129 1
        msg = "command canceled"
130 1
        if common.verbosity == common.MAX_VERBOSITY:
131
            log.exception(msg)
132 1
        else:
133 1
            log.debug(msg)
134
        success = False
135 1
    if success:
136 1
        log.debug("Command succeeded")
137 1
    else:
138
        log.debug("Command failed")
139 1
        sys.exit(1)
140
141 1
142
def run(
143 1
    path=None,
144
    cleanup=True,
145
    delay=None,
146 1
    switch=None,
147
    edit=False,
148 1
    delete=False,
149
    force=False,
150 1
):
151
    """Run the program.
152
153 1
    :param path: custom settings file path
154
    :param cleanup: remove unused items from the config
155
    :param delay: number of seconds to delay before repeating
156
157
    :param switch: computer name to queue for launch
158 1
159 1
    :param edit: launch the configuration file for editing
160 1
161
    :param delete: attempt to delete conflicted files
162 1
    :param force: actually delete conflicted files
163 1
164
    """  # pylint: disable=too-many-arguments,too-many-branches
165
    manager = get_manager()
166
    if not manager.is_running(services.APPLICATION):
167
        manager.start(services.APPLICATION)
168
169
    root = services.find_root()
170
    path = path or services.find_config_path(root=root)
171
172
    data = Data()
173
    yorm.sync(data, path)
174
175
    config = data.config
176
    status = data.status
177 1
178 1
    log.info("Identifying current computer...")
179
    computer = config.computers.get_current()
180 1
    log.info("Current computer: %s", computer)
181 1
182
    if edit:
183
        return manager.launch(path)
184
    if delete:
185
        return services.delete_conflicts(root, force=force)
186 1
187 1
    if switch is True:
188 1
        switch = computer
189 1
    elif switch is False:
190
        data.close_all_applications(config, manager)
191 1
    elif switch:
192 1
        switch = config.computers.match(switch)
193 1
194 1
    if switch:
195
        if switch != computer:
196 1
            data.close_all_applications(config, manager)
197 1
        data.queue_all_applications(config, status, switch)
198
199 1
    while True:
200
        services.delete_conflicts(root, config_only=True, force=True)
201
        data.launch_queued_applications(config, status, computer, manager)
202
        data.update_status(config, status, computer, manager)
203
204
        if delay is None:
205
            break
206
207
        log.info("Delaying %s seconds for files to sync...", delay)
208
        time.sleep(delay)
209
210
        step = 5
211
        elapsed = 0
212
        log.info("Waiting %s seconds for status changes...", delay)
213
        while elapsed < delay and not data.modified:
214
            time.sleep(step)
215
            elapsed += step
216
217
        short_delay = 30
218
        log.info("Delaying %s seconds for files to sync...", short_delay)
219
        time.sleep(short_delay)
220
221
    if cleanup:
222
        data.prune_status(config, status)
223
224
    if delay is None:
225
        return _restart_daemon(manager)
226
227
    return True
228
229
230
def _restart_daemon(manager):
231
    cmd = "nohup {} --daemon --verbose >> /tmp/mine.log 2>&1 &".format(CLI)
232
    if daemon and not manager.is_running(daemon):
233
        log.warning("Daemon is not running, attempting to restart...")
234
235
        log.info("$ %s", cmd)
236
        subprocess.call(cmd, shell=True)
237
        if manager.is_running(daemon):
238
            return True
239
240
        log.error("Manually start daemon: %s", cmd)
241
        return False
242
243
    return True
244
245
246
if __name__ == '__main__':  # pragma: no cover (manual test)
247
    main()
248