def restart(): if pid_of() is None: raise Error("timetracker daemon is not installed") logger.info("restarting timetracker daemon") _stop_daemon() _start_daemon()
def uninstall(): if pid_of() is not None: logger.info("uninstalling timetracker daemon") _stop_daemon() if LAUNCHD_FILE.exists(): LAUNCHD_FILE.unlink()
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)
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))
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)
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")
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()
def exit_handler(): logger.info("daemon stopped (pid %s)", os.getpid())