# SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2021, dsc@xmr.pm from typing import List, Optional, Union import re import shutil import os import sys import random import time import asyncio from asyncio.subprocess import Process from io import TextIOWrapper import mutagen import aiofiles import aiohttp import jinja2 from jinja2 import Environment, PackageLoader, select_autoescape from aiocache import cached, Cache from aiocache.serializers import PickleSerializer import settings class AsyncSubProcess(object): def __init__(self, *args, **kwargs): self.proc: Process = None self.max_buffer: int = 1000 self.buffer = [] @property async def is_running(self) -> bool: return self.proc and self.proc.returncode is None async def run(self, args: List[str], ws_type_prefix: str): loop = asyncio.get_event_loop() read_stdout, write_stdout = os.pipe() read_stderr, write_stderr = os.pipe() self.proc = await asyncio.create_subprocess_exec( *args, stdin=asyncio.subprocess.PIPE, stdout=write_stdout, stderr=write_stderr, cwd=settings.cwd ) os.close(write_stdout) os.close(write_stderr) f_stdout = os.fdopen(read_stdout, "r") f_stderr = os.fdopen(read_stderr, "r") try: await asyncio.gather( self.consume(fd=f_stdout, _type='stdout', _type_prefix=ws_type_prefix), self.consume(fd=f_stderr, _type='stderr', _type_prefix=ws_type_prefix), self.proc.communicate() ) finally: f_stdout.close() f_stderr.close() async def consume(self, fd: TextIOWrapper, _type: str, _type_prefix: str): from ircradio.factory import app import wow.websockets as websockets _type_int = 0 if _type == "stdout" else 1 reader = asyncio.StreamReader() loop = asyncio.get_event_loop() await loop.connect_read_pipe( lambda: asyncio.StreamReaderProtocol(reader), fd ) async for line in reader: line = line.strip() msg = line.decode(errors="ignore") _logger = app.logger.info if _type_int == 0 else app.logger.error _logger(msg) self.buffer.append((int(time.time()), _type_int, msg)) if len(self.buffer) >= self.max_buffer: self.buffer.pop(0) await websockets.broadcast( message=line, message_type=f"{_type_prefix}_{_type}", ) async def loopyloop(secs: int, func, after_func=None): while True: result = await func() if after_func: await after_func(result) await asyncio.sleep(secs) def jinja2_render(template_name: str, **data): loader = jinja2.FileSystemLoader(searchpath=[ os.path.join(settings.cwd, "utils"), os.path.join(settings.cwd, "ircradio/templates") ]) env = jinja2.Environment(loader=loader, autoescape=select_autoescape()) template = env.get_template(template_name) return template.render(**data) async def write_file(fn: str, data: Union[str, bytes], mode="w"): async with aiofiles.open(fn, mode=mode) as f: f.write(data) def write_file_sync(fn: str, data: bytes): f = open(fn, "wb") f.write(data) f.close() async def executeSQL(sql: str, params: tuple = None): from ircradio.factory import db async with db.pool.acquire() as connection: async with connection.transaction(): result = connection.fetch(sql, params) return result def systemd_servicefile( name: str, description: str, user: str, group: str, path_executable: str, args_executable: str, env: str = None ) -> bytes: template = jinja2_render( "acme.service.jinja2", name=name, description=description, user=user, group=group, env=env, path_executable=path_executable, args_executable=args_executable ) return template.encode() def liquidsoap_version(): ls = shutil.which("liquidsoap") f = os.popen(f"{ls} --version 2>/dev/null").read() if not f: print("please install liquidsoap\n\napt install -y liquidsoap") sys.exit() f = f.lower() match = re.search(r"liquidsoap (\d+.\d+.\d+)", f) if not match: return return match.groups()[0] def liquidsoap_check_symlink(): # msg = """ # Due to a bug you need to create this symlink: # # $ sudo ln -s /usr/share/liquidsoap/ /usr/share/liquidsoap/1.4.1 # # info: https://github.com/savonet/liquidsoap/issues/1224 # """ # version = liquidsoap_version() # if not os.path.exists(f"/usr/share/liquidsoap/{version}"): # print(msg) # sys.exit() pass async def httpget(url: str, json=True, timeout: int = 5, raise_for_status=True, verify_tls=True): headers = {"User-Agent": random_agent()} opts = {"timeout": aiohttp.ClientTimeout(total=timeout)} async with aiohttp.ClientSession(**opts) as session: async with session.get(url, headers=headers, ssl=verify_tls) as response: if raise_for_status: response.raise_for_status() result = await response.json() if json else await response.text() if result is None or (isinstance(result, str) and result == ''): raise Exception("empty response from request") return result def random_agent(): from ircradio.factory import user_agents return random.choice(user_agents) class Price: def __init__(self): self.usd = 0.3 def calculate(self): pass async def wownero_usd_price_loop(self): while True: self.usd = await Price.wownero_usd_price() asyncio.sleep(1200) @staticmethod async def wownero_usd_price(): url = "https://api.coingecko.com/api/v3/simple/price?ids=wownero&vs_currencies=usd" blob = await httpget(url, json=True) return blob.get('usd', 0) #@cached(ttl=3600, cache=Cache.MEMORY, # key_builder=lambda *args, **kw: f"mutagen_file_{args[1]}", # serializer=PickleSerializer()) async def mutagen_file(path): from quart import current_app if current_app: return await current_app.sync_to_async(mutagen.File)(path) else: return mutagen.File(path) def print_banner(): print("""\033[91m ▪ ▄▄▄ ▄▄· ▄▄▄ ▄▄▄· ·▄▄▄▄ ▪ ██ ▀▄ █·▐█ ▌▪▀▄ █·▐█ ▀█ ██▪ ██ ██ ▪ ▐█·▐▀▀▄ ██ ▄▄▐▀▀▄ ▄█▀▀█ ▐█· ▐█▌▐█· ▄█▀▄ ▐█▌▐█•█▌▐███▌▐█•█▌▐█ ▪▐▌██. ██ ▐█▌▐█▌.▐▌ ▀▀▀.▀ ▀·▀▀▀ .▀ ▀ ▀ ▀ ▀▀▀▀▀• ▀▀▀ ▀█▄▀▪\033[0m """.strip())