Merge branch 'dev'
このコミットが含まれているのは:
コミット
288c31a03c
|
@ -47,6 +47,7 @@ def create_app(config_file):
|
||||||
app.stream_title = None
|
app.stream_title = None
|
||||||
app.stream_uptime = None
|
app.stream_uptime = None
|
||||||
app.stream_viewership = None
|
app.stream_viewership = None
|
||||||
|
app.last_info_task = None
|
||||||
|
|
||||||
# Background tasks' asyncio.sleep tasks, cancelled on shutdown
|
# Background tasks' asyncio.sleep tasks, cancelled on shutdown
|
||||||
app.background_sleep = set()
|
app.background_sleep = set()
|
||||||
|
|
|
@ -62,11 +62,16 @@ class ArgsInt(Args):
|
||||||
return self.spec, 1, [n]
|
return self.spec, 1, [n]
|
||||||
|
|
||||||
class ArgsString(Args):
|
class ArgsString(Args):
|
||||||
|
def transform_string(self, string):
|
||||||
|
return string
|
||||||
|
|
||||||
def consume(self, words, index):
|
def consume(self, words, index):
|
||||||
try:
|
try:
|
||||||
string = words[index]
|
string = words[index]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise NoParse(f'incomplete: expected string')
|
raise NoParse(f'incomplete: expected string')
|
||||||
|
else:
|
||||||
|
string = self.transform_string(string)
|
||||||
return self.spec, 1, [string]
|
return self.spec, 1, [string]
|
||||||
|
|
||||||
class ArgsJson(Args):
|
class ArgsJson(Args):
|
||||||
|
|
|
@ -20,10 +20,19 @@ class ArgsJsonTokenUser(ArgsJsonString):
|
||||||
raise NoParse(f'no user with token {token!r}')
|
raise NoParse(f'no user with token {token!r}')
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
class ArgsJsonHashUser(ArgsString):
|
||||||
|
def transform_string(self, token_hash):
|
||||||
|
for user in USERS:
|
||||||
|
if user['token_hash'] == token_hash:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise NoParse(f'no user with token_hash {token_hash!r}')
|
||||||
|
return user
|
||||||
|
|
||||||
def ArgsUser(spec):
|
def ArgsUser(spec):
|
||||||
return Str({
|
return Str({
|
||||||
'token': ArgsJsonTokenUser(spec),
|
'token': ArgsJsonTokenUser(spec),
|
||||||
#'hash': ArgsJsonHashUser(spec),
|
'hash': ArgsJsonHashUser(spec),
|
||||||
})
|
})
|
||||||
|
|
||||||
async def cmd_user_help():
|
async def cmd_user_help():
|
||||||
|
@ -31,19 +40,18 @@ async def cmd_user_help():
|
||||||
response = (
|
response = (
|
||||||
'Usage: user [show | attr USER | get USER ATTR | set USER ATTR VALUE]\n'
|
'Usage: user [show | attr USER | get USER ATTR | set USER ATTR VALUE]\n'
|
||||||
'Commands:\n'
|
'Commands:\n'
|
||||||
' user [show].......................show all users\' tokens\n'
|
' user [show]...................show all users\' tokens\n'
|
||||||
' user attr USER....................show names of a user\'s attributes\n'
|
' user attr USER................show names of a user\'s attributes\n'
|
||||||
' user get USER ATTR................show an attribute of a user\n'
|
' user get USER ATTR............show an attribute of a user\n'
|
||||||
' user set USER ATTR................set an attribute of a user\n'
|
' user set USER ATTR............set an attribute of a user\n'
|
||||||
' user eyes USER [show].............show a user\'s active video responses\n'
|
' user eyes USER [show].........show a user\'s active video responses\n'
|
||||||
' user eyes USER delete EYES_ID.....end a video response to a user\n'
|
' user eyes USER delete EYES_ID.end a video response to a user\n'
|
||||||
'Definitions:\n'
|
'Definitions:\n'
|
||||||
#' USER..............................={token TOKEN | hash HASH}\n'
|
' USER..........................={token TOKEN | hash HASH}\n'
|
||||||
' USER..............................=token TOKEN\n'
|
' TOKEN.........................a token, json string\n'
|
||||||
' TOKEN..............................a token, json string\n'
|
' HASH..........................a token hash\n'
|
||||||
#' HASH..............................a token hash\n'
|
' ATTR..........................a user attribute, re:[a-z0-9_]+\n'
|
||||||
' ATTR...............................a user attribute, re:[a-z0-9_]+\n'
|
' EYES_ID.......................a user\'s eyes_id, base 10 integer\n'
|
||||||
' EYES_ID............................a user\'s eyes_id, base 10 integer\n'
|
|
||||||
)
|
)
|
||||||
return normal, response
|
return normal, response
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import aiofiles
|
||||||
import m3u8
|
import m3u8
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
|
|
||||||
from anonstream.wrappers import ttl_cache, with_timestamp
|
from anonstream.wrappers import ttl_cache
|
||||||
|
|
||||||
CONFIG = current_app.config
|
CONFIG = current_app.config
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ const create_chat_message = (object) => {
|
||||||
const create_chat_user_name = (user) => {
|
const create_chat_user_name = (user) => {
|
||||||
const chat_user_name = document.createElement("span");
|
const chat_user_name = document.createElement("span");
|
||||||
chat_user_name.classList.add("chat-name");
|
chat_user_name.classList.add("chat-name");
|
||||||
chat_user_name.innerText = get_user_name({user});
|
chat_user_name.innerText = get_user_name({user}).replaceAll(/\r?\n/g, " ");
|
||||||
//chat_user_name.dataset.color = user.color; // not working in any browser
|
//chat_user_name.dataset.color = user.color; // not working in any browser
|
||||||
if (!user.broadcaster && user.name === null) {
|
if (!user.broadcaster && user.name === null) {
|
||||||
const b = document.createElement("b");
|
const b = document.createElement("b");
|
||||||
|
@ -648,6 +648,10 @@ const on_websocket_message = (event) => {
|
||||||
default_name = receipt.default;
|
default_name = receipt.default;
|
||||||
max_chat_scrollback = receipt.scrollback;
|
max_chat_scrollback = receipt.scrollback;
|
||||||
|
|
||||||
|
// if the chat is scrolled all the way to the bottom, make sure this is
|
||||||
|
// still the case after updating user names and tripcodes
|
||||||
|
at_bottom = chat_messages.scrollTop === chat_messages.scrollTopMax;
|
||||||
|
|
||||||
// update users
|
// update users
|
||||||
users = receipt.users;
|
users = receipt.users;
|
||||||
update_user_names();
|
update_user_names();
|
||||||
|
@ -655,6 +659,15 @@ const on_websocket_message = (event) => {
|
||||||
update_user_tripcodes();
|
update_user_tripcodes();
|
||||||
update_users_list()
|
update_users_list()
|
||||||
|
|
||||||
|
// ensure chat scroll (see above)
|
||||||
|
if (at_bottom) {
|
||||||
|
chat_messages.scrollTo({
|
||||||
|
left: 0,
|
||||||
|
top: chat_messages.scrollTopMax,
|
||||||
|
behavior: "instant",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// appearance form default values
|
// appearance form default values
|
||||||
const user = users[TOKEN_HASH];
|
const user = users[TOKEN_HASH];
|
||||||
if (user.name !== null) {
|
if (user.name !== null) {
|
||||||
|
@ -739,10 +752,26 @@ const on_websocket_message = (event) => {
|
||||||
for (const token_hash of Object.keys(receipt.users)) {
|
for (const token_hash of Object.keys(receipt.users)) {
|
||||||
users[token_hash] = receipt.users[token_hash];
|
users[token_hash] = receipt.users[token_hash];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the chat is scrolled all the way to the bottom, make sure this is
|
||||||
|
// still the case after updating user names and tripcodes
|
||||||
|
at_bottom = chat_messages.scrollTop === chat_messages.scrollTopMax;
|
||||||
|
|
||||||
|
// update users
|
||||||
update_user_names();
|
update_user_names();
|
||||||
update_user_colors();
|
update_user_colors();
|
||||||
update_user_tripcodes();
|
update_user_tripcodes();
|
||||||
update_users_list()
|
update_users_list()
|
||||||
|
|
||||||
|
// ensure chat scroll (see above)
|
||||||
|
if (at_bottom) {
|
||||||
|
chat_messages.scrollTo({
|
||||||
|
left: 0,
|
||||||
|
top: chat_messages.scrollTopMax,
|
||||||
|
behavior: "instant",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "rem-users":
|
case "rem-users":
|
||||||
|
|
|
@ -552,6 +552,9 @@ footer {
|
||||||
#chat-form_js[data-captcha] #chat-live__status [data-verbose="false"] {
|
#chat-form_js[data-captcha] #chat-live__status [data-verbose="false"] {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
#info_nojs {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
#nochat:target {
|
#nochat:target {
|
||||||
--chat-width: 0px;
|
--chat-width: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ def get_stream_uptime(rounded=True):
|
||||||
uptime = round(uptime, 2) if rounded else uptime
|
uptime = round(uptime, 2) if rounded else uptime
|
||||||
return uptime
|
return uptime
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def get_raw_viewership(timestamp):
|
def get_raw_viewership(timestamp):
|
||||||
users = get_watching_users(timestamp)
|
users = get_watching_users(timestamp)
|
||||||
return max(
|
return max(
|
||||||
|
@ -52,8 +52,8 @@ def get_raw_viewership(timestamp):
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_stream_uptime_and_viewership(for_websocket=False):
|
def get_stream_uptime_and_viewership(rounded=True, for_websocket=False):
|
||||||
uptime = get_stream_uptime()
|
uptime = get_stream_uptime(rounded=rounded)
|
||||||
if not for_websocket:
|
if not for_websocket:
|
||||||
viewership = None if uptime is None else get_raw_viewership()
|
viewership = None if uptime is None else get_raw_viewership()
|
||||||
result = (uptime, viewership)
|
result = (uptime, viewership)
|
||||||
|
|
|
@ -44,7 +44,7 @@ def with_period(period):
|
||||||
return periodically
|
return periodically
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_ROTATE_EYES'])
|
@with_period(CONFIG['TASK_ROTATE_EYES'])
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
async def t_delete_eyes(timestamp, iteration):
|
async def t_delete_eyes(timestamp, iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
return
|
return
|
||||||
|
@ -59,7 +59,7 @@ async def t_delete_eyes(timestamp, iteration):
|
||||||
user['eyes']['current'].pop(eyes_id)
|
user['eyes']['current'].pop(eyes_id)
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_ROTATE_USERS'])
|
@with_period(CONFIG['TASK_ROTATE_USERS'])
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
async def t_sunset_users(timestamp, iteration):
|
async def t_sunset_users(timestamp, iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
return
|
return
|
||||||
|
@ -102,7 +102,7 @@ async def t_expire_captchas(iteration):
|
||||||
CAPTCHAS.pop(digest)
|
CAPTCHAS.pop(digest)
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_ROTATE_WEBSOCKETS'])
|
@with_period(CONFIG['TASK_ROTATE_WEBSOCKETS'])
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
async def t_close_websockets(timestamp, iteration):
|
async def t_close_websockets(timestamp, iteration):
|
||||||
THRESHOLD = CONFIG['TASK_BROADCAST_PING'] * 1.5 + 4.0
|
THRESHOLD = CONFIG['TASK_BROADCAST_PING'] * 1.5 + 4.0
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
|
@ -130,56 +130,58 @@ async def t_broadcast_users_update(iteration):
|
||||||
broadcast_users_update()
|
broadcast_users_update()
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_BROADCAST_STREAM_INFO_UPDATE'])
|
@with_period(CONFIG['TASK_BROADCAST_STREAM_INFO_UPDATE'])
|
||||||
async def t_broadcast_stream_info_update(iteration):
|
@with_timestamp(precise=True)
|
||||||
|
async def t_broadcast_stream_info_update(timestamp, iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
title = await get_stream_title()
|
title = await get_stream_title()
|
||||||
uptime, viewership = get_stream_uptime_and_viewership()
|
uptime, viewership = get_stream_uptime_and_viewership(rounded=False)
|
||||||
current_app.stream_title = title
|
current_app.stream_title = title
|
||||||
current_app.stream_uptime = uptime
|
current_app.stream_uptime = uptime
|
||||||
current_app.stream_viewership = viewership
|
current_app.stream_viewership = viewership
|
||||||
else:
|
else:
|
||||||
payload = {}
|
info = {}
|
||||||
|
|
||||||
title = await get_stream_title()
|
title = await get_stream_title()
|
||||||
uptime, viewership = get_stream_uptime_and_viewership()
|
uptime, viewership = get_stream_uptime_and_viewership(rounded=False)
|
||||||
|
|
||||||
# Check if the stream title has changed
|
# Check if the stream title has changed
|
||||||
if current_app.stream_title != title:
|
if current_app.stream_title != title:
|
||||||
current_app.stream_title = title
|
current_app.stream_title = title
|
||||||
payload['title'] = title
|
info['title'] = title
|
||||||
|
|
||||||
# Check if the stream uptime has changed unexpectedly
|
# Check if the stream uptime has changed unexpectedly
|
||||||
if current_app.stream_uptime is None:
|
last_uptime, current_app.stream_uptime = (
|
||||||
expected_uptime = None
|
current_app.stream_uptime, uptime
|
||||||
|
)
|
||||||
|
if last_uptime is None:
|
||||||
|
projected_uptime = None
|
||||||
else:
|
else:
|
||||||
expected_uptime = (
|
last_info_task_ago = timestamp - current_app.last_info_task
|
||||||
current_app.stream_uptime
|
projected_uptime = last_uptime + last_info_task_ago
|
||||||
+ CONFIG['TASK_BROADCAST_STREAM_INFO_UPDATE']
|
if uptime is None and projected_uptime is None:
|
||||||
)
|
|
||||||
current_app.stream_uptime = uptime
|
|
||||||
if uptime is None and expected_uptime is None:
|
|
||||||
stats_changed = False
|
stats_changed = False
|
||||||
elif uptime is None or expected_uptime is None:
|
elif uptime is None or projected_uptime is None:
|
||||||
stats_changed = True
|
stats_changed = True
|
||||||
else:
|
else:
|
||||||
stats_changed = abs(uptime - expected_uptime) >= 0.5
|
stats_changed = abs(uptime - projected_uptime) >= 0.5
|
||||||
|
|
||||||
# Check if viewership has changed
|
# Check if viewership has changed
|
||||||
if current_app.stream_viewership != viewership:
|
if current_app.stream_viewership != viewership:
|
||||||
current_app.stream_viewership = viewership
|
current_app.stream_viewership = viewership
|
||||||
stats_changed = True
|
stats_changed = True
|
||||||
|
|
||||||
|
# Broadcast iff anything has changed
|
||||||
if stats_changed:
|
if stats_changed:
|
||||||
if uptime is None:
|
if uptime is None:
|
||||||
payload['stats'] = None
|
info['stats'] = None
|
||||||
else:
|
else:
|
||||||
payload['stats'] = {
|
info['stats'] = {
|
||||||
'uptime': uptime,
|
'uptime': round(uptime, 2),
|
||||||
'viewership': viewership,
|
'viewership': viewership,
|
||||||
}
|
}
|
||||||
|
if info:
|
||||||
|
broadcast(USERS, payload={'type': 'info', **info})
|
||||||
|
|
||||||
if payload:
|
current_app.last_info_task = timestamp
|
||||||
broadcast(USERS, payload={'type': 'info', **payload})
|
|
||||||
|
|
||||||
current_app.add_background_task(t_delete_eyes)
|
current_app.add_background_task(t_delete_eyes)
|
||||||
current_app.add_background_task(t_sunset_users)
|
current_app.add_background_task(t_sunset_users)
|
||||||
|
|
|
@ -75,13 +75,13 @@
|
||||||
animation: appear step-end both;
|
animation: appear step-end both;
|
||||||
}
|
}
|
||||||
#m1 {
|
#m1 {
|
||||||
animation-duration: {{ 600 - uptime }}s;
|
animation-delay: {{ 600 - uptime }}s;
|
||||||
}
|
}
|
||||||
#h0 {
|
#h0 {
|
||||||
animation-duration: {{ 3600 - uptime }}s;
|
animation-delay: {{ 3600 - uptime }}s;
|
||||||
}
|
}
|
||||||
#h1 {
|
#h1 {
|
||||||
animation-duration: {{ 36000 - uptime }}s;
|
animation-delay: {{ 36000 - uptime }}s;
|
||||||
}
|
}
|
||||||
#uptime-dynamic {
|
#uptime-dynamic {
|
||||||
animation: disappear step-end {{ 360000 - uptime }}s forwards;
|
animation: disappear step-end {{ 360000 - uptime }}s forwards;
|
||||||
|
@ -95,12 +95,15 @@
|
||||||
@keyframes appear {
|
@keyframes appear {
|
||||||
from {
|
from {
|
||||||
width: 0;
|
width: 0;
|
||||||
|
height: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes disappear {
|
@keyframes disappear {
|
||||||
to {
|
to {
|
||||||
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,16 +127,16 @@ def change_tripcode(user, password, dry_run=False):
|
||||||
def delete_tripcode(user):
|
def delete_tripcode(user):
|
||||||
user['tripcode'] = None
|
user['tripcode'] = None
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def see(timestamp, user):
|
def see(timestamp, user):
|
||||||
user['last']['seen'] = timestamp
|
user['last']['seen'] = timestamp
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def watched(timestamp, user):
|
def watched(timestamp, user):
|
||||||
user['last']['seen'] = timestamp
|
user['last']['seen'] = timestamp
|
||||||
user['last']['watching'] = timestamp
|
user['last']['watching'] = timestamp
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def get_all_users_for_websocket(timestamp):
|
def get_all_users_for_websocket(timestamp):
|
||||||
return {
|
return {
|
||||||
user['token_hash']: get_user_for_websocket(user)
|
user['token_hash']: get_user_for_websocket(user)
|
||||||
|
@ -160,7 +160,7 @@ def verify(user, digest, answer):
|
||||||
|
|
||||||
return verification_happened
|
return verification_happened
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def deverify(timestamp, user):
|
def deverify(timestamp, user):
|
||||||
if not user['verified']:
|
if not user['verified']:
|
||||||
return
|
return
|
||||||
|
@ -182,7 +182,7 @@ def _update_presence(timestamp, user):
|
||||||
USERS_UPDATE_BUFFER.add(user['token'])
|
USERS_UPDATE_BUFFER.add(user['token'])
|
||||||
return user['presence']
|
return user['presence']
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def update_presence(timestamp, user):
|
def update_presence(timestamp, user):
|
||||||
return _update_presence(timestamp, user)
|
return _update_presence(timestamp, user)
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ def get_unsunsettable_users(timestamp):
|
||||||
get_users_and_update_presence(timestamp),
|
get_users_and_update_presence(timestamp),
|
||||||
)
|
)
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def get_users_by_presence(timestamp):
|
def get_users_by_presence(timestamp):
|
||||||
users_by_presence = {
|
users_by_presence = {
|
||||||
Presence.WATCHING: [],
|
Presence.WATCHING: [],
|
||||||
|
@ -236,7 +236,7 @@ def get_users_by_presence(timestamp):
|
||||||
users_by_presence[user['presence']].append(user)
|
users_by_presence[user['presence']].append(user)
|
||||||
return users_by_presence
|
return users_by_presence
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp(precise=True)
|
||||||
def create_eyes(timestamp, user, headers):
|
def create_eyes(timestamp, user, headers):
|
||||||
# Enforce cooldown
|
# Enforce cooldown
|
||||||
last_created_ago = timestamp - user['last']['eyes']
|
last_created_ago = timestamp - user['last']['eyes']
|
||||||
|
@ -271,7 +271,7 @@ def create_eyes(timestamp, user, headers):
|
||||||
}
|
}
|
||||||
return eyes_id
|
return eyes_id
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp(precise=True)
|
||||||
def renew_eyes(timestamp, user, eyes_id, just_read_new_segment=False):
|
def renew_eyes(timestamp, user, eyes_id, just_read_new_segment=False):
|
||||||
try:
|
try:
|
||||||
eyes = user['eyes']['current'][eyes_id]
|
eyes = user['eyes']['current'][eyes_id]
|
||||||
|
|
|
@ -72,7 +72,7 @@ async def websocket_inbound(queue, user):
|
||||||
if payload is not None:
|
if payload is not None:
|
||||||
queue.put_nowait(payload)
|
queue.put_nowait(payload)
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp()
|
||||||
def handle_inbound_pong(timestamp, queue, user):
|
def handle_inbound_pong(timestamp, queue, user):
|
||||||
print(f'[pong] {user["token"]}')
|
print(f'[pong] {user["token"]}')
|
||||||
user['websockets'][queue] = timestamp
|
user['websockets'][queue] = timestamp
|
||||||
|
|
|
@ -4,23 +4,25 @@
|
||||||
import time
|
import time
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
def with_timestamp(f):
|
def with_function_call(fn, *fn_args, **fn_kwargs):
|
||||||
@wraps(f)
|
def with_result(f):
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
timestamp = int(time.time())
|
|
||||||
return f(timestamp, *args, **kwargs)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
def with_first_argument(x):
|
|
||||||
def with_x(f):
|
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return f(x, *args, **kwargs)
|
result = fn(*fn_args, **fn_kwargs)
|
||||||
|
return f(result, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
return with_result
|
||||||
|
|
||||||
return with_x
|
def with_constant(x):
|
||||||
|
return with_function_call(lambda: x)
|
||||||
|
|
||||||
|
def with_timestamp(monotonic=False, precise=False):
|
||||||
|
n = 1_000_000_000
|
||||||
|
if monotonic:
|
||||||
|
fn = precise and time.monotonic or (lambda: time.monotonic_ns() // n)
|
||||||
|
else:
|
||||||
|
fn = precise and time.time or (lambda: time.time_ns() // n)
|
||||||
|
return with_function_call(fn)
|
||||||
|
|
||||||
def try_except_log(errors, exception_class):
|
def try_except_log(errors, exception_class):
|
||||||
def try_except_log_specific(f):
|
def try_except_log_specific(f):
|
||||||
|
@ -30,9 +32,7 @@ def try_except_log(errors, exception_class):
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
except exception_class as e:
|
except exception_class as e:
|
||||||
errors.append(e)
|
errors.append(e)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return try_except_log_specific
|
return try_except_log_specific
|
||||||
|
|
||||||
def ttl_cache(ttl):
|
def ttl_cache(ttl):
|
||||||
|
@ -42,19 +42,14 @@ def ttl_cache(ttl):
|
||||||
'''
|
'''
|
||||||
def ttl_cache_specific(f):
|
def ttl_cache_specific(f):
|
||||||
value, expires = None, None
|
value, expires = None, None
|
||||||
|
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper():
|
def wrapper():
|
||||||
nonlocal value, expires
|
nonlocal value, expires
|
||||||
|
|
||||||
if expires is None or time.monotonic() >= expires:
|
if expires is None or time.monotonic() >= expires:
|
||||||
value = f()
|
value = f()
|
||||||
expires = time.monotonic() + ttl
|
expires = time.monotonic() + ttl
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return ttl_cache_specific
|
return ttl_cache_specific
|
||||||
|
|
||||||
def ttl_cache_async(ttl):
|
def ttl_cache_async(ttl):
|
||||||
|
@ -63,17 +58,12 @@ def ttl_cache_async(ttl):
|
||||||
'''
|
'''
|
||||||
def ttl_cache_specific(f):
|
def ttl_cache_specific(f):
|
||||||
value, expires = None, None
|
value, expires = None, None
|
||||||
|
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
async def wrapper():
|
async def wrapper():
|
||||||
nonlocal value, expires
|
nonlocal value, expires
|
||||||
|
|
||||||
if expires is None or time.monotonic() >= expires:
|
if expires is None or time.monotonic() >= expires:
|
||||||
value = await f()
|
value = await f()
|
||||||
expires = time.monotonic() + ttl
|
expires = time.monotonic() + ttl
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return ttl_cache_specific
|
return ttl_cache_specific
|
||||||
|
|
読み込み中…
新しいイシューから参照