From c9f21862006f50937f22f51155ee81ff47399730 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sat, 7 Mar 2020 16:27:55 +0100 Subject: bump --- nix/default.nix | 3 +- nix/notmuch-links.nix | 24 +++++++++ nix/notmuch-tcp.nix | 22 +++++--- notmuch-links | 30 +++++++++++ notmuch-tcp | 2 +- notmuch-tcp-client | 61 ++++++++++++++++++++++ notmuch-tcp-server | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++ worktime.py | 53 +++++++++++-------- 8 files changed, 306 insertions(+), 31 deletions(-) create mode 100644 nix/notmuch-links.nix create mode 100644 notmuch-links create mode 100644 notmuch-tcp-client create mode 100644 notmuch-tcp-server diff --git a/nix/default.nix b/nix/default.nix index 097d092..5665a9d 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,6 +1,6 @@ self: super: -{ +rec { cliparg = self.callPackage ./cliparg.nix {}; rebuild-system = self.callPackage ./rebuild-system.nix {}; pulseaudio-ctl = self.callPackage ./pulseaudio-ctl.nix {}; @@ -8,6 +8,7 @@ self: super: rolling-directory = self.callPackage ./rolling-directory.nix {}; recv = self.callPackage ./recv.nix {}; notmuch-tcp = self.callPackage ./notmuch-tcp.nix {}; + notmuch-links = self.callPackage ./notmuch-links.nix { inherit notmuch-tcp; }; persistent-nix-shell = self.callPackage ./persistent-nix-shell.nix {}; worktime = self.callPackage ./worktime.nix {}; } diff --git a/nix/notmuch-links.nix b/nix/notmuch-links.nix new file mode 100644 index 0000000..67ebe3b --- /dev/null +++ b/nix/notmuch-links.nix @@ -0,0 +1,24 @@ +{ stdenv +, notmuch-tcp +, zsh, coreutils, gawk, gnused +}: + +stdenv.mkDerivation rec { + pname = "notmuch-links"; + version = "0.1"; + src = ../notmuch-links; + + phases = [ "buildPhase" "installPhase" ]; + + inherit zsh coreutils gawk gnused; + notmuchtcp = notmuch-tcp; + + buildPhase = '' + substituteAll $src notmuch-links + ''; + + installPhase = '' + install -m 0755 -D -t $out/bin \ + notmuch-links + ''; +} diff --git a/nix/notmuch-tcp.nix b/nix/notmuch-tcp.nix index 59d2e39..1a1947b 100644 --- a/nix/notmuch-tcp.nix +++ b/nix/notmuch-tcp.nix @@ -1,22 +1,30 @@ { stdenv -, zsh, socat, nettools, coreutils +, python38 }: stdenv.mkDerivation rec { pname = "notmuch-tcp"; - version = "0.1"; - src = ../notmuch-tcp; + version = "1.0"; + src = [../notmuch-tcp-client ../notmuch-tcp-server]; - phases = [ "buildPhase" "installPhase" ]; + phases = [ "unpackPhase" "buildPhase" "installPhase" ]; - inherit zsh socat nettools coreutils; + python = python38; + + unpackPhase = '' + for srcFile in $src; do + cp $srcFile $(stripHash $srcFile) + done + ''; buildPhase = '' - substituteAll $src notmuch-tcp + substituteAllInPlace notmuch-tcp-client + substituteAllInPlace notmuch-tcp-server ''; installPhase = '' install -m 0755 -D -t $out/bin \ - notmuch-tcp + notmuch-tcp-client notmuch-tcp-server + ln -s notmuch-tcp-client $out/bin/notmuch-tcp ''; } diff --git a/notmuch-links b/notmuch-links new file mode 100644 index 0000000..16cc01c --- /dev/null +++ b/notmuch-links @@ -0,0 +1,30 @@ +#!@zsh@/bin/zsh + +set -xe + +function notmuch { + NOTMUCH_TCP=${NOTMUCH_TCP:-2011} @notmuchtcp@/bin/notmuch-tcp ${@} +} + +function browser { + ${BROWSER:-firefox} ${@} +} + +maxCount=0 +if [[ -n "$1" && "$1" == <-> ]]; then + maxCount="$1" +fi + +count=0 +for thread (${(z)$(notmuch search --sort=oldest-first 'tag:inbox AND is:link AND is:unread AND NOT (is:killed or tag:later)' | @coreutils@/bin/tee >(cat >&2) | @gawk@/bin/awk '{ print $1; }')}); do + url=$(notmuch show --format=mbox $thread | @gnused@/bin/sed -r '/^X-RSS-URL: /!d; s/^X-RSS-URL: (.*)$/\1/') + count=$((count + 1)) + + if [[ -n "${url}" ]]; then + browser ${url} && notmuch tag -unread -inbox -- $thread + fi + + if [[ ${maxCount} -gt 0 && ${count} -ge ${maxCount} ]]; then + exit 0 + fi +done diff --git a/notmuch-tcp b/notmuch-tcp index 57da3f5..d2f7a57 100755 --- a/notmuch-tcp +++ b/notmuch-tcp @@ -6,4 +6,4 @@ NOTMUCH_HOST=${NOTMUCH_HOST:-odin.asgard.yggdrasil} cd ~/.notmuch-tcp -@socat@/bin/socat STDIO,ignoreeof openssl:${NOTMUCH_HOST}:${NOTMUCH_TCP},verify=1,cafile=ca.pem,certificate=${HOST}.pem,key=${HOST}.key <<(print "${(@q)@}"; inp=$(cat); printf "%d\n" $(@coreutils@/bin/wc -c <<<"${inp}"); @coreutils@/bin/cat <<<"${inp}") +@socat@/bin/socat STDIO,ignoreeof openssl:${NOTMUCH_HOST}:${NOTMUCH_TCP},verify=1,cafile=ca.pem,certificate=${HOST}.pem,key=${HOST}.key <<(print "${(@q)@}"; inp=$(cat 2>&- || echo ""); printf "%d\n" $(@coreutils@/bin/wc -c <<<"${inp}"); @coreutils@/bin/cat <<<"${inp}") diff --git a/notmuch-tcp-client b/notmuch-tcp-client new file mode 100644 index 0000000..b29d6b2 --- /dev/null +++ b/notmuch-tcp-client @@ -0,0 +1,61 @@ +#!@python@/bin/python + +from os import environ +from os.path import expanduser +import socket +import ssl +from shlex import quote +from sys import argv, stdin, stdout +from multiprocessing import Process +from select import select + + +port = environ.get('NOTMUCH_TCP') +host = environ.get('NOTMUCH_HOST') +hostname = socket.gethostname() + +if port is None: + port = 2010 +if host is None: + host = "odin.asgard.yggdrasil" + + +sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) +sslcontext.load_verify_locations(cafile = expanduser('~/.notmuch-tcp/ca.pem')) +sslcontext.load_cert_chain(certfile = expanduser(f"~/.notmuch-tcp/{hostname}.pem"), keyfile = expanduser(f"~/.notmuch-tcp/{hostname}.key")) + +with socket.create_connection((host, port)) as sock: + with sslcontext.wrap_socket(sock, server_hostname = host) as ssock: + def send_args(): + escaped_args = ' '.join(map(quote, argv[1:])) + ssock.sendall(f"{escaped_args}\n".encode()) + + def send_stdin(): + with open(0, 'rb') as stdin_bin: + while True: + to_send = stdin_bin.read(256) + + if to_send: + ssock.sendall(to_send) + else: + break + + def recv_stdout(): + with open(1, 'wb') as stdout_bin: + while True: + ready = select([ssock], [], [], 5) + if ready[0]: + received = ssock.recv(256) + if len(received) <= 0: + break + stdout_bin.write(received) + else: + break + + send_args() + send = Process(target = send_stdin) + recv = Process(target = recv_stdout) + send.start() + recv.start() + recv.join() + send.terminate() diff --git a/notmuch-tcp-server b/notmuch-tcp-server new file mode 100644 index 0000000..328b72c --- /dev/null +++ b/notmuch-tcp-server @@ -0,0 +1,142 @@ +#!@python@/bin/python + +from os import environ +from os.path import expanduser +import socket +import ssl +import shlex +from sys import argv, stdin, stdout, exit +from multiprocessing import Process +from select import select +from io import BytesIO +import subprocess +from time import sleep + +import logging + + +logger = logging.getLogger('notmuch-tcp-server') +logger.setLevel(logging.DEBUG) +lh = logging.StreamHandler() +lh.setLevel(logging.DEBUG) +lh.setFormatter(logging.Formatter( fmt = '{levelname} - {message}', style = '{' )) +logger.addHandler(lh) + + +port = environ.get('NOTMUCH_TCP') +host = environ.get('NOTMUCH_HOST') +hostname = socket.gethostname() + +if port is None: + port = 2010 +if host is None: + host = "odin.asgard.yggdrasil" + +sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +sslcontext.load_verify_locations(cafile = expanduser('~/.notmuch-tcp/ca.pem')) +sslcontext.load_cert_chain(certfile = expanduser(f"~/.notmuch-tcp/{hostname}.pem"), keyfile = expanduser(f"~/.notmuch-tcp/{hostname}.key")) + +s = None +for res in socket.getaddrinfo(host, int(port), socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE): + af, socktype, proto, canonname, sa = res + try: + s = socket.socket(af, socktype, proto) + except OSError as msg: + logger.error(f"Could not create socket: {msg}") + s = None + continue + try: + s.bind(sa) + s.listen() + except OSError as msg: + logger.error(f"Could not bind socket: {msg}") + s.close() + s = None + continue + break +if s is None: + logger.error('Could not open any sockets') + exit(1) + +with s as sock: + with sslcontext.wrap_socket(sock, server_side = True) as ssock: + while True: + logger.debug('waiting...') + conn = None + try: + conn, client_addr = ssock.accept() + client_host, client_port = client_addr + logger.info(f"Connected: {client_host}:{client_port}") + + with BytesIO() as buffer: + while True: + resp = conn.recv(256) + buffer.write(resp) + buffer.seek(0) + start_index = 0 + for line in buffer: + start_index += len(line) + escaped_args = line[:-1].decode() + break + if start_index: + buffer.seek(start_index) + remaining = buffer.read() + buffer.truncate(0) + buffer.seek(0) + buffer.write(remaining) + break + else: + buffer.seek(0, 2) + + logger.info(f"Arguments: {escaped_args}") + + sproc = subprocess.Popen(["notmuch"] + shlex.split(escaped_args), shell = False, stdin = subprocess.PIPE, stdout = subprocess.PIPE) + + def send_stdout(): + while True: + to_send = sproc.stdout.read(256) + logger.debug(f"Sending: {to_send}") + + if to_send: + conn.sendall(to_send) + else: + logger.debug(f"Done sending") + break + + def recv_stdin(): + remaining = buffer.read() + logger.debug(f"Received (buffered): {remaining}") + sproc.stdin.write(remaining) + + while True: + logger.debug("Waiting on input...") + ready = select([conn], [], [], 5) + if ready[0]: + resp = conn.recv(256) + logger.debug(f"Received: {resp}") + + if len(resp) <= 0: + break + + sproc.stdin.write(resp) + else: + break + logger.debug(f"Done receiving") + sproc.stdin.close() + + sleep(5) + + sproc.kill() + + send = Process(target = send_stdout) + recv = Process(target = recv_stdin) + send.start() + recv.start() + sproc_res = sproc.wait() + logger.info(f"Subprocess result: {sproc_res}") + send.join() + recv.terminate() + logger.debug(f"Handled I/O") + finally: + if conn is not None: + conn.close() diff --git a/worktime.py b/worktime.py index 9c72d30..19e2186 100755 --- a/worktime.py +++ b/worktime.py @@ -60,7 +60,6 @@ class TogglAPI(object): return now - start if start <= now else None class Worktime(object): - time_to_work = timedelta() time_worked = timedelta() running_entry = None now = datetime.now(tzlocal()) @@ -82,34 +81,40 @@ class Worktime(object): hours_per_week = float(config.get('WORKTIME', 'HoursPerWeek', fallback=40)) workdays = set([int(d.strip()) for d in config.get('WORKTIME', 'Workdays', fallback='1,2,3,4,5').split(',')]) - hours_per_day = hours_per_week / len(workdays) + time_per_day = timedelta(hours = hours_per_week) / len(workdays) - holidays = set() + holidays = dict() for year in range(start_date.year, end_date.year + 1): y_easter = datetime.combine(easter(year), time(), tzinfo=tzlocal()) # Legal holidays in munich, bavaria - holidays.add(datetime(year, 1, 1, tzinfo=tzlocal()).date()) - holidays.add(datetime(year, 1, 6, tzinfo=tzlocal()).date()) - holidays.add((y_easter+timedelta(days=-2)).date()) - holidays.add((y_easter+timedelta(days=+1)).date()) - holidays.add(datetime(year, 5, 1, tzinfo=tzlocal()).date()) - holidays.add((y_easter+timedelta(days=+39)).date()) - holidays.add((y_easter+timedelta(days=+50)).date()) - holidays.add((y_easter+timedelta(days=+60)).date()) - holidays.add(datetime(year, 8, 15, tzinfo=tzlocal()).date()) - holidays.add(datetime(year, 10, 3, tzinfo=tzlocal()).date()) - holidays.add(datetime(year, 11, 1, tzinfo=tzlocal()).date()) - holidays.add(datetime(year, 12, 25, tzinfo=tzlocal()).date()) - holidays.add(datetime(year, 12, 26, tzinfo=tzlocal()).date()) + holidays[datetime(year, 1, 1, tzinfo=tzlocal()).date()] = time_per_day + holidays[datetime(year, 1, 6, tzinfo=tzlocal()).date()] = time_per_day + holidays[(y_easter+timedelta(days=-2)).date()] = time_per_day + holidays[(y_easter+timedelta(days=+1)).date()] = time_per_day + holidays[datetime(year, 5, 1, tzinfo=tzlocal()).date()] = time_per_day + holidays[(y_easter+timedelta(days=+39)).date()] = time_per_day + holidays[(y_easter+timedelta(days=+50)).date()] = time_per_day + holidays[(y_easter+timedelta(days=+60)).date()] = time_per_day + holidays[datetime(year, 8, 15, tzinfo=tzlocal()).date()] = time_per_day + holidays[datetime(year, 10, 3, tzinfo=tzlocal()).date()] = time_per_day + holidays[datetime(year, 11, 1, tzinfo=tzlocal()).date()] = time_per_day + holidays[datetime(year, 12, 25, tzinfo=tzlocal()).date()] = time_per_day + holidays[datetime(year, 12, 26, tzinfo=tzlocal()).date()] = time_per_day try: with open(f"{config_dir}/excused", 'r') as excused: for line in excused: stripped_line = line.strip() if stripped_line: - holidays.add(datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()) + splitLine = stripped_line.split(' ') + if len(splitLine) == 2: + [hours, date] = splitLine + day = datetime.strptime(date, date_format).replace(tzinfo=tzlocal()).date() + holidays[day] = timedelta(hours = float(hours)) + else: + holidays[datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()] = time_per_day except IOError as e: if e.errno != 2: raise e @@ -130,7 +135,7 @@ class Worktime(object): raise e - days_to_work = set() + days_to_work = dict() start_day = start_date.date() end_day = end_date.date() @@ -138,14 +143,18 @@ class Worktime(object): end_day = max(end_day, max(list(pull_forward))) for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: - if day.isoweekday() in workdays and not day in holidays: - days_to_work.add(day) + if day.isoweekday() in workdays: + time_to_work = time_per_day + if day in holidays.keys(): + time_to_work -= holidays[day] + if time_to_work > timedelta(): + days_to_work[day] = time_to_work self.is_workday = self.now.date() in days_to_work - self.time_to_work = timedelta(hours = len([day for day in days_to_work if day <= end_date.date()]) * hours_per_day) + self.time_to_work = sum([days_to_work[day] for day in days_to_work.keys() if day <= end_date.date()], timedelta()) for day in list(pull_forward): - days_forward = set([d for d in days_to_work if d >= end_date.date() and d < day and not d in pull_forward]) + days_forward = set([d for d in days_to_work.keys() if d >= end_date.date() and d < day and not d in pull_forward]) hours_per_day_forward = pull_forward[day] / len(days_forward) if len(days_forward) > 0 else timedelta() days_forward.discard(end_date.date()) self.time_pulled_forward += pull_forward[day] - hours_per_day_forward * len(days_forward) -- cgit v1.2.3