From 63c10720c6087950cf5e2ebae0d7939214426b5f Mon Sep 17 00:00:00 2001 From: kalzu rekku Date: Sun, 11 Feb 2024 09:05:43 +0200 Subject: [PATCH] Foobar WTF --- foobar/kiss.py | 49 +++++++++++++++ ircthing.py | 64 +++++++++----------- ircthing_core.py | 148 +++++++++++++++++++++++++--------------------- ircthing_utils.py | 60 +++++++++++++++---- 4 files changed, 208 insertions(+), 113 deletions(-) create mode 100644 foobar/kiss.py diff --git a/foobar/kiss.py b/foobar/kiss.py new file mode 100644 index 0000000..a4dc3d0 --- /dev/null +++ b/foobar/kiss.py @@ -0,0 +1,49 @@ +import queue +import time +import threading + +def main(): + print("Hello There") + + que = queue.Queue() + event = threading.Event() + + #input(que) + input_thread = threading.Thread(target=input, args=(que,event)) + input_thread.daemon = True + input_thread.start() + + #output(que) + output_thread = threading.Thread(target=output, args=(que,event)) + output_thread.daemon = True + output_thread.start() + + for num in range(21): + print(f"wait till full: {num}/20", end='\r') + time.sleep(0.5) + print("\nDone!") + event.set() + +def input(que, event): + loop = 0 + while not event: + message = f"{time.time()} | Hi! {loop}" + que.put(message) + loop = loop + 1 + time.sleep(1) + +def output(que, event): + file = '/tmp/test_out' + with open(file, 'a') as out_file: + while not event: + message = que.get() + out_file.write(f"{message}\r\n") + out_file.flush() + time.sleep(1) + out_file.close() + + +if __name__ == "__main__": + main() + print("Bye now!") + exit(0) diff --git a/ircthing.py b/ircthing.py index 82e36bf..8b23fb1 100644 --- a/ircthing.py +++ b/ircthing.py @@ -1,28 +1,43 @@ +""" +My attempt to mimic Suckless.org's ii (irc it) software +""" +import sys +import time +import multiprocessing from ircthing_core import irc_router, connect_to_irc_server from ircthing_utils import read_config, cli_args, base_path -import time -import os -import multiprocessing Processes = {} Stop_Toggle = multiprocessing.Event() + def clean_exit(): + """ + Sets the Stop_Toggle event to signal clean exit. + """ Stop_Toggle.set() + def main(): + """ + Main function to initialize irc connections specified on the configuration file. + + This will run each irc server connection in invidual sub process. + Hold the process handlers on list. + Kill them with Stop_Toggle. + """ root_path = base_path() # Get configuration file path if given - config_path = 'config.ini' + config_path = "config.ini" argument = cli_args() if argument: config_path = argument - # Read configuration + # Read configuration network_configs = read_config(config_path) ## Get irc socket for each network in configuration - ## Start thread for each socket + ## Start thread for each socket for network in network_configs: net_name = network["net_name"] server = network["server"] @@ -31,42 +46,21 @@ def main(): password = network["password"] print(f"{time.time()} | Found configs for {net_name} network.") - irc_socket, fifo_files, network_dir = connect_to_irc_server(root_path, net_name, server, port, nickname, password) + irc_socket, fifo_files, network_dir = connect_to_irc_server( + root_path, net_name, server, port, nickname, password + ) - router_instance = irc_router(fifo_files, irc_socket, server, nickname, network_dir) + router_instance = irc_router( + fifo_files, irc_socket, server, nickname, network_dir + ) Processes[net_name] = multiprocessing.Process(target=router_instance.start) Processes[net_name].daemon = True Processes[net_name].start() - main_handle(root_path) - + # Wait for all the sub processes to end. for process in Processes.values(): process.join() -def main_handle(path): - input = f"{path}/in" - output = f"{path}/out" - os.mkfifo(input) - os.mkfifo(output) - while True: - line = read_input(input) - write_output(output, line) - -def read_input(file): - with open(file, 'r') as input: - line = input.readline().strip() - if not line: - return - if line == "exit": - clean_exit() - return line - -def write_output(file, line): - with open(file, 'a') as output: - output.write(f"{time.time()} | {line}\r\n") - output.flush() - output.close() - if __name__ == "__main__": print(f"{time.time()} | Lets start!") @@ -77,4 +71,4 @@ if __name__ == "__main__": finally: clean_exit() print(f"{time.time()} | Bye!") - exit(0) + sys.exit(0) diff --git a/ircthing_core.py b/ircthing_core.py index 15f885c..eb34c43 100644 --- a/ircthing_core.py +++ b/ircthing_core.py @@ -1,9 +1,10 @@ import time import re -import asyncio import socket import os from ircthing_utils import make_files +from threading import Thread, Event + def connect_to_irc_server( base_path, @@ -35,6 +36,7 @@ def connect_to_irc_server( class irc_router: + def __init__(self, fifo_files, irc_socket, server, nickname, network_dir): self.fifo_files = fifo_files self.irc_socket = irc_socket @@ -42,83 +44,98 @@ class irc_router: self.nickname = nickname self.network_dir = network_dir self.channels = [] - self._loop = asyncio.new_event_loop() + self.threads = [] + self.disconnect_toggle = Event() def start(self): - try: - asyncio.set_event_loop(self._loop) - tasks = [self.user_listener(), self.irc_message_loop()] - self._loop.run_until_complete(asyncio.gather(*tasks)) - finally: - for file in self.fifo_files: - try: - os.close(file) - except: - # Errors are okay at this point. - pass - self._loop.close() + self.start_threads() - ## Main loop for reading irc inflow and routing to correct _out fifos. - ## Also handle ping/pong here - async def irc_message_loop(self): - output_files=[] - loop = asyncio.get_running_loop() - while True: + for file in self.fifo_files: + try: + os.close(file) + except: + # Errors are okay at this point. + pass + self.irc_send("QUIT") - for output in self.fifo_files: - if output.endswith('out'): - if output not in output_files: - print(f"{time.time()} | {self.server} has output file: {output}") - output_files.append(output) + def start_threads(self): + user_listener_thread = Thread(target=self.user_listener) + irc_listener_thread = Thread(target=self.irc_message_loop) + + self.threads.append(user_listener_thread) + self.threads.append(irc_listener_thread) + + # start the threads + for thread in self.threads: + thread.start() + + # wait for all the threads to reach end + for thread in self.threads: + thread.join() + + def get_output_fifos(self): + output_fifos = [] + for fifo in self.fifo_files: + if fifo.endswith("out") and fifo not in output_fifos: + output_fifos.append(fifo) + return output_fifos + + def write_to_server_out(self, message): + server_out = os.path.join(self.network_dir, "out") + if os.access(server_out, os.W_OK): + self.write_to_out(server_out, message) + else: + print(f"{time.time()} | {self.server} | {message}") + + def irc_message_loop(self): + while not self.disconnect_toggle.is_set(): try: - irc_input = await loop.sock_recv(self.irc_socket, 2048) + irc_input = self.irc_socket.recv(2048) irc_input = irc_input.decode("utf-8") except Exception as e: print(f"Error reading from socket: {e}") continue if irc_input: - ## - ## The main logic hapens here. - ## support for channel joins - ## and spliting the input to channel specifig channel/[in, out] locations - ## - if "PING" in irc_input: - self.irc_send(f"PONG :{self.server}") - self.write_to_out(output_files[0], f"{time.time()} | ping/pong") - continue + self.handle_irc_input(irc_input) + time.sleep(0.5) - if f":{self.server} 353" in irc_input: - ## We have joined a channel! - match = re.search(r'353 \S+ = (#\S+)', irc_input) - channel_join = match.group(1) - await self.make_files(channel_join) - continue + def handle_irc_input(self, irc_input): + if "PING" in irc_input: + self.irc_send(f"PONG :{self.server}") + self.write_to_server_out("ping/pong") + elif f":{self.server} 353" in irc_input: + self.handle_channel_join(irc_input) + elif "PRIVMSG" in irc_input: + self.handle_privmsg(irc_input) + else: + self.write_to_server_out(irc_input) - if "PRIVMSG" in irc_input: - if self.if_input_belongs_to_channel(irc_input): - continue + def handle_channel_join(self, irc_input): + ## We have joined a channel! + channel = re.search(r"353 \S+ = (#\S+)", irc_input).group(1) + self.make_files(channel) - self.write_to_out(output_files[0], irc_input) + def handle_privmsg(self, irc_input): + if not self.input_belongs_to_channel(irc_input): + self.write_to_server_out(irc_input) - await asyncio.sleep(0.1) - - def if_input_belongs_to_channel(self, input): + def input_belongs_to_channel(self, input): + output_files = self.get_output_fifos() # We know that input includes "PRIVMSG" # But to where it shoudl go? - message = re.search(r':.*:(.*)', input).group(1) - input_user = re.search(r'^:(\S+)', input).group(1) - input_channel = re.search(r'PRIVMSG (\S+) :', input) - done=False - for file in self.fifo_files: - if input_channel.group(1) in file: - if file.endswith("out"): - self.write_to_out(file, f"{input_user}: {message}") - done=True + message = re.search(r":.*:(.*)", input).group(1) + input_user = re.search(r"^:(\S+)", input).group(1) + input_channel = re.search(r"PRIVMSG (\S+) :", input).group(1) + done = False + for file in output_files: + if input_channel in file: + self.write_to_out(file, f"{input_user}: {message}") + done = True return done - async def make_files(self, channel): + def make_files(self, channel): network_path = os.path.dirname(self.fifo_files[1]) channel_dir = os.path.join(network_path, channel) os.makedirs(channel_dir, exist_ok=True) @@ -134,7 +151,6 @@ class irc_router: fifo_files.append(f"{channel_dir}/in") fifo_files.append(f"{channel_dir}/out") self.fifo_files.extend(fifo_files) - await asyncio.sleep(1) def write_to_out(self, file, input): if file == None: @@ -149,29 +165,28 @@ class irc_router: self.irc_socket.send(bytes(f"{message}\r\n", "utf-8")) print(f"{time.time()} | Send: {message}") - async def async_readline(self, file, queue): + def async_readline(self, file, queue): print(f"Trying to read from file {file}") try: while True: - with open(file, mode='r') as in_file: + with open(file, mode="r") as in_file: line = in_file.readline().strip() if not line: print("There was no line!") continue - await queue.put(line) + queue.put(line) in_file.close() - await asyncio.sleep(0.5) except Exception as e: print(f"ERROR IN async_readline func: {e}") - async def process_user_input(self, queue): + def process_user_input(self, queue): print("Processing of user input may start!") while True: line = await queue.get() print(f"{time.time()} | trying to send: {line} to {self.server}") self.irc_send(line) - await asyncio.sleep(0.2) + time.sleep(0.5) async def user_listener(self): print("User Listener is HERE!") @@ -189,5 +204,4 @@ class irc_router: # Wait for all tasks to complete await asyncio.gather(*tasks) finally: - self.irc_send('QUIT') - + self.irc_send("QUIT") diff --git a/ircthing_utils.py b/ircthing_utils.py index 5c52433..fd6f0f8 100644 --- a/ircthing_utils.py +++ b/ircthing_utils.py @@ -1,23 +1,53 @@ +""" +ircthing_utils + +Some functions that make life nicer. +""" + import configparser import argparse import os import sys +from typing import Union, List, Tuple -def base_path(): + +def base_path() -> str: + """ + Returns location that the script will use as root for its dir structure + Returns: + str: path for run time files + """ my_name = sys.argv[0] my_name_pyless, _ = os.path.splitext(my_name) - return f'/tmp/{my_name_pyless}' + return f"/tmp/{my_name_pyless}" -def cli_args(): - parser = argparse.ArgumentParser(description="Usage: python3.11 ircthing.py /myconfig.ini") - parser.add_argument('config', help="Path to the configuration file.") + +def cli_args() -> Union[str, None]: + """ + Parse arguments and provide small usage message + Returns: + str or None: path to config file + """ + parser = argparse.ArgumentParser( + description="Usage: python3.11 ircthing.py /myconfig.ini" + ) + parser.add_argument("config", help="Path to the configuration file.") args = parser.parse_args() if args.config: return args.config return None -def read_config(config_path): + +def read_config(config_path: str) -> List[dict]: + """ + Read configuration file and return list of dicts. + Dictionaries hold information about irc networks/server to connecto to. + args: + str: Path to the configuration file. + Returns: + list[{}, {}]: List of dictionaries. + """ config = configparser.ConfigParser() config.read(config_path) @@ -43,12 +73,21 @@ def read_config(config_path): } network_configs.append(network_config) return network_configs - except Exception as e: - print(f"Failure while reading configuration file. {e}") - exit(1) + except Exception as config_read_error: + print(f"Failure while reading configuration file. {config_read_error}") + sys.exit(1) -def make_files(path, net_name): +def make_files(path: str, net_name: str) -> Tuple[List[str], str]: + """ + Make directories and fifo files need to make irc server connection. + Args: + str: The root path for run time files. + str: The network name of the irc server we are joining + Returns: + list: List containing the fio files created. + str: Path to the directory created for the network connection. + """ os.makedirs(path, exist_ok=True) server_dir = os.path.join(path, net_name) os.makedirs(server_dir, exist_ok=True) @@ -64,4 +103,3 @@ def make_files(path, net_name): fifo_files.append(f"{server_dir}/in") fifo_files.append(f"{server_dir}/out") return fifo_files, server_dir -