File size: 10,256 Bytes
020c337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: baozi <baozi@bt.cn>
# -------------------------------------------------------------------
# 项目监听重启模块
# ------------------------------
# 功能: 监听指定项目的文件,当文件发生变化时,调用服务重启
# 1.配置
#     conf = {
#         "project_type": "java",
#         "project_id": 14,
#         "project_name": "project_name",
#         "watch_path": "/www/wwwroot/java_p/xxx.jar",
#     }
# 对文件的处理方式:
#     1. 比对size
#     2. 比对mtime
# 对文件夹的处理方式:Todo:视情况后续开发
#     1.暂不支持
# Todo: 目前只支持java springboot项目
# 服务运行逻辑
# BT-Task 做轮询
#     1.1 加载配置文件
#     1.2 获取状态并比对
#     1.3 根据比对结果处理重启服务
#     1.4.尝试重载配置文件

import threading
import json
import os.path
import time
import sys
from importlib import import_module
from typing import Optional, Dict, Any, Tuple


os.chdir("/www/server/panel")
if "class/" not in sys.path:
    sys.path.insert(0, "class/")

if "/www/server/panel" not in sys.path:
    sys.path.insert(0, "/www/server/panel")

import public


class WatchConfig:
    _CONF_FILE = "{}/data/watch_project.json".format(public.get_panel_path())

    def __init__(self):
        self._config: Optional[dict] = None
        self._conf_time: Optional[int] = None

    @property
    def config(self) -> dict:
        if self._config is not None:
            return self._config
        data = {}
        if not os.path.isfile(self._CONF_FILE):
            self._config = data
            self.save_conf()
            return self._config
        try:
            data = json.loads(public.readFile(self._CONF_FILE))
            if isinstance(data, dict):
                self._config = data
                self._conf_time = int(os.path.getmtime(self._CONF_FILE))  # 正确读出配置时保存时间戳
            else:
                self._config = {}
        except (json.JSONDecodeError, TypeError):
            self._config = {}

        if self._conf_time is None:
            self.save_conf()
        return self._config

    def save_conf(self):
        if self._config is not None:
            public.writeFile(self._CONF_FILE, json.dumps(self._config))
            self._conf_time = int(os.path.getmtime(self._CONF_FILE))

    def reload(self) -> bool:
        if not os.path.exists(self._CONF_FILE):
            self._config = {}
            self.save_conf()
            return True

        now_mtime = int(os.path.getmtime(self._CONF_FILE))
        if now_mtime == self._conf_time:
            return False
        else:
            self._config = None
            return True


class Task:
    WAIT_TIME = 20

    def __init__(self):
        self.watch_conf = WatchConfig()
        self._mod_cache: Dict[str, Any] = {}
        self._status_cache = {}
        self._sub_thread_cache = {}

    def get_mod_obj(self, project_type: str) -> Any:
        if project_type in self._mod_cache:
            return self._mod_cache[project_type]

        try:
            if project_type == "java" and os.path.exists("/www/server/panel/mod/project/java/projectMod.py"):
                main_module = import_module("mod.project.java.projectMod")
            else:
                if not project_type.endswith("Model"):
                    project_type += "Model"
                main_module = import_module(".{}".format(project_type), package="projectModel")
        except ImportError:
            print(public.get_error_info())
            # public.print_log(public.get_error_info())
            return None

        main_class = getattr(main_module, "main", None)
        if not callable(main_class):
            return None

        self._mod_cache[project_type] = main_class()
        return self._mod_cache[project_type]

    def need_restart(self, project_type: str, project_id: int) -> bool:
        model_main_obj = self.get_mod_obj(project_type)
        if not model_main_obj:
            return False
        stop_by_user = getattr(model_main_obj, "is_stop_by_user")(project_id)
        if stop_by_user:
            return False
        return True

    def get_status_by_name(self, project_name: str) -> Tuple[int, float]:
        conf = self.watch_conf.config[project_name]
        file = conf['watch_path']
        if not os.path.exists(file):
            return 0, 0
        return os.path.getsize(file), int(os.path.getmtime(file))

    def check_status(self, project_name: str) -> bool:
        if project_name not in self._status_cache:
            self._status_cache[project_name] = self.get_status_by_name(project_name)
            return False

        now_status = self.get_status_by_name(project_name)
        if self._status_cache[project_name] == now_status:
            return False
        else:
            self._status_cache[project_name] = now_status
            return True

    def restart_with_threading(self, project_type, project_name):
        try:
            model_main_obj = self.get_mod_obj(project_type)
            get_obj = public.dict_obj()
            get_obj.project_name = project_name
            getattr(model_main_obj, "restart_project")(get_obj)
        except:
            # print(public.get_error_info())
            public.print_log(public.get_error_info())

    def _run(self):
        while True:
            # print(self.watch_conf.config)
            # print(self.watch_conf._conf_time)
            # print(self._status_cache)
            for p_name, p_conf in self.watch_conf.config.items():
                p_type = p_conf["project_type"]
                if self.need_restart(p_type, p_conf["project_id"]):
                    if self.check_status(p_name):
                        if p_name in self._sub_thread_cache:
                            self._sub_thread_cache[p_name].join()
                        restart_task = threading.Thread(target=self.restart_with_threading, args=(p_type, p_name))
                        restart_task.start()
                        self._sub_thread_cache[p_name] = restart_task

            # public.print_log("---------WAIT--------------")
            for i in range(int(self.WAIT_TIME / 5)):
                time.sleep(5)
                if self.watch_conf.reload():
                    print("配置文件发生变化,重载配置")
                    # public.print_log("配置文件发生变化,重载配置")
                    # public.print_log(self.watch_conf.config)
                    break

    def run(self):
        try:
            self._run()
        except KeyboardInterrupt:
            return
        except:
            public.print_log(public.get_error_info())
            # print(self.watch_conf.config)
            self.watch_conf = WatchConfig()
            self.run()


