mirror of
https://github.com/onyx-and-iris/vban-cmd-python.git
synced 2026-03-03 08:49:09 +00:00
add a ratelimit decorator to {VbanCmd}.sendtext()
ip kwarg renamed to host.
This commit is contained in:
parent
cf66ae252c
commit
86d0aa91c3
@ -84,18 +84,20 @@ class FactoryBase(VbanCmd):
|
|||||||
|
|
||||||
def __init__(self, kind_id: str, **kwargs):
|
def __init__(self, kind_id: str, **kwargs):
|
||||||
defaultkwargs = {
|
defaultkwargs = {
|
||||||
'ip': 'localhost',
|
'host': 'localhost',
|
||||||
'port': 6980,
|
'port': 6980,
|
||||||
'streamname': 'Command1',
|
'streamname': 'Command1',
|
||||||
'bps': 256000,
|
'bps': 256000,
|
||||||
'channel': 0,
|
'channel': 0,
|
||||||
'ratelimit': 0.01,
|
'script_ratelimit': 0.05, # 20 commands per second, to avoid overloading Voicemeeter
|
||||||
'timeout': 5,
|
'timeout': 5, # timeout on socket operations, in seconds
|
||||||
'disable_rt_listeners': False,
|
'disable_rt_listeners': False,
|
||||||
'sync': False,
|
'sync': False,
|
||||||
'pdirty': False,
|
'pdirty': False,
|
||||||
'ldirty': False,
|
'ldirty': False,
|
||||||
}
|
}
|
||||||
|
if 'ip' in kwargs:
|
||||||
|
defaultkwargs['host'] = kwargs.pop('ip') # for backwards compatibility
|
||||||
if 'subs' in kwargs:
|
if 'subs' in kwargs:
|
||||||
defaultkwargs |= kwargs.pop('subs') # for backwards compatibility
|
defaultkwargs |= kwargs.pop('subs') # for backwards compatibility
|
||||||
kwargs = defaultkwargs | kwargs
|
kwargs = defaultkwargs | kwargs
|
||||||
|
|||||||
@ -1,6 +1,23 @@
|
|||||||
|
import time
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
|
|
||||||
|
def ratelimit(func):
|
||||||
|
"""ratelimit decorator for {VbanCmd}.sendtext, to prevent flooding the network with script requests."""
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
self, *rem = args
|
||||||
|
if self.script_ratelimit > 0:
|
||||||
|
now = time.time()
|
||||||
|
elapsed = now - self._last_script_request_time
|
||||||
|
if elapsed < self.script_ratelimit:
|
||||||
|
time.sleep(self.script_ratelimit - elapsed)
|
||||||
|
self._last_script_request_time = time.time()
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def cache_bool(func, param):
|
def cache_bool(func, param):
|
||||||
"""Check cache for a bool prop"""
|
"""Check cache for a bool prop"""
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from .packet.headers import (
|
|||||||
)
|
)
|
||||||
from .packet.ping0 import VbanPing0Payload, VbanServerType
|
from .packet.ping0 import VbanPing0Payload, VbanServerType
|
||||||
from .subject import Subject
|
from .subject import Subject
|
||||||
from .util import bump_framecounter, deep_merge
|
from .util import bump_framecounter, deep_merge, ratelimit
|
||||||
from .worker import Producer, Subscriber, Updater
|
from .worker import Producer, Subscriber, Updater
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -38,7 +38,7 @@ class VbanCmd(abc.ABC):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.logger = logger.getChild(self.__class__.__name__)
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.event = Event({k: kwargs.pop(k) for k in ('pdirty', 'ldirty')})
|
self.event = Event({k: kwargs.pop(k) for k in ('pdirty', 'ldirty')})
|
||||||
if not kwargs['ip']:
|
if not kwargs['host']:
|
||||||
kwargs |= self._conn_from_toml()
|
kwargs |= self._conn_from_toml()
|
||||||
for attr, val in kwargs.items():
|
for attr, val in kwargs.items():
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
@ -52,9 +52,9 @@ class VbanCmd(abc.ABC):
|
|||||||
self.cache = {}
|
self.cache = {}
|
||||||
self._pdirty = False
|
self._pdirty = False
|
||||||
self._ldirty = False
|
self._ldirty = False
|
||||||
self._script = str()
|
|
||||||
self.stop_event = None
|
self.stop_event = None
|
||||||
self.producer = None
|
self.producer = None
|
||||||
|
self._last_script_request_time = 0
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -113,7 +113,7 @@ class VbanCmd(abc.ABC):
|
|||||||
self.producer.start()
|
self.producer.start()
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
"Successfully logged into VBANCMD {kind} with ip='{ip}', port={port}, streamname='{streamname}'".format(
|
"Successfully logged into VBANCMD {kind} with host='{host}', port={port}, streamname='{streamname}'".format(
|
||||||
**self.__dict__
|
**self.__dict__
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -149,8 +149,8 @@ class VbanCmd(abc.ABC):
|
|||||||
self.sock.settimeout(0.5)
|
self.sock.settimeout(0.5)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.sock.sendto(ping_packet, (socket.gethostbyname(self.ip), self.port))
|
self.sock.sendto(ping_packet, (socket.gethostbyname(self.host), self.port))
|
||||||
self.logger.debug(f'PING sent to {self.ip}:{self.port}')
|
self.logger.debug(f'PING sent to {self.host}:{self.port}')
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
response_count = 0
|
response_count = 0
|
||||||
@ -193,11 +193,13 @@ class VbanCmd(abc.ABC):
|
|||||||
f'PING timeout after {timeout}s, received {response_count} non-PONG packets'
|
f'PING timeout after {timeout}s, received {response_count} non-PONG packets'
|
||||||
)
|
)
|
||||||
raise VBANCMDConnectionError(
|
raise VBANCMDConnectionError(
|
||||||
f'PING timeout: No response from {self.ip}:{self.port} after {timeout}s'
|
f'PING timeout: No response from {self.host}:{self.port} after {timeout}s'
|
||||||
)
|
)
|
||||||
|
|
||||||
except socket.gaierror as e:
|
except socket.gaierror as e:
|
||||||
raise VBANCMDConnectionError(f'Unable to resolve hostname {self.ip}') from e
|
raise VBANCMDConnectionError(
|
||||||
|
f'Unable to resolve hostname {self.host}'
|
||||||
|
) from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise VBANCMDConnectionError(f'PING failed: {e}') from e
|
raise VBANCMDConnectionError(f'PING failed: {e}') from e
|
||||||
finally:
|
finally:
|
||||||
@ -230,7 +232,7 @@ class VbanCmd(abc.ABC):
|
|||||||
framecounter=self._get_next_framecounter(),
|
framecounter=self._get_next_framecounter(),
|
||||||
payload=payload,
|
payload=payload,
|
||||||
),
|
),
|
||||||
(socket.gethostbyname(self.ip), self.port),
|
(socket.gethostbyname(self.host), self.port),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_rt(self, cmd: str, val: Union[str, float]):
|
def _set_rt(self, cmd: str, val: Union[str, float]):
|
||||||
@ -238,6 +240,7 @@ class VbanCmd(abc.ABC):
|
|||||||
self._send_request(f'{cmd}={val};')
|
self._send_request(f'{cmd}={val};')
|
||||||
self.cache[cmd] = val
|
self.cache[cmd] = val
|
||||||
|
|
||||||
|
@ratelimit
|
||||||
def sendtext(self, script) -> str | None:
|
def sendtext(self, script) -> str | None:
|
||||||
"""Sends a multiple parameter string over a network."""
|
"""Sends a multiple parameter string over a network."""
|
||||||
self._send_request(script)
|
self._send_request(script)
|
||||||
@ -252,12 +255,10 @@ class VbanCmd(abc.ABC):
|
|||||||
except TimeoutError as e:
|
except TimeoutError as e:
|
||||||
self.logger.exception(f'Timeout waiting for matrix response: {e}')
|
self.logger.exception(f'Timeout waiting for matrix response: {e}')
|
||||||
raise VBANCMDConnectionError(
|
raise VBANCMDConnectionError(
|
||||||
f'Timeout waiting for response from {self.ip}:{self.port}'
|
f'Timeout waiting for response from {self.host}:{self.port}'
|
||||||
) from e
|
) from e
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
time.sleep(self.DELAY)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
"""Returns the type of Voicemeeter installation."""
|
"""Returns the type of Voicemeeter installation."""
|
||||||
|
|||||||
@ -27,7 +27,6 @@ class Subscriber(threading.Thread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while not self.stopped():
|
while not self.stopped():
|
||||||
try:
|
|
||||||
for nbs in NBS:
|
for nbs in NBS:
|
||||||
sub_packet = VbanSubscribeHeader().to_bytes(
|
sub_packet = VbanSubscribeHeader().to_bytes(
|
||||||
nbs, self._remote._get_next_framecounter()
|
nbs, self._remote._get_next_framecounter()
|
||||||
@ -35,11 +34,6 @@ class Subscriber(threading.Thread):
|
|||||||
self._remote.sock.sendto(
|
self._remote.sock.sendto(
|
||||||
sub_packet, (self._remote.ip, self._remote.port)
|
sub_packet, (self._remote.ip, self._remote.port)
|
||||||
)
|
)
|
||||||
except TimeoutError as e:
|
|
||||||
self.logger.exception(f'{type(e).__name__}: {e}')
|
|
||||||
raise VBANCMDConnectionError(
|
|
||||||
f'timeout sending subscription to {self._remote.ip}:{self._remote.port}'
|
|
||||||
) from e
|
|
||||||
|
|
||||||
self.wait_until_stopped(10)
|
self.wait_until_stopped(10)
|
||||||
self.logger.debug(f'terminating {self.name} thread')
|
self.logger.debug(f'terminating {self.name} thread')
|
||||||
@ -128,7 +122,6 @@ class Producer(threading.Thread):
|
|||||||
self.queue.put('pdirty')
|
self.queue.put('pdirty')
|
||||||
if self._remote.event.ldirty:
|
if self._remote.event.ldirty:
|
||||||
self.queue.put('ldirty')
|
self.queue.put('ldirty')
|
||||||
# time.sleep(self._remote.ratelimit)
|
|
||||||
self.logger.debug(f'terminating {self.name} thread')
|
self.logger.debug(f'terminating {self.name} thread')
|
||||||
self.queue.put(None)
|
self.queue.put(None)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user