@@ -94,6 +94,15 @@ def execute_multi_line_cmd(self, cmd: str):
9494 import subprocess
9595 subprocess .run (cmd , shell = True , check = True )
9696
97+ def enter_wait_list (self ):
98+ self .option .need_wait_plugins .append (self )
99+
100+ def leave_wait_list (self ):
101+ self .option .need_wait_plugins .remove (self )
102+
103+ def wait_until_finish (self ):
104+ pass
105+
97106
98107class JmLoginPlugin (JmOptionPlugin ):
99108 """
@@ -407,7 +416,7 @@ def invoke(self,
407416
408417 proxy_clazz = JmModuleConfig .client_impl_class (proxy_client_key )
409418 clazz_init_kwargs = kwargs
410- new_jm_client = self .option .new_jm_client
419+ new_jm_client : Callable = self .option .new_jm_client
411420
412421 def hook_new_jm_client (* args , ** kwargs ):
413422 client = new_jm_client (* args , ** kwargs )
@@ -749,3 +758,149 @@ def generate_cmd():
749758
750759 paths .append (self .option .decide_image_save_dir (photo , ensure_exists = False ))
751760 self .execute_deletion (paths )
761+
762+
763+ class JmServerPlugin (JmOptionPlugin ):
764+ plugin_key = 'jm_server'
765+
766+ default_run_kwargs = {
767+ 'host' : '0.0.0.0' ,
768+ 'port' : '80' ,
769+ 'debug' : False ,
770+ }
771+
772+ from threading import Lock
773+ single_instance_lock = Lock ()
774+
775+ def __init__ (self , option : JmOption ):
776+ super ().__init__ (option )
777+ self .run_server_lock = Lock ()
778+ self .running = False
779+ self .server_thread : Optional [Thread ] = None
780+
781+ def invoke (self ,
782+ password = '' ,
783+ base_dir = None ,
784+ album = None ,
785+ photo = None ,
786+ downloader = None ,
787+ run = None ,
788+ ** kwargs
789+ ):
790+ """
791+
792+ :param password: 密码
793+ :param base_dir: 初始访问服务器的根路径
794+ :param album: 为了支持 after_album 这种调用时机
795+ :param photo: 为了支持 after_album 这种调用时机
796+ :param downloader: 为了支持 after_album 这种调用时机
797+ :param run: 用于启动服务器: app.run(**run_kwargs)
798+ :param kwargs: 用于JmServer构造函数: JmServer(base_dir, password, **kwargs)
799+ """
800+
801+ if base_dir is None :
802+ base_dir = self .option .dir_rule .base_dir
803+
804+ if run is None :
805+ run = self .default_run_kwargs
806+ else :
807+ base_run_kwargs = self .default_run_kwargs .copy ()
808+ base_run_kwargs .update (run )
809+ run = base_run_kwargs
810+
811+ if self .running is True :
812+ return
813+
814+ with self .run_server_lock :
815+ if self .running is True :
816+ return
817+
818+ self .running = True
819+
820+ # 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装
821+ # 源代码仓库:https://github.com/hect0x7/plugin-jm-server
822+ try :
823+ import plugin_jm_server
824+ self .log (f'当前使用plugin_jm_server版本: { plugin_jm_server .__version__ } ' )
825+ except ImportError :
826+ self .warning_lib_not_install ('plugin_jm_server' )
827+ return
828+
829+ # 核心函数,启动服务器,会阻塞当前线程
830+ def blocking_run_server ():
831+ self .server_thread = current_thread ()
832+ self .enter_wait_list ()
833+ server = plugin_jm_server .JmServer (base_dir , password , ** kwargs )
834+ # run方法会阻塞当前线程直到flask退出
835+ server .run (** run )
836+
837+ # 对于debug模式,特殊处理
838+ if run ['debug' ] is True :
839+ run .setdefault ('use_reloader' , False )
840+
841+ # debug模式只能在主线程启动,判断当前线程是不是主线程
842+ if current_thread () is not threading .main_thread ():
843+ # 不是主线程,return
844+ return self .warning_wrong_usage_of_debug ()
845+ else :
846+ # 是主线程,启动服务器
847+ blocking_run_server ()
848+
849+ else :
850+ # 非debug模式,开新线程启动
851+ threading .Thread (target = blocking_run_server , daemon = True ).start ()
852+ atexit_register (self .wait_server_stop )
853+
854+ def warning_wrong_usage_of_debug (self ):
855+ self .log ('注意!当配置debug=True时,请确保当前插件是在主线程中被调用。\n '
856+ '因为如果本插件配置在 [after_album/after_photo] 这种时机调用,\n '
857+ '会使得flask框架不在主线程debug运行,\n '
858+ '导致报错(ValueError: signal only works in main thread of the main interpreter)。\n ' ,
859+ '【基于上述原因,当前线程非主线程,不启动服务器】'
860+ 'warning'
861+ )
862+
863+ def wait_server_stop (self , proactive = False ):
864+ st = self .server_thread
865+ if (
866+ st is None
867+ or st == current_thread ()
868+ or not st .is_alive ()
869+ ):
870+ return
871+
872+ if proactive :
873+ msg = f'[{ self .plugin_key } ]的服务器线程仍运行中,可按下ctrl+c结束程序'
874+ else :
875+ msg = f'主线程执行完毕,但插件[{ self .plugin_key } ]的服务器线程仍运行中,可按下ctrl+c结束程序'
876+
877+ self .log (msg , 'wait' )
878+
879+ while st .is_alive ():
880+ try :
881+ st .join (timeout = 0.5 )
882+ except KeyboardInterrupt :
883+ self .log ('收到ctrl+c,结束程序' , 'wait' )
884+ return
885+
886+ def wait_until_finish (self ):
887+ self .wait_server_stop (proactive = True )
888+
889+ @classmethod
890+ def build (cls , option : JmOption ) -> 'JmOptionPlugin' :
891+ """
892+ 单例模式
893+ """
894+ field_name = 'single_instance'
895+
896+ instance = getattr (cls , field_name , None )
897+ if instance is not None :
898+ return instance
899+
900+ with cls .single_instance_lock :
901+ instance = getattr (cls , field_name , None )
902+ if instance is not None :
903+ return instance
904+ instance = JmServerPlugin (option )
905+ setattr (cls , field_name , instance )
906+ return instance
0 commit comments