class main:

    def __init__(self):
        pass

    @staticmethod
    def project_is_watch(get):
        try:
            project_name = get.project_name.strip()
        except AttributeError:
            return public.returnMsg(False, "参数错误")

        conf = WatchConfig()
        if project_name in conf.config:
            return conf.config[project_name]
        else:
            return {}

    @staticmethod
    def add_project_watch(get):
        try:
            project_name = get.project_name.strip()
        except AttributeError:
            return public.returnMsg(False, "参数错误")

        site_info = public.M('sites').where("name = ?", (project_name, )).find()
        if isinstance(site_info, str) and site_info.startswith("error"):
            return public.returnMsg(False, "数据库查询错误:" + site_info)
        if not isinstance(site_info, dict):
            return public.returnMsg(False, "未查询到指定的网站")
        if site_info["project_type"] != "Java":
            return public.returnMsg(False, "目前只支持Java Springboot项目使用")

        project_config = json.loads(site_info["project_config"])
        if project_config['java_type'] != 'springboot':
            return public.returnMsg(False, "目前只支持Java Springboot项目使用")

        conf = WatchConfig()
        conf.config[project_name] = {
                "project_type": site_info["project_type"].lower(),
                "project_id": site_info["id"],
                "project_name": project_name,
                "watch_path": project_config["project_jar"]
            }
        conf.save_conf()
        return public.returnMsg(True, "添加成功")

    @staticmethod
    def del_project_watch(get):
        try:
            project_name = get.project_name.strip()
        except AttributeError:
            return public.returnMsg(False, "参数错误")

        conf = WatchConfig()
        if project_name in conf.config:
            del conf.config[project_name]
            conf.save_conf()

        return public.returnMsg(True, "删除成功")


def use_project_watch(project_name: str) -> bool:
    conf = WatchConfig()
    return project_name in conf.config


def add_project_watch(p_name: str, p_type: str, site_id: int, watch_path: str) -> None:
    conf = WatchConfig()
    conf.config[p_name] = {
        "project_type": p_type.lower(),
        "project_id": site_id,
        "project_name": p_name,
        "watch_path": watch_path
    }
    try:
        site_ids = public.M('sites').field("id").select()
        site_id_list = [i['id'] for i in site_ids]
        public.print_log(site_id_list)
        remove_list = []
        for k, v in conf.config.items():
            if v["project_id"] not in site_id_list:
                remove_list.append(k)
        for i in remove_list:
            del conf.config[i]
    except:
        pass
    conf.save_conf()


def del_project_watch(p_name: str) -> None:
    conf = WatchConfig()
    if p_name in conf.config:
        del conf.config[p_name]
        conf.save_conf()


if __name__ == '__main__':
    Task().run()