2021-10-05 01:02:43 +08:00
import argparse
2021-10-03 19:18:47 +08:00
import logging
2021-10-03 21:40:05 +08:00
import queue
import time
2021-10-24 20:54:06 +08:00
from datetime import datetime
2021-10-03 21:40:05 +08:00
from multiprocessing import Manager , Process
2021-10-23 00:34:57 +08:00
from multiprocessing . managers import SyncManager
2021-11-14 23:57:28 +08:00
from typing import Dict , Generator , List
2021-10-03 19:18:47 +08:00
2021-10-24 20:54:06 +08:00
from filelock import FileLock
2021-11-08 23:28:49 +08:00
from pywebio import config as webconfig
2021-10-24 20:54:06 +08:00
from pywebio . exceptions import SessionClosedException , SessionNotFoundException
2021-11-14 23:57:28 +08:00
from pywebio . output import ( clear , close_popup , output , popup , put_button ,
put_buttons , put_collapse , put_column , put_error ,
put_html , put_loading , put_markdown , put_row ,
put_text , toast )
from pywebio . pin import pin , pin_wait_change
from pywebio . session import go_app , info , register_thread , run_js , set_env
2021-10-05 01:02:43 +08:00
2021-10-23 21:17:00 +08:00
# This must be the first to import
from module . logger import logger # Change folder
2021-10-03 19:18:47 +08:00
import module . webui . lang as lang
2021-11-15 00:17:52 +08:00
import module . config . server as server
2021-10-21 01:21:05 +08:00
from module . config . config import AzurLaneConfig , Function
2021-10-05 01:02:43 +08:00
from module . config . config_updater import ConfigUpdater
2021-10-24 20:54:06 +08:00
from module . config . utils import ( alas_instance , deep_get , deep_iter , deep_set ,
dict_to_kv , filepath_args , filepath_config ,
read_file , write_file )
2021-10-23 00:34:57 +08:00
from module . webui . base import Frame
2021-10-03 21:40:05 +08:00
from module . webui . lang import _t , t
2021-11-14 23:57:28 +08:00
from module . webui . pin import put_input , put_select
2021-10-03 19:18:47 +08:00
from module . webui . translate import translate
2021-11-06 21:26:42 +08:00
from module . webui . utils import ( Icon , QueueHandler , Task , Thread , add_css ,
filepath_css , get_output ,
get_window_visibility_state , login ,
2021-11-14 23:57:28 +08:00
parse_pin_value , re_fullmatch )
from module . webui . widgets import ScrollableCode , put_group , put_icon_buttons
2021-10-03 19:18:47 +08:00
2021-10-05 01:02:43 +08:00
config_updater = ConfigUpdater ( )
2021-10-03 21:40:05 +08:00
2021-10-24 20:54:06 +08:00
class AlasManager :
sync_manager : SyncManager
all_alas : Dict [ str , " AlasManager " ] = { }
2021-10-23 00:34:57 +08:00
def __init__ ( self , config_name : str = ' alas ' ) - > None :
2021-10-03 19:18:47 +08:00
self . config_name = config_name
2021-10-24 20:54:06 +08:00
self . log_queue = self . sync_manager . Queue ( )
2021-10-03 19:18:47 +08:00
self . log = [ ]
self . log_max_length = 500
self . log_reduce_length = 100
2021-10-24 20:54:06 +08:00
self . _process = Process ( )
2021-10-03 21:40:05 +08:00
self . thd_log_queue_handler = Thread ( )
2021-10-03 19:18:47 +08:00
2021-10-29 13:37:26 +08:00
def start ( self , func : str = ' Alas ' ) - > None :
2021-10-24 20:54:06 +08:00
if not self . alive :
2021-10-29 13:37:26 +08:00
self . _process = Process (
target = AlasManager . run_alas ,
args = ( self . config_name , self . log_queue , func ) )
2021-10-24 20:54:06 +08:00
self . _process . start ( )
2021-10-03 21:40:05 +08:00
self . thd_log_queue_handler = Thread (
target = self . _thread_log_queue_handler )
2021-10-03 19:18:47 +08:00
register_thread ( self . thd_log_queue_handler )
self . thd_log_queue_handler . start ( )
else :
toast ( t ( " Gui.Toast.AlasIsRunning " ) , position = ' right ' , color = ' warn ' )
2021-10-23 00:34:57 +08:00
def stop ( self ) - > None :
2021-10-23 20:32:19 +08:00
lock = FileLock ( f " { filepath_config ( self . config_name ) } .lock " )
with lock :
2021-10-24 20:54:06 +08:00
if self . alive :
self . _process . terminate ( )
2021-10-23 20:32:19 +08:00
self . log . append ( " Scheduler stopped. \n " )
if self . thd_log_queue_handler . is_alive ( ) :
self . thd_log_queue_handler . stop ( )
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
def _thread_log_queue_handler ( self ) - > None :
2021-10-24 20:54:06 +08:00
while self . alive :
2021-10-03 19:18:47 +08:00
log = self . log_queue . get ( )
self . log . append ( log )
if len ( self . log ) > self . log_max_length :
self . log = self . log [ self . log_reduce_length : ]
2021-10-24 20:54:06 +08:00
@property
def alive ( self ) - > bool :
return self . _process . is_alive ( )
2021-10-03 21:40:05 +08:00
2021-10-24 20:54:06 +08:00
@classmethod
def get_alas ( cls , config_name : str ) - > " AlasManager " :
"""
Create a new alas if not exists .
"""
if config_name not in cls . all_alas :
cls . all_alas [ config_name ] = AlasManager ( config_name )
return cls . all_alas [ config_name ]
@staticmethod
2021-10-29 13:37:26 +08:00
def run_alas ( config_name , q : queue . Queue , func : str ) - > None :
2021-10-24 20:54:06 +08:00
# Setup logger
qh = QueueHandler ( q )
formatter = logging . Formatter (
fmt = ' %(asctime)s . %(msecs)03d | %(levelname)s | %(message)s ' ,
datefmt = ' % Y- % m- %d % H: % M: % S ' )
webconsole = logging . StreamHandler ( stream = qh )
webconsole . setFormatter ( formatter )
logging . getLogger ( ' alas ' ) . addHandler ( webconsole )
2021-11-15 00:17:52 +08:00
# Set server before loading any buttons.
config = AzurLaneConfig ( config_name = config_name )
server . server = deep_get ( config . data , keys = ' Alas.Emulator.Server ' , default = ' cn ' )
2021-10-24 20:54:06 +08:00
# Run alas
2021-10-29 13:37:26 +08:00
if func == ' Alas ' :
from alas import AzurLaneAutoScript
AzurLaneAutoScript ( config_name = config_name ) . loop ( )
elif func == ' Daemon ' :
from module . daemon . daemon import AzurLaneDaemon
AzurLaneDaemon ( config = config_name , task = ' Daemon ' ) . run ( )
elif func == ' OpsiDaemon ' :
from module . daemon . os_daemon import AzurLaneDaemon
AzurLaneDaemon ( config = config_name , task = ' OpsiDaemon ' ) . run ( )
elif func == ' AzurLaneUncensored ' :
from module . daemon . uncensored import AzurLaneUncensored
AzurLaneUncensored ( config = config_name , task = ' AzurLaneUncensored ' ) . run ( )
q . put ( " Scheduler stopped. \n " ) # Prevent status turns to warning
elif func == ' Benchmark ' :
from module . daemon . benchmark import Benchmark
Benchmark ( config = config_name , task = ' Benchmark ' ) . run ( )
q . put ( " Scheduler stopped. \n " ) # Prevent status turns to warning
else :
logger . critical ( " No function matched " )
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
class AlasGUI ( Frame ) :
2021-11-07 12:14:06 +08:00
ALAS_MENU : Dict [ str , Dict [ str , List [ str ] ] ]
ALAS_ARGS : Dict [ str , Dict [ str , Dict [ str , Dict [ str , str ] ] ] ]
2021-10-24 20:54:06 +08:00
path_to_idx : Dict [ str , str ] = { }
idx_to_path : Dict [ str , str ] = { }
2021-11-08 23:28:49 +08:00
theme = ' default '
2021-10-24 20:54:06 +08:00
@classmethod
def shorten_path ( cls , prefix = ' a ' ) - > None :
"""
Reduce pin_wait_change ( ) command content - length
Using full path name will transfer ~ 16 KB per command ,
may lag when remote control or in bad internet condition .
Use ~ 4 KB after doing this .
Args :
prefix : all idx need to be a valid html , so a random character here
"""
2021-11-07 12:14:06 +08:00
cls . ALAS_MENU = read_file ( filepath_args ( ' menu ' ) )
cls . ALAS_ARGS = read_file ( filepath_args ( ' args ' ) )
2021-10-24 20:54:06 +08:00
i = 0
for list_path , _ in deep_iter ( cls . ALAS_ARGS , depth = 3 ) :
cls . path_to_idx [ ' . ' . join ( list_path ) ] = f ' { prefix } { i } '
cls . idx_to_path [ f ' { prefix } { i } ' ] = ' . ' . join ( list_path )
i + = 1
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
def __init__ ( self ) - > None :
super ( ) . __init__ ( )
2021-10-21 01:21:05 +08:00
# modified keys, return values of pin_wait_change()
2021-10-03 19:18:47 +08:00
self . modified_config_queue = queue . Queue ( )
2021-10-21 01:21:05 +08:00
# alas config name
self . alas_name = ' '
2021-11-14 23:57:28 +08:00
self . alas_config = AzurLaneConfig ( ' template ' )
2021-10-21 01:21:05 +08:00
self . alas_logs = ScrollableCode ( )
self . alas_running = output ( ) . style ( " container-overview-task " )
self . alas_pending = output ( ) . style ( " container-overview-task " )
self . alas_waiting = output ( ) . style ( " container-overview-task " )
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
def set_aside ( self ) - > None :
2021-10-03 19:18:47 +08:00
# note: value doesn't matters if onclick is a list, button binds to functions in order.
# when onclick isn't a list, value will pass to function.
2021-10-23 00:34:57 +08:00
self . aside . reset (
2021-10-03 21:40:05 +08:00
put_icon_buttons ( Icon . DEVELOP , buttons = [ { " label " : t (
2021-10-24 16:54:07 +08:00
" Gui.Aside.Develop " ) , " value " : " Develop " , " color " : " aside " } ] , onclick = [ self . ui_develop ] ) ,
2021-10-23 00:34:57 +08:00
* [ put_icon_buttons ( Icon . RUN ,
buttons = [ { " label " : name , " value " : name , " color " : " aside " } ] ,
onclick = self . ui_alas )
2021-10-24 16:54:07 +08:00
for name in alas_instance ( ) ] ,
put_icon_buttons ( Icon . ADD , buttons = [ { " label " : t (
" Gui.Aside.AddAlas " ) , " value " : " AddAlas " , " color " : " aside " } ] , onclick = [ self . ui_add_alas ] ) ,
2021-10-03 19:18:47 +08:00
)
2021-11-07 12:17:48 +08:00
self . aside_setting . reset (
put_icon_buttons (
Icon . SETTING ,
buttons = [
{ " label " : t ( " Gui.Aside.Setting " ) ,
" value " : " setting " , " color " : " aside " } ] ,
onclick = [ self . ui_setting ] ,
)
)
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
def set_status ( self , status : int ) - > None :
2021-10-14 13:31:54 +08:00
"""
Args :
status ( int ) :
1 ( running ) ,
2 ( not running ) ,
- 1 ( warning , stop unexpectedly ) ,
0 ( hide )
"""
2021-11-06 21:26:42 +08:00
if not self . visible :
return
2021-10-14 13:31:54 +08:00
if self . _status == status :
return
2021-10-14 23:10:03 +08:00
self . _status = status
2021-10-16 16:52:35 +08:00
2021-10-14 13:31:54 +08:00
if status == 1 :
s = put_row ( [
2021-10-23 00:34:57 +08:00
put_loading ( color = ' success ' ) . style (
" width:1.5rem;height:1.5rem;border:.2em solid currentColor;border-right-color:transparent; " ) ,
2021-10-14 13:31:54 +08:00
None ,
put_text ( t ( " Gui.Status.Running " ) )
] , size = ' auto 2px 1fr ' )
elif status == 2 :
s = put_row ( [
put_loading ( color = ' secondary ' ) . style ( " width:1.5rem;height:1.5rem;border:.2em solid currentColor; " ) ,
None ,
put_text ( t ( " Gui.Status.Inactive " ) )
] , size = ' auto 2px 1fr ' )
elif status == - 1 :
s = put_row ( [
put_loading ( shape = ' grow ' , color = ' warning ' ) . style ( " width:1.5rem;height:1.5rem; " ) ,
None ,
put_text ( t ( " Gui.Status.Warning " ) )
] , size = ' auto 2px 1fr ' )
else :
s = ' '
2021-10-16 16:52:35 +08:00
2021-10-14 13:31:54 +08:00
self . status . reset ( s )
2021-10-03 19:18:47 +08:00
# Alas
2021-10-03 21:40:05 +08:00
2021-10-23 00:34:57 +08:00
def alas_set_menu ( self ) - > None :
2021-10-03 19:18:47 +08:00
"""
Set menu for alas
"""
self . menu . append (
put_buttons ( [
2021-10-03 21:40:05 +08:00
{ " label " : t ( " Gui.MenuAlas.Overview " ) ,
" value " : " Overview " , " color " : " menu " }
2021-10-14 23:10:03 +08:00
] , onclick = [ self . alas_overview ] ) . style ( f ' --menu-Overview-- ' ) ,
2021-10-03 19:18:47 +08:00
)
2021-10-24 20:54:06 +08:00
for key , tasks in deep_iter ( self . ALAS_MENU , depth = 2 ) :
2021-10-03 19:18:47 +08:00
# path = '.'.join(key)
menu = key [ 1 ]
2021-10-29 13:37:26 +08:00
if menu == ' Tool ' :
_onclick = self . alas_daemon_overview
else :
_onclick = self . alas_set_group
task_btn_list = [ ]
for task in tasks :
task_btn_list . append (
put_buttons ( [
{ " label " : t ( f ' Task. { task } .name ' ) , " value " : task , " color " : " menu " }
] , onclick = _onclick ) . style ( f ' --menu- { task } -- ' )
)
2021-10-03 19:18:47 +08:00
self . menu . append (
2021-10-29 13:37:26 +08:00
put_collapse (
title = t ( f " Menu. { menu } .name " ) ,
content = task_btn_list
)
2021-10-03 19:18:47 +08:00
)
2021-10-16 16:52:35 +08:00
2021-10-14 23:10:03 +08:00
self . alas_overview ( )
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
def alas_set_group ( self , task : str ) - > None :
2021-10-03 19:18:47 +08:00
"""
Set arg groups from dict
"""
2021-10-23 00:34:57 +08:00
self . init_menu ( name = task )
2021-10-21 01:21:05 +08:00
self . title . reset ( f " { t ( f ' Task. { task } .name ' ) } " )
2021-10-03 19:18:47 +08:00
group_area = output ( )
navigator = output ( )
2021-10-21 01:21:05 +08:00
2021-10-23 00:34:57 +08:00
if self . is_mobile :
2021-10-21 01:21:05 +08:00
content_alas = group_area
else :
content_alas = put_row ( [
None ,
group_area ,
navigator ,
2021-10-26 02:00:35 +08:00
] , size = " 1fr minmax(25rem, 5fr) 2fr " )
2021-10-03 19:18:47 +08:00
2021-10-12 13:38:21 +08:00
self . content . append ( content_alas )
2021-10-03 19:18:47 +08:00
2021-10-05 01:02:43 +08:00
config = config_updater . update_config ( self . alas_name )
2021-10-03 19:18:47 +08:00
2021-10-24 20:54:06 +08:00
for group , arg_dict in deep_iter ( self . ALAS_ARGS [ task ] , depth = 1 ) :
2021-10-03 19:18:47 +08:00
group = group [ 0 ]
2021-10-06 23:22:56 +08:00
group_help = t ( f " { group } ._info.help " )
if group_help == " " or not group_help :
group_help = None
arg_group = put_group ( t ( f " { group } ._info.name " ) , group_help )
2021-10-23 02:34:11 +08:00
list_arg = [ ]
2021-10-03 19:18:47 +08:00
for arg , d in deep_iter ( arg_dict , depth = 1 ) :
arg = arg [ 0 ]
2021-10-03 21:40:05 +08:00
arg_type = d [ ' type ' ]
2021-10-23 02:34:11 +08:00
if arg_type == ' disable ' :
continue
2021-10-03 19:18:47 +08:00
value = deep_get ( config , f ' { task } . { group } . { arg } ' , d [ ' value ' ] )
2021-10-05 01:02:43 +08:00
value = str ( value ) if isinstance ( value , datetime ) else value
2021-10-03 19:18:47 +08:00
# Option
options = deep_get ( d , ' option ' , None )
if options :
option = [ ]
2021-10-06 23:22:56 +08:00
for opt in options :
o = { " label " : t ( f " { group } . { arg } . { opt } " ) , " value " : opt }
if value == opt :
o [ " selected " ] = True
option . append ( o )
2021-10-03 19:18:47 +08:00
else :
option = None
2021-10-03 21:40:05 +08:00
2021-10-03 19:18:47 +08:00
# Help
arg_help = t ( f " { group } . { arg } .help " )
if arg_help == " " or not arg_help :
arg_help = None
2021-11-14 23:57:28 +08:00
# Invalid feedback
invalid_feedback = t ( " Gui.Text.InvalidFeedBack " ) . format ( d [ ' value ' ] )
2021-10-21 01:21:05 +08:00
2021-10-23 02:34:11 +08:00
list_arg . append ( get_output (
2021-10-03 21:40:05 +08:00
arg_type = arg_type ,
2021-10-24 20:54:06 +08:00
name = self . path_to_idx [ f " { task } . { group } . { arg } " ] ,
2021-10-03 19:18:47 +08:00
title = t ( f " { group } . { arg } .name " ) ,
2021-10-03 21:40:05 +08:00
arg_help = arg_help ,
2021-10-03 19:18:47 +08:00
value = value ,
options = option ,
2021-11-14 23:57:28 +08:00
invalid_feedback = invalid_feedback ,
2021-10-12 13:55:24 +08:00
) )
2021-10-23 02:34:11 +08:00
if list_arg :
group_area . append ( arg_group )
arg_group . append ( * list_arg )
2021-10-03 21:40:05 +08:00
2021-10-23 00:34:57 +08:00
def alas_overview ( self ) - > None :
self . init_menu ( name = " Overview " )
2021-10-21 01:21:05 +08:00
self . title . reset ( f " { t ( f ' Gui.MenuAlas.Overview ' ) } " )
scheduler = put_row ( [
put_text ( t ( " Gui.Overview.Scheduler " ) ) . style (
2021-10-23 00:58:41 +08:00
" font-size: 1.25rem; margin: auto .5rem auto; " ) ,
2021-10-21 01:21:05 +08:00
None ,
put_buttons (
buttons = [
2021-10-23 00:34:57 +08:00
{ " label " : t ( " Gui.Button.Start " ) ,
" value " : " Start " , " color " : " scheduler-on " } ,
{ " label " : t ( " Gui.Button.Stop " ) ,
" value " : " Stop " , " color " : " scheduler-off " } ,
2021-10-21 01:21:05 +08:00
] ,
onclick = [
2021-10-29 13:37:26 +08:00
lambda : self . alas . start ( ' Alas ' ) ,
2021-10-21 01:21:05 +08:00
self . alas . stop ,
]
)
] , size = " auto 1fr auto " ) . style ( " container-overview-group " )
log = put_row ( [
put_text ( t ( " Gui.Overview.Log " ) ) . style (
2021-10-23 00:58:41 +08:00
" font-size: 1.25rem; margin: auto .5rem auto; " ) ,
2021-10-21 01:21:05 +08:00
None ,
put_buttons (
buttons = [
2021-10-23 00:34:57 +08:00
{ " label " : t ( " Gui.Button.ClearLog " ) ,
" value " : " ClearLog " , " color " : " scheduler-on " } ,
{ " label " : t ( " Gui.Button.ScrollON " ) ,
" value " : " ScrollON " , " color " : " scheduler-on " } ,
{ " label " : t ( " Gui.Button.ScrollOFF " ) ,
" value " : " ScrollOFF " , " color " : " scheduler-on " } ,
2021-10-21 01:21:05 +08:00
] ,
onclick = [
2021-10-23 00:34:57 +08:00
self . alas_logs . reset ,
2021-10-21 01:21:05 +08:00
lambda : self . alas_logs . set_scroll ( True ) ,
lambda : self . alas_logs . set_scroll ( False ) ,
2021-10-23 00:34:57 +08:00
] ,
2021-10-21 01:21:05 +08:00
)
] , size = " auto 1fr auto " ) . style ( " container-overview-group " )
running = put_column ( [
2021-10-23 00:58:41 +08:00
put_text ( t ( " Gui.Overview.Running " ) ) . style ( " group-overview-title " ) ,
2021-10-21 01:21:05 +08:00
put_html ( ' <hr class= " hr-group " > ' ) ,
self . alas_running ,
] , size = " auto auto 1fr " ) . style ( " container-overview-group " )
pending = put_column ( [
2021-10-23 00:58:41 +08:00
put_text ( t ( " Gui.Overview.Pending " ) ) . style ( " group-overview-title " ) ,
2021-10-21 01:21:05 +08:00
put_html ( ' <hr class= " hr-group " > ' ) ,
self . alas_pending ,
] , size = " auto auto 1fr " ) . style ( " container-overview-group " )
waiting = put_column ( [
2021-10-23 00:58:41 +08:00
put_text ( t ( " Gui.Overview.Waiting " ) ) . style ( " group-overview-title " ) ,
2021-10-21 01:21:05 +08:00
put_html ( ' <hr class= " hr-group " > ' ) ,
self . alas_waiting ,
] , size = " auto auto 1fr " ) . style ( " container-overview-group " )
2021-10-23 00:34:57 +08:00
if self . is_mobile :
2021-10-21 01:21:05 +08:00
self . content . append (
put_column ( [
scheduler ,
running ,
pending ,
waiting ,
2021-10-23 00:58:41 +08:00
] , size = " auto 7.75rem 13rem 13rem " ) ,
2021-10-21 01:21:05 +08:00
put_column ( [
log ,
self . alas_logs . output
] , size = " auto 1fr " ) . style ( " height: 100 % ; overflow-y: auto " )
)
else :
self . content . append (
put_row ( [
put_column ( [
scheduler ,
running ,
pending ,
waiting ,
2021-10-23 00:58:41 +08:00
] , size = " auto 7.75rem minmax(7.75rem, 13rem) minmax(7.75rem, 1fr) "
2021-10-21 01:21:05 +08:00
) . style ( " height: 100 % ; overflow-y: auto " ) ,
put_column ( [
log ,
self . alas_logs . output
] , size = " auto 1fr " ) . style ( " height: 100 % ; overflow-y: auto " ) ,
] , size = " minmax(16rem, 20rem) minmax(24rem, 1fr) "
) . style ( " height: 100 % ; overflow-y: auto " ) ,
)
2021-10-24 20:54:06 +08:00
def refresh_overview_tasks ( ) :
2021-10-23 00:34:57 +08:00
while True :
yield self . alas_update_overiew_tasks ( )
2021-10-21 01:21:05 +08:00
2021-10-24 20:54:06 +08:00
self . task_handler . add ( refresh_overview_tasks ( ) , 10 , True )
2021-10-29 13:37:26 +08:00
self . task_handler . add ( self . alas_put_log ( ) , 0.2 , True )
def alas_put_log ( self ) - > Generator [ None , None , None ] :
last_idx = len ( self . alas . log )
self . alas_logs . append ( ' ' . join ( self . alas . log ) )
lines = 0
while True :
yield
idx = len ( self . alas . log )
if idx < last_idx :
last_idx - = self . alas . log_reduce_length
if idx != last_idx :
try :
self . alas_logs . append ( ' ' . join ( self . alas . log [ last_idx : idx ] ) )
except SessionNotFoundException :
break
lines + = idx - last_idx
last_idx = idx
2021-10-23 00:34:57 +08:00
def _alas_thread_wait_config_change ( self ) - > None :
2021-10-03 19:18:47 +08:00
paths = [ ]
2021-10-24 20:54:06 +08:00
for path , d in deep_iter ( self . ALAS_ARGS , depth = 3 ) :
2021-10-03 19:18:47 +08:00
if d [ ' type ' ] == ' disable ' :
continue
2021-10-24 20:54:06 +08:00
paths . append ( self . path_to_idx [ ' . ' . join ( path ) ] )
2021-10-09 01:12:04 +08:00
while self . alive :
2021-10-03 19:18:47 +08:00
try :
2021-10-24 20:54:06 +08:00
val = pin_wait_change ( * paths )
2021-10-09 01:12:04 +08:00
self . modified_config_queue . put ( val )
2021-10-03 19:18:47 +08:00
except SessionClosedException :
break
2021-10-23 00:34:57 +08:00
def _alas_thread_update_config ( self ) - > None :
2021-10-03 19:18:47 +08:00
modified = { }
2021-11-14 23:57:28 +08:00
valid = [ ]
invalid = [ ]
2021-10-03 19:18:47 +08:00
while self . alive :
try :
d = self . modified_config_queue . get ( timeout = 10 )
2021-10-14 23:10:03 +08:00
config_name = self . alas_name
2021-10-03 21:40:05 +08:00
except queue . Empty :
2021-10-03 19:18:47 +08:00
continue
2021-10-24 20:54:06 +08:00
modified [ self . idx_to_path [ d [ ' name ' ] ] ] = parse_pin_value ( d [ ' value ' ] )
2021-10-03 19:18:47 +08:00
while True :
try :
d = self . modified_config_queue . get ( timeout = 1 )
2021-10-24 20:54:06 +08:00
modified [ self . idx_to_path [ d [ ' name ' ] ] ] = parse_pin_value ( d [ ' value ' ] )
2021-10-03 19:18:47 +08:00
except queue . Empty :
2021-10-15 00:20:32 +08:00
config = read_file ( filepath_config ( config_name ) )
2021-11-14 23:57:28 +08:00
for k , v in modified . copy ( ) . items ( ) :
validate = deep_get ( self . ALAS_ARGS , k + ' .validate ' )
2021-11-16 12:25:55 +08:00
if not len ( str ( v ) ) :
2021-11-15 12:40:13 +08:00
default = deep_get ( self . ALAS_ARGS , k + ' .value ' )
deep_set ( config , k , default )
valid . append ( self . path_to_idx [ k ] )
modified [ k ] = default
elif not validate or re_fullmatch ( validate , v ) :
2021-11-14 23:57:28 +08:00
deep_set ( config , k , v )
valid . append ( self . path_to_idx [ k ] )
else :
modified . pop ( k )
invalid . append ( self . path_to_idx [ k ] )
logger . warning ( f ' Invalid value { v } for key { k } , skip saving. ' )
# toast(t("Gui.Toast.InvalidConfigValue").format(
# t('.'.join(k.split('.')[1:] + ['name']))),
# duration=0, position='right', color='warn')
self . pin_remove_invalid_mark ( valid )
self . pin_set_invalid_mark ( invalid )
2021-11-15 12:40:13 +08:00
if modified :
2021-11-14 23:57:28 +08:00
toast ( t ( " Gui.Toast.ConfigSaved " ) ,
duration = 1 , position = ' right ' , color = ' success ' )
2021-11-15 12:40:13 +08:00
logger . info ( f ' Save config { filepath_config ( config_name ) } , { dict_to_kv ( modified ) } ' )
write_file ( filepath_config ( config_name ) , config )
2021-11-14 23:57:28 +08:00
modified . clear ( )
valid . clear ( )
invalid . clear ( )
2021-10-03 19:18:47 +08:00
break
2021-10-03 21:40:05 +08:00
2021-10-23 00:34:57 +08:00
def alas_update_status ( self ) - > None :
2021-10-14 13:31:54 +08:00
if hasattr ( self , ' alas ' ) :
2021-10-24 20:54:06 +08:00
if self . alas . alive :
2021-10-14 13:31:54 +08:00
self . set_status ( 1 )
2021-10-14 23:10:03 +08:00
elif len ( self . alas . log ) == 0 or self . alas . log [ - 1 ] == " Scheduler stopped. \n " :
2021-10-14 13:31:54 +08:00
self . set_status ( 2 )
else :
self . set_status ( - 1 )
else :
self . set_status ( 0 )
2021-10-23 00:34:57 +08:00
def alas_update_overiew_tasks ( self ) - > None :
2021-11-06 21:26:42 +08:00
if not self . visible :
return
2021-10-21 01:21:05 +08:00
self . alas_config . load ( )
self . alas_config . get_next_task ( )
if len ( self . alas_config . pending_task ) > = 1 :
2021-10-24 20:54:06 +08:00
if self . alas . alive :
2021-10-21 01:21:05 +08:00
running = self . alas_config . pending_task [ : 1 ]
pending = self . alas_config . pending_task [ 1 : ]
else :
running = [ ]
pending = self . alas_config . pending_task [ : ]
else :
running = [ ]
pending = [ ]
waiting = self . alas_config . waiting_task
2021-10-23 00:34:57 +08:00
def box ( func : Function ) :
2021-10-21 01:21:05 +08:00
return put_row ( [
put_column ( [
put_text ( t ( f ' Task. { func . command } .name ' ) ) . style ( " arg-title " ) ,
put_text ( str ( func . next_run ) ) . style ( " arg-help " ) ,
] , size = " auto auto " ) ,
put_button (
label = t ( " Gui.Button.Setting " ) ,
onclick = lambda : self . alas_set_group ( func . command ) ,
color = " scheduler-on "
) ,
2021-10-26 02:00:35 +08:00
] , size = " 1fr auto " ) . style ( " container-overview-args " )
2021-10-30 21:48:05 +08:00
# Another version without setting button, box become clickable
# return put_column([
# put_text(t(f'Task.{func.command}.name')).style("arg-title"),
# put_text(str(func.next_run)).style("arg-help"),
# ], size="auto auto"
# ).style("container-overview-args"
# ).onclick(lambda: self.alas_set_group(func.command))
2021-10-23 00:34:57 +08:00
2021-10-21 01:21:05 +08:00
no_task_style = " text-align:center; font-size: 0.875rem; color: darkgrey; "
if running :
self . alas_running . reset ( * [ box ( task ) for task in running ] )
else :
self . alas_running . reset (
put_text ( t ( " Gui.Overview.NoTask " ) ) . style ( no_task_style )
)
if pending :
self . alas_pending . reset ( * [ box ( task ) for task in pending ] )
else :
self . alas_pending . reset (
put_text ( t ( " Gui.Overview.NoTask " ) ) . style ( no_task_style )
)
if waiting :
self . alas_waiting . reset ( * [ box ( task ) for task in waiting ] )
else :
self . alas_waiting . reset (
put_text ( t ( " Gui.Overview.NoTask " ) ) . style ( no_task_style )
)
2021-10-29 13:37:26 +08:00
def alas_daemon_overview ( self , task : str ) - > None :
self . init_menu ( name = task )
self . title . reset ( f " { t ( f ' Task. { task } .name ' ) } " )
scheduler = put_row ( [
put_text ( t ( " Gui.Overview.Scheduler " ) ) . style (
" font-size: 1.25rem; margin: auto .5rem auto; " ) ,
None ,
put_buttons (
buttons = [
{ " label " : t ( " Gui.Button.Start " ) ,
" value " : " Start " , " color " : " scheduler-on " } ,
{ " label " : t ( " Gui.Button.Stop " ) ,
" value " : " Stop " , " color " : " scheduler-off " } ,
] ,
onclick = [
lambda : self . alas . start ( task ) ,
self . alas . stop ,
]
)
] , size = " auto 1fr auto " ) . style ( " container-overview-group " )
log = put_row ( [
put_text ( t ( " Gui.Overview.Log " ) ) . style (
" font-size: 1.25rem; margin: auto .5rem auto; " ) ,
None ,
put_buttons (
buttons = [
{ " label " : t ( " Gui.Button.ClearLog " ) ,
" value " : " ClearLog " , " color " : " scheduler-on " } ,
{ " label " : t ( " Gui.Button.ScrollON " ) ,
" value " : " ScrollON " , " color " : " scheduler-on " } ,
{ " label " : t ( " Gui.Button.ScrollOFF " ) ,
" value " : " ScrollOFF " , " color " : " scheduler-on " } ,
] ,
onclick = [
self . alas_logs . reset ,
lambda : self . alas_logs . set_scroll ( True ) ,
lambda : self . alas_logs . set_scroll ( False ) ,
] ,
)
] , size = " auto 1fr auto " ) . style ( " container-overview-group " )
setting = output ( ) . style ( " container-overview-group " )
if self . is_mobile :
self . content . append (
put_column ( [
scheduler ,
setting ,
log ,
self . alas_logs . output
] , size = " auto auto auto 1fr "
) . style ( " height: 100 % ; overflow-y: auto " )
)
else :
self . content . append (
put_row ( [
None ,
put_column ( [
put_row ( [
scheduler ,
log ,
] , size = " auto auto " ) ,
setting ,
self . alas_logs . output
] , size = " auto minmax(6rem, auto) minmax(15rem, 1fr) "
) . style ( " height: 100 % ; overflow-y: auto " ) ,
None ,
] , size = " 1fr minmax(25rem, 6fr) 1fr "
) . style ( " height: 100 % ; overflow-y: auto " )
)
config = config_updater . update_config ( self . alas_name )
for group , arg_dict in deep_iter ( self . ALAS_ARGS [ task ] , depth = 1 ) :
group = group [ 0 ]
setting . append ( put_text ( t ( f " { group } ._info.name " ) ) . style ( " group-title " ) )
group_help = t ( f " { group } ._info.help " )
if group_help :
setting . append ( put_text ( group_help ) . style ( " group-help " ) )
list_arg = [ ]
for arg , d in deep_iter ( arg_dict , depth = 1 ) :
arg = arg [ 0 ]
arg_type = d [ ' type ' ]
if arg_type == ' disable ' :
continue
value = deep_get ( config , f ' { task } . { group } . { arg } ' , d [ ' value ' ] )
value = str ( value ) if isinstance ( value , datetime ) else value
# Option
options = deep_get ( d , ' option ' , None )
if options :
option = [ ]
for opt in options :
o = { " label " : t ( f " { group } . { arg } . { opt } " ) , " value " : opt }
if value == opt :
o [ " selected " ] = True
option . append ( o )
else :
option = None
# Help
arg_help = t ( f " { group } . { arg } .help " )
if arg_help == " " or not arg_help :
arg_help = None
list_arg . append ( get_output (
arg_type = arg_type ,
name = self . path_to_idx [ f " { task } . { group } . { arg } " ] ,
title = t ( f " { group } . { arg } .name " ) ,
arg_help = arg_help ,
value = value ,
options = option ,
) )
if list_arg :
setting . append ( * list_arg )
self . task_handler . add ( self . alas_put_log ( ) , 0.2 , True )
2021-10-03 19:18:47 +08:00
# Develop
2021-11-14 23:57:28 +08:00
2021-10-23 00:34:57 +08:00
def dev_set_menu ( self ) - > None :
2021-10-23 00:58:41 +08:00
self . init_menu ( collapse_menu = False , name = " Develop " )
2021-10-03 19:18:47 +08:00
self . menu . append (
2021-10-30 21:48:05 +08:00
put_buttons ( [
{ " label " : t ( " Gui.MenuDevelop.HomePage " ) ,
" value " : " HomePage " , " color " : " menu " }
] , onclick = [ self . show ] ) . style ( f ' --menu-HomePage-- ' ) ,
2021-10-03 19:18:47 +08:00
put_buttons ( [
2021-10-03 21:40:05 +08:00
{ " label " : t ( " Gui.MenuDevelop.Translate " ) ,
" value " : " Translate " , " color " : " menu " }
2021-10-14 23:10:03 +08:00
] , onclick = [ self . dev_translate ] ) . style ( f ' --menu-Translate-- ' ) ,
2021-10-03 19:18:47 +08:00
# put_buttons([
2021-10-09 01:12:04 +08:00
# {"label": t("Gui.MenuDevelop.Something"),
# "value": "Something", "color": "menu"}
2021-10-14 23:10:03 +08:00
# ], onclick=[self.dev_something]).style(f'--menu-Something--'),
2021-10-03 19:18:47 +08:00
)
2021-10-23 00:34:57 +08:00
def dev_translate ( self ) - > None :
2021-10-03 19:18:47 +08:00
go_app ( ' translate ' , new_window = True )
lang . TRANSLATE_MODE = True
2021-10-30 21:48:05 +08:00
self . show ( )
2021-10-03 19:18:47 +08:00
# Aside UI route
2021-10-03 21:40:05 +08:00
2021-10-23 00:34:57 +08:00
def ui_develop ( self ) - > None :
self . init_aside ( name = " Develop " )
self . title . reset ( f " { t ( ' Gui.Aside.Develop ' ) } " )
2021-10-03 19:18:47 +08:00
self . dev_set_menu ( )
2021-10-21 01:21:05 +08:00
self . alas_name = ' '
2021-11-14 23:57:28 +08:00
if hasattr ( self , ' alas ' ) :
del self . alas
self . set_status ( 0 )
2021-10-03 19:18:47 +08:00
2021-10-23 00:34:57 +08:00
def ui_alas ( self , config_name : str ) - > None :
2021-10-21 01:21:05 +08:00
if config_name == self . alas_name :
2021-10-23 00:34:57 +08:00
self . expand_menu ( )
2021-10-21 01:21:05 +08:00
return
2021-10-23 00:34:57 +08:00
self . init_aside ( name = config_name )
self . content . reset ( )
2021-10-03 19:18:47 +08:00
self . alas_name = config_name
2021-10-24 20:54:06 +08:00
self . alas = AlasManager . get_alas ( config_name )
2021-10-21 01:21:05 +08:00
self . alas_config = AzurLaneConfig ( config_name , ' ' )
2021-10-14 13:31:54 +08:00
self . alas_update_status ( )
2021-10-03 19:18:47 +08:00
self . alas_set_menu ( )
2021-10-23 00:34:57 +08:00
def ui_setting ( self ) - > None :
2021-10-03 19:18:47 +08:00
toast ( ' Not implemented ' , position = ' right ' , color = ' error ' )
2021-10-14 23:10:03 +08:00
return
2021-10-23 00:34:57 +08:00
self . init_aside ( name = " Setting " )
2021-10-21 01:21:05 +08:00
self . alas_name = ' '
2021-10-03 19:18:47 +08:00
2021-10-24 16:54:07 +08:00
def ui_add_alas ( self ) - > None :
with popup ( t ( " Gui.AddAlas.PopupTitle " ) ) as s :
def get_unused_name ( ) :
all_name = alas_instance ( )
for i in range ( 2 , 100 ) :
if f ' alas { i } ' not in all_name :
return f ' alas { i } '
else :
return ' '
def add ( ) :
name = pin [ " AddAlas_name " ]
origin = pin [ " AddAlas_copyfrom " ]
if name not in alas_instance ( ) :
r = read_file ( filepath_config ( origin ) )
write_file ( filepath_config ( name ) , r )
self . set_aside ( )
self . active_button ( " aside " , self . alas_name )
close_popup ( )
else :
clear ( s )
2021-10-24 20:54:06 +08:00
put ( name , origin )
2021-10-24 16:54:07 +08:00
put_error ( t ( " Gui.AddAlas.FileExist " ) , scope = s )
2021-10-24 20:54:06 +08:00
def put ( name = None , origin = None ) :
2021-10-24 16:54:07 +08:00
put_input (
name = " AddAlas_name " ,
label = t ( " Gui.AddAlas.NewName " ) ,
value = name or get_unused_name ( ) ,
scope = s
) ,
put_select (
name = " AddAlas_copyfrom " ,
label = t ( " Gui.AddAlas.CopyFrom " ) ,
options = [ ' template ' ] + alas_instance ( ) ,
value = origin or ' template ' ,
scope = s
) ,
put_button (
label = t ( " Gui.AddAlas.Confirm " ) ,
onclick = add ,
scope = s
)
2021-10-24 20:54:06 +08:00
put ( )
2021-10-24 16:54:07 +08:00
2021-10-30 21:48:05 +08:00
def show ( self ) - > None :
clear ( )
2021-10-12 13:38:21 +08:00
self . main_area . show ( )
2021-10-03 19:18:47 +08:00
self . set_aside ( )
2021-10-23 00:34:57 +08:00
self . collapse_menu ( )
2021-10-03 19:18:47 +08:00
2021-10-30 21:48:05 +08:00
def set_language ( l ) :
lang . set_language ( l )
self . show ( )
2021-10-03 19:18:47 +08:00
2021-11-08 23:28:49 +08:00
def set_theme ( t ) :
"""
Theme support is not available in pywebio 1.4
You need to upgrade to 1.5 dev version manually by run
` pip install - U https : / / code . aliyun . com / wang0618 / pywebio / repository / archive . zip `
"""
from pywebio . __version__ import __version__ as version
if ' dev ' not in version and ' 1.4 ' in version :
toast ( " Not supported " , position = ' right ' , color = ' error ' )
return
type ( self ) . theme = t
webconfig ( theme = t )
run_js ( " location.reload() " )
2021-10-23 21:17:00 +08:00
# temporary buttons, there is no setting page now :(
self . content . append (
put_text ( " Select your language " ) . style ( " text-align: center " ) ,
put_buttons (
[ " zh-CN " , " zh-TW " , " en-US " , " ja-JP " ] ,
2021-10-30 21:48:05 +08:00
onclick = lambda l : set_language ( l )
2021-11-08 23:28:49 +08:00
) . style ( " text-align: center " ) ,
put_text ( " Change theme (Not supported now) " ) . style ( " text-align: center " ) ,
put_buttons (
[ { " label " : " Light " , " value " : " default " , " color " : " light " } ,
{ " label " : " Dark " , " value " : " dark " , " color " : " dark " } ] ,
onclick = lambda t : set_theme ( t ) ,
2021-10-23 21:17:00 +08:00
) . style ( " text-align: center " )
)
2021-10-03 19:18:47 +08:00
# show something
2021-10-12 13:38:21 +08:00
self . content . append ( output ( output (
2021-10-03 19:18:47 +08:00
put_markdown ( """
## AzurLaneAutoScript
This new UI is still under development .
2021-10-09 01:12:04 +08:00
if you encounter any error or find a bug , [ create new issue ] ( https : / / github . com / LmeSzinc / AzurLaneAutoScript / issues / new / choose ) or ` @ 18870 #0856` in discord with error logs.
You may found logs in python console or browser console ( ` Ctrl ` + ` Shift ` + ` I ` - ` Console ` )
2021-10-03 19:18:47 +08:00
! [ ] ( https : / / i . loli . net / 2021 / 10 / 03 / 5 pNYoS8EFcvrhIs . png )
! [ ] ( https : / / i . loli . net / 2021 / 10 / 03 / 5 xCaATjl6oK7S1f . png )
## Join in translation
2021-10-09 01:12:04 +08:00
Go ` Develop ` - ` Translate `
2021-10-03 19:18:47 +08:00
""" , strip_indent=12)).style( ' welcome ' )))
2021-10-03 21:40:05 +08:00
2021-10-30 21:48:05 +08:00
if lang . TRANSLATE_MODE :
lang . reload ( )
def _disable ( ) :
lang . TRANSLATE_MODE = False
self . show ( )
toast ( _t ( " Gui.Toast.DisableTranslateMode " ) , duration = 0 , position = ' right ' , onclick = _disable )
def run ( self ) - > None :
# setup gui
set_env ( title = " Alas " , output_animation = False )
add_css ( filepath_css ( ' alas ' ) )
if self . is_mobile :
add_css ( filepath_css ( ' alas-mobile ' ) )
2021-11-14 23:57:28 +08:00
else :
add_css ( filepath_css ( ' alas-pc ' ) )
2021-10-30 21:48:05 +08:00
2021-11-14 23:57:28 +08:00
if self . theme == ' dark ' :
2021-11-08 23:28:49 +08:00
add_css ( filepath_css ( ' dark-alas ' ) )
2021-11-14 23:57:28 +08:00
else :
add_css ( filepath_css ( ' light-alas ' ) )
2021-11-08 23:28:49 +08:00
2021-10-30 21:48:05 +08:00
self . show ( )
2021-10-03 19:18:47 +08:00
# detect config change
2021-10-03 21:40:05 +08:00
_thread_wait_config_change = Thread (
target = self . _alas_thread_wait_config_change )
2021-10-03 19:18:47 +08:00
register_thread ( _thread_wait_config_change )
_thread_wait_config_change . start ( )
# save config
2021-10-14 13:31:54 +08:00
_thread_save_config = Thread (
target = self . _alas_thread_update_config )
2021-10-03 19:18:47 +08:00
register_thread ( _thread_save_config )
_thread_save_config . start ( )
2021-10-24 20:54:06 +08:00
def refresh_status ( ) :
2021-10-23 00:34:57 +08:00
while True :
yield self . alas_update_status ( )
2021-11-06 21:26:42 +08:00
def refresh_visibility_state ( ) :
_visible = self . visible
while True :
v = get_window_visibility_state ( )
if v != _visible :
self . task_handler . remove_running_task ( )
self . visible = v
if v :
logger . info ( " Window running in the foreground " )
self . task_handler . add_task (
Task ( refresh_visibility_state ( ) , 15 , time . time ( ) + 15 ) )
else :
logger . info ( " Window running in the background " )
self . task_handler . add_task (
Task ( refresh_visibility_state ( ) , 2 , time . time ( ) + 2 ) )
yield
2021-10-24 20:54:06 +08:00
self . task_handler . add ( refresh_status ( ) , 2 )
2021-11-06 21:26:42 +08:00
self . task_handler . add ( refresh_visibility_state ( ) , 15 )
2021-10-23 00:34:57 +08:00
self . task_handler . start ( )
2021-10-14 13:31:54 +08:00
2021-10-03 19:18:47 +08:00
2021-11-14 23:57:28 +08:00
def debug ( ) :
""" For interactive python.
$ python3
>> > from gui import *
>> > debug ( )
>> >
"""
AlasManager . sync_manager = Manager ( )
AlasGUI . shorten_path ( )
lang . reload ( )
AlasGUI ( ) . run ( )
2021-10-03 19:18:47 +08:00
if __name__ == " __main__ " :
2021-10-05 01:02:43 +08:00
parser = argparse . ArgumentParser ( description = ' Alas web service ' )
parser . add_argument ( " -d " , " --debug " , action = " store_true " ,
help = " show log " )
2021-10-09 01:12:04 +08:00
parser . add_argument ( ' -p ' , ' --port ' , type = int , default = 22267 ,
help = ' Port to listen. Default to 22267 ' )
2021-10-05 01:02:43 +08:00
parser . add_argument ( ' -b ' , ' --backend ' , type = str , default = ' starlette ' ,
help = ' Backend framework of web server, starlette or tornado. Default to starlette ' )
2021-10-09 17:20:59 +08:00
parser . add_argument ( ' -k ' , ' --key ' , type = str , default = ' ' ,
help = ' Password of alas. No password by default ' )
2021-10-05 01:02:43 +08:00
args = parser . parse_args ( )
2021-10-24 20:54:06 +08:00
AlasManager . sync_manager = Manager ( )
AlasGUI . shorten_path ( )
2021-11-07 12:14:06 +08:00
lang . reload ( )
2021-10-23 21:17:00 +08:00
2021-10-03 19:18:47 +08:00
def index ( ) :
2021-10-09 17:20:59 +08:00
if args . key != ' ' and not login ( args . key ) :
logger . warning ( f " { info . user_ip } login failed. " )
2021-11-08 23:28:49 +08:00
time . sleep ( 1.5 )
2021-10-09 17:20:59 +08:00
run_js ( ' location.reload(); ' )
return
2021-10-03 19:18:47 +08:00
AlasGUI ( ) . run ( )
2021-10-03 21:40:05 +08:00
2021-10-23 00:34:57 +08:00
2021-10-05 01:02:43 +08:00
if args . backend == ' starlette ' :
2021-10-03 19:18:47 +08:00
from pywebio . platform . fastapi import start_server
else :
from pywebio . platform . tornado import start_server
2021-10-03 21:40:05 +08:00
2021-10-06 16:49:47 +08:00
try :
start_server ( [ index , translate ] , port = args . port , debug = args . debug )
finally :
2021-10-24 20:54:06 +08:00
for alas in AlasManager . all_alas . values ( ) :
2021-10-06 16:49:47 +08:00
alas . stop ( )
logger . info ( " Alas closed. " )