Example #1
0
def restart():
    if pid_of() is None:
        raise Error("timetracker daemon is not installed")

    logger.info("restarting timetracker daemon")
    _stop_daemon()
    _start_daemon()
Example #2
0
def uninstall():
    if pid_of() is not None:
        logger.info("uninstalling timetracker daemon")
        _stop_daemon()

    if LAUNCHD_FILE.exists():
        LAUNCHD_FILE.unlink()
Example #3
0
def _configure_main():
    import cfg
    import database
    import launchd
    import ui
    from harness import logger

    logger.info("configuring timetracker")
    is_first_run = not cfg.settings_file.exists()
    is_running = launchd.pid_of() is not None

    settings = {
        "work_week":
        ui.input_ex("Hours of work per week",
                    default=cfg.work_week,
                    validator=_check_int),
        "auto_away_time":
        ui.input_ex(
            "Auto-away idle time [minutes]",
            default=cfg.auto_away_time,
            validator=_check_int,
        ),
    }

    if is_running:
        start = True
        stop = ui.input_ex("Stop timetracker?", options="yn",
                           default="n") == "y"
    else:
        start = ui.input_ex("Start timetracker?", options="yn",
                            default="y") == "y"
        stop = False

    cfg.update_settings(settings, write_to_disk=True)

    if not cfg.db_file.exists():
        logger.info("initialising database")
    else:
        logger.info("updating database")
    database.init_schema()

    _install_state_scripts()

    if start and not stop:
        launchd.install()
    if stop:
        launchd.uninstall()

    logger.info("done")

    if not launchd.pid_of():
        logger.warning("timetracker daemon is not running")

    if is_first_run:
        logger.info(FIRST_RUN_HELP)
Example #4
0
def _install_state_scripts():
    import shutil

    import cfg
    from harness import logger

    if cfg.on_away_file.exists() or cfg.on_back_file.exists():
        return

    logger.info("creating on-away and on-back scripts in ~/.timetracker")
    scripts_path = cfg.src_path / "scripts"
    for src_file in scripts_path.glob("*"):
        dst_file = cfg.dot_path / src_file.name
        if not dst_file.exists():
            logger.debug("%s -> %s", src_file, dst_file)
            shutil.copy(str(src_file), str(dst_file))
Example #5
0
def daemon():
    G.lock_fh = open(cfg.lock_file, "w")
    try:
        fcntl.lockf(G.lock_fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError as e:
        if e.errno not in (errno.EACCES, errno.EAGAIN):
            raise
        raise Error("daemon already running")

    logger.info("daemon started (pid %s)", os.getpid())
    atexit.register(exit_handler)

    cfg.pid_file.write_text("%s\n" % os.getpid())
    while True:
        start_time = time.time()
        invoke(["update"])
        delay = 60 - (time.time() - start_time)
        if delay > 0:
            logger.debug("sleeping for %ss", delay)
            time.sleep(delay)
Example #6
0
def consistency_check():
    conn = _connection()

    logger.info("checking database consistency")
    found_issues = False

    # duplicate start_time
    with closing(conn.cursor()) as cursor:
        cursor.execute(
            "SELECT start_time FROM active GROUP BY start_time HAVING COUNT(*) > 1"
        )
        duplicate_start_times = [r[0] for r in cursor.fetchall()]

    if duplicate_start_times:
        found_issues = True
        logger.warning("found %s with duplicates",
                       plural(len(duplicate_start_times), "time"))

        duplicate_row_ids = []
        for start_time in duplicate_start_times:
            start_dt = arrow.get(start_time).to(cfg.time_zone)

            with closing(conn.cursor()) as cursor:
                cursor.execute(
                    "SELECT ROWID, end_time FROM active WHERE start_time = %s"
                    % start_time)

                prev_end_time = None
                inexact_duplicates = []
                for row_id, end_time in cursor.fetchall():
                    end_dt = arrow.get(end_time).to(cfg.time_zone)
                    dtr = DateRange(start_dt, end_dt)
                    logger.debug(dtr.date_time_str)

                    # delete exact duplicates
                    if end_time == prev_end_time:
                        duplicate_row_ids.append(row_id)

                    else:
                        inexact_duplicates.append((row_id, dtr))

                    prev_end_time = end_time

                # keep just the shortest range
                for r in inexact_duplicates:
                    print(r[1].date_time_str)

                inexact_duplicates.sort(key=lambda r: r[1].minutes)
                inexact_duplicates.pop(0)
                duplicate_row_ids.extend([r[0] for r in inexact_duplicates])

        if duplicate_row_ids:
            conn.execute("DELETE FROM active WHERE ROWID IN (%s)" %
                         ",".join([str(i) for i in duplicate_row_ids]))
            conn.commit()
            logger.info("deleted %s",
                        plural(len(duplicate_row_ids), "identical item"))

    if not found_issues:
        logger.info("no issues found")
Example #7
0
def install():
    tt_file = cfg.src_path.parent / "tt"
    py_file = sys.executable

    xml = TEMPLATE.format(py=py_file, tt=tt_file, name=NAME)

    if not LAUNCHD_FILE.exists():
        # create launchd plist file
        logger.info("installing timetracker daemon")
        LAUNCHD_FILE.write_text(xml)

    if pid_of() is None:
        # not installed - load (and start)
        logger.info("starting timetracker daemon")
        _start_daemon()
        if pid_of() is None:
            logger.error("failed to install launchd config")
            sys.exit(1)

    else:
        # installed - restart
        restart()
Example #8
0
def exit_handler():
    logger.info("daemon stopped (pid %s)", os.getpid())