first commit
This commit is contained in:
219
lib/framework/__init__.py
Normal file
219
lib/framework/__init__.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
version = "0.0.1.0"
|
||||
|
||||
# ===================================================
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import platform
|
||||
|
||||
path_app_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
path_data = os.path.join(path_app_root, "data")
|
||||
flag_system_loading = False
|
||||
|
||||
import traceback
|
||||
|
||||
# third - party
|
||||
from flask import (
|
||||
Flask,
|
||||
redirect,
|
||||
render_template,
|
||||
Response,
|
||||
request,
|
||||
jsonify,
|
||||
send_file,
|
||||
send_from_directory,
|
||||
abort,
|
||||
Markup,
|
||||
)
|
||||
from flask_socketio import SocketIO, emit
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import (
|
||||
LoginManager,
|
||||
login_user,
|
||||
logout_user,
|
||||
current_user,
|
||||
login_required,
|
||||
)
|
||||
|
||||
# gommi 공용
|
||||
from .init_args import args
|
||||
from .py_version_func import *
|
||||
from framework.logger import get_logger
|
||||
from framework.class_scheduler import Scheduler
|
||||
from .menu import init_menu, get_menu_map
|
||||
from .user import User
|
||||
from .init_etc import make_default_dir, config_initialize, check_api
|
||||
from .init_web import jinja_initialize
|
||||
|
||||
|
||||
# =================================================
|
||||
# App Start
|
||||
# =================================================
|
||||
|
||||
# 기본디렉토리 생성
|
||||
make_default_dir(path_data)
|
||||
|
||||
package_name = __name__.split(".")[0]
|
||||
print(package_name)
|
||||
|
||||
logger = get_logger(package_name)
|
||||
print(logger)
|
||||
|
||||
try:
|
||||
# pass
|
||||
# Global
|
||||
logger.debug("Path app root : %s", path_app_root)
|
||||
logger.debug("Path app data : %s", path_data)
|
||||
logger.debug("Platform : %s", platform.system())
|
||||
|
||||
app = Flask("gommi")
|
||||
|
||||
app.secret_key = os.urandom(24)
|
||||
# ------------------------------
|
||||
# sqlAlchemy
|
||||
# ------------------------------
|
||||
app.config[
|
||||
"SQLALCHEMY_DATABASE_URI"
|
||||
] = "sqlite:///data/db/gommi.db?check_same_thread=False"
|
||||
app.config["SQLALCHEMY_BINDS"] = {"gommi": "sqlite:///data/db/gommi.db"}
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
app.config["config"] = {}
|
||||
app.config["JSON_AS_ASCII"] = False
|
||||
|
||||
config_initialize("start")
|
||||
|
||||
db = SQLAlchemy(app, session_options={"autoflush": False})
|
||||
|
||||
scheduler = Scheduler(args)
|
||||
|
||||
if args is not None and args.use_gevent is False:
|
||||
socketio = SocketIO(
|
||||
app, cors_allowed_origins="*", async_mode="threading"
|
||||
)
|
||||
else:
|
||||
socketio = SocketIO(
|
||||
app, cors_allowed_origins="*"
|
||||
) # , async_mode='gevent')
|
||||
|
||||
from flask_cors import CORS
|
||||
|
||||
CORS(app)
|
||||
|
||||
# markdown extension
|
||||
from flaskext.markdown import Markdown
|
||||
|
||||
Markdown(app)
|
||||
|
||||
# login manager
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "login"
|
||||
exit_code = -1
|
||||
|
||||
# 추후 삭제
|
||||
# USERS = {
|
||||
# "sjva" + version: User("sjva" + version, passwd_hash="sjva" + version),
|
||||
# }
|
||||
|
||||
# app route가 되어 있는데 import 해야지만 routing이 됨
|
||||
from .log_viewer import *
|
||||
|
||||
# from .manual import *
|
||||
|
||||
USERS = {
|
||||
"gommi": User("sjva", passwd_hash="sjva"),
|
||||
"sjva": User("sjva", passwd_hash="sjva"),
|
||||
}
|
||||
|
||||
from .init_celery import celery
|
||||
import framework.common.celery
|
||||
|
||||
# ========================================================================
|
||||
# system plugin
|
||||
# ========================================================================
|
||||
import system
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
# epg 없이 klive 만 있고 db 파일이 없을 때 아예 다른 모듈이 로딩안되는 문제 발생
|
||||
# klive에서 epg 칼럼을 참조해서 그러는것 같음. 방어코드이나 확인못함
|
||||
try:
|
||||
db.create_all()
|
||||
except Exception as exception:
|
||||
logger.error("CRITICAL db.create_all()!!!")
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# 인증처리
|
||||
config_initialize("auth")
|
||||
|
||||
system.plugin_load()
|
||||
flag_system_loading = True # 로그레벨에서 사용. 필요한가??
|
||||
|
||||
# app blueprint 등록
|
||||
app.register_blueprint(system.blueprint)
|
||||
config_initialize("system_loading_after")
|
||||
|
||||
plugin_menu = []
|
||||
plugin_menu.append(system.menu)
|
||||
plugin_instance_list = {}
|
||||
|
||||
jinja_initialize(app)
|
||||
|
||||
# ------------------------------------------
|
||||
# plugins
|
||||
# ------------------------------------------
|
||||
system.LogicPlugin.custom_plugin_update()
|
||||
from .init_plugin import plugin_init
|
||||
|
||||
plugin_init()
|
||||
|
||||
logger.debug("#------------ plugin loading completed ------------")
|
||||
# ------------------------------------------
|
||||
# menu
|
||||
# ------------------------------------------
|
||||
init_menu(plugin_menu)
|
||||
system.SystemLogic.apply_menu_link()
|
||||
logger.debug("#-------- menu loading completed ------")
|
||||
|
||||
logger.debug("### init app.config['config]")
|
||||
app.config["config"]["port"] = 0
|
||||
if sys.argv[0] == "gommi.py" or sys.argv[0] == "gommi3.py":
|
||||
try:
|
||||
app.config["config"]["port"] = SystemModelSetting.get_int("port")
|
||||
if (
|
||||
app.config["config"]["port"] == 19999
|
||||
and app.config["config"]["running_type"] == "docker"
|
||||
and not os.path.exists("/usr/sbin/nginx")
|
||||
):
|
||||
SystemModelSetting.set("port", "7771")
|
||||
app.config["config"]["port"] = 7771
|
||||
except Exception:
|
||||
app.config["config"]["port"] = 7771
|
||||
|
||||
if args is not None:
|
||||
if args.port is not None:
|
||||
app.config["config"]["port"] = args.port
|
||||
app.config["config"]["repeat"] = args.repeat
|
||||
app.config["config"]["use_celery"] = args.use_celery
|
||||
if platform.system() == "Windows":
|
||||
app.config["config"]["use_celery"] = False
|
||||
app.config["config"]["use_gevent"] = args.use_gevent
|
||||
logger.debug("### config ###")
|
||||
logger.debug(json.dumps(app.config["config"], indent=4))
|
||||
|
||||
logger.debug("### LAST")
|
||||
logger.debug("### PORT:%s", app.config["config"]["port"])
|
||||
logger.debug("### Now you can access GOMMI by webbrowser!!")
|
||||
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
# 반드시 마지막에
|
||||
# import init_route
|
||||
from .init_route import *
|
||||
BIN
lib/framework/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
lib/framework/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/class_scheduler.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/class_scheduler.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/init_args.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/init_args.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/init_celery.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/init_celery.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/init_etc.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/init_etc.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/init_plugin.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/init_plugin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/init_route.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/init_route.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/init_web.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/init_web.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/job.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/job.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/log_viewer.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/log_viewer.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/logger.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/logger.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/menu.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/menu.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/py_version_func.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/py_version_func.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/user.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/user.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/framework/__pycache__/util.cpython-310.pyc
Normal file
BIN
lib/framework/__pycache__/util.cpython-310.pyc
Normal file
Binary file not shown.
188
lib/framework/class_scheduler.py
Normal file
188
lib/framework/class_scheduler.py
Normal file
@@ -0,0 +1,188 @@
|
||||
import traceback
|
||||
from pytz import timezone
|
||||
import sys
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
from random import randint
|
||||
import time
|
||||
|
||||
# third-party
|
||||
from apscheduler.jobstores.base import JobLookupError
|
||||
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
||||
|
||||
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
jobstores = {"default": SQLAlchemyJobStore(url="sqlite:///data/db/gommi.db")}
|
||||
|
||||
excutors = {"default": ThreadPoolExecutor(20)}
|
||||
|
||||
job_defaults = {"coalesce": False, "max_instances": 1}
|
||||
|
||||
# gommi 공용
|
||||
from framework.logger import get_logger
|
||||
|
||||
# 패키지
|
||||
# ==========================================================================
|
||||
package_name = __name__.split(".")[0]
|
||||
logger = get_logger(package_name)
|
||||
|
||||
|
||||
class Scheduler(object):
|
||||
job_list = []
|
||||
first_run_check_thread = None
|
||||
|
||||
def __init__(self, args):
|
||||
# (jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
|
||||
# self.sched = GeventScheduler( executors=executors)
|
||||
try:
|
||||
if args is None or (args is not None and args.use_gevent):
|
||||
from apscheduler.schedulers.gevent import GeventScheduler
|
||||
|
||||
self.sched = GeventScheduler(timezone="Asia/Seoul")
|
||||
else:
|
||||
raise Exception("")
|
||||
except Exception:
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
self.sched = BackgroundScheduler(timezone="Asia/Seoul")
|
||||
# jobstores=jobstores, 에러 pickle 직렬화 할수없다
|
||||
# self.sched.configure(executors=executors, job_defaults=job_defaults, timezone='Asia/Seoul')
|
||||
# self.sched.configure(executors=executors, timezone='Asia/Seoul')
|
||||
self.sched.start()
|
||||
logger.debug("SCHEDULER...")
|
||||
|
||||
def first_run_check_thread_function(self):
|
||||
logger.warning("XX first_run_check_thread_function")
|
||||
|
||||
try:
|
||||
# time.sleep(60)
|
||||
# for i in range(5):
|
||||
flag_exit = True
|
||||
for job_instance in self.job_list:
|
||||
if not job_instance.run:
|
||||
continue
|
||||
if (
|
||||
job_instance.count == 0
|
||||
and not job_instance.is_running
|
||||
and job_instance.is_interval
|
||||
):
|
||||
# if job_instance.count == 0 and not job_instance.is_running:
|
||||
job = self.sched.get_job(job_instance.job_id)
|
||||
if job is not None:
|
||||
logger.warning("job_instance : %s", job_instance.plugin)
|
||||
logger.warning("XX job re-sched:%s", job)
|
||||
flag_exit = False
|
||||
tmp = randint(1, 20)
|
||||
job.modify(
|
||||
next_run_time=datetime.now(timezone("Asia/Seoul"))
|
||||
+ timedelta(seconds=tmp)
|
||||
)
|
||||
# break
|
||||
else:
|
||||
pass
|
||||
if flag_exit:
|
||||
self.remove_job("scheduler_check")
|
||||
# time.sleep(30)
|
||||
logger.warning("first_run_check_thread_function end!!")
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def get_job_list_info(self):
|
||||
|
||||
ret = []
|
||||
idx = 0
|
||||
job_list = self.sched.get_jobs()
|
||||
# logger.debug('len jobs %s %s', len(jobs), len(Scheduler.job_list))
|
||||
for j in job_list:
|
||||
idx += 1
|
||||
entity = {}
|
||||
entity["no"] = idx
|
||||
entity["id"] = j.id
|
||||
entity["next_run_time"] = j.next_run_time.strftime("%m-%d %H:%M:%S")
|
||||
remain = j.next_run_time - datetime.now(timezone("Asia/Seoul"))
|
||||
tmp = ""
|
||||
if remain.days > 0:
|
||||
tmp += "%s일 " % (remain.days)
|
||||
remain = remain.seconds
|
||||
if remain // 3600 > 0:
|
||||
tmp += "%s시간 " % (remain // 3600)
|
||||
remain = remain % 3600
|
||||
if remain // 60 > 0:
|
||||
tmp += "%s분 " % (remain // 60)
|
||||
tmp += "%s초" % (remain % 60)
|
||||
# entity['remain_time'] = (j.next_run_time - datetime.now(timezone('Asia/Seoul'))).seconds
|
||||
entity["remain_time"] = tmp
|
||||
job = self.get_job_instance(j.id)
|
||||
if job is not None:
|
||||
entity["count"] = job.count
|
||||
entity["plugin"] = job.plugin
|
||||
if job.is_cron:
|
||||
entity["interval"] = job.interval
|
||||
elif job.interval == 9999:
|
||||
entity["interval"] = "항상 실행"
|
||||
entity["remain_time"] = ""
|
||||
else:
|
||||
entity["interval"] = "%s분 %s초" % (
|
||||
job.interval,
|
||||
job.interval_seconds,
|
||||
)
|
||||
entity["is_running"] = job.is_running
|
||||
entity["description"] = job.description
|
||||
entity["running_timedelta"] = (
|
||||
job.running_timedelta.seconds
|
||||
if job.running_timedelta is not None
|
||||
else "-"
|
||||
)
|
||||
entity["make_time"] = job.make_time.strftime("%m-%d %H:%M:%S")
|
||||
entity["run"] = job.run
|
||||
else:
|
||||
entity["count"] = ""
|
||||
entity["plugin"] = ""
|
||||
entity["interval"] = ""
|
||||
entity["is_running"] = ""
|
||||
entity["description"] = ""
|
||||
entity["running_timedelta"] = ""
|
||||
entity["make_time"] = ""
|
||||
entity["run"] = True
|
||||
|
||||
ret.append(entity)
|
||||
return ret
|
||||
|
||||
def add_job_instance(self, job_instance, run=True):
|
||||
from framework import app
|
||||
|
||||
if app.config["config"]["run_by_real"] and app.config["config"]["auth_status"]:
|
||||
if not self.is_include(job_instance.job_id):
|
||||
job_instance.run = run
|
||||
Scheduler.job_list.append(job_instance)
|
||||
if job_instance.is_interval:
|
||||
self.sched.add_job(
|
||||
job_instance.job_function,
|
||||
"interval",
|
||||
minutes=job_instance.interval,
|
||||
seconds=job_instance.interval_seconds,
|
||||
id=job_instance.job_id,
|
||||
args=(None),
|
||||
)
|
||||
elif job_instance.is_cron:
|
||||
self.sched.add_job(
|
||||
job_instance.job_function,
|
||||
CronTrigger.from_crontab(job_instance.interval),
|
||||
id=job_instance.job_id,
|
||||
args=(None),
|
||||
)
|
||||
# self.sched.add_job(job_instance.job_function, 'interval', minutes=job_instance.interval,
|
||||
# id=job_instance.job_id, args=(None))
|
||||
job = self.sched.get_job(job_instance.job_id)
|
||||
if run and job_instance.is_interval:
|
||||
tmp = randint(5, 20)
|
||||
job.modify(
|
||||
next_run_time=datetime.now(timezone("Asia/Seoul"))
|
||||
+ timedelta(seconds=tmp)
|
||||
)
|
||||
|
||||
def is_include(self, job_id):
|
||||
job = self.sched.get_job(job_id)
|
||||
return job is not None
|
||||
1
lib/framework/common/__init__.py
Normal file
1
lib/framework/common/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
import os
|
||||
BIN
lib/framework/common/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
lib/framework/common/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
1
lib/framework/common/celery/__init__.py
Executable file
1
lib/framework/common/celery/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from .shutil_task import move, copytree, copy, rmtree, move_exist_remove
|
||||
BIN
lib/framework/common/celery/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
lib/framework/common/celery/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
156
lib/framework/common/celery/shutil_task.py
Executable file
156
lib/framework/common/celery/shutil_task.py
Executable file
@@ -0,0 +1,156 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import traceback
|
||||
import shutil
|
||||
|
||||
from framework import app, celery, logger
|
||||
|
||||
# run_in_celery=True 이미 celery안에서 실행된다. 바로 콜한다.
|
||||
def move(source_path, target_path, run_in_celery=False):
|
||||
try:
|
||||
if app.config['config']['use_celery'] and run_in_celery == False:
|
||||
result = _move_task.apply_async((source_path, target_path))
|
||||
return result.get()
|
||||
else:
|
||||
return _move_task(source_path, target_path)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return _move_task(source_path, target_path)
|
||||
|
||||
|
||||
@celery.task
|
||||
def _move_task(source_path, target_path):
|
||||
try:
|
||||
logger.debug('_move_task:%s %s', source_path, target_path)
|
||||
shutil.move(source_path, target_path)
|
||||
logger.debug('_move_task end')
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def move_exist_remove(source_path, target_path, run_in_celery=False):
|
||||
try:
|
||||
if app.config['config']['use_celery'] and run_in_celery == False:
|
||||
result = _move_exist_remove_task.apply_async((source_path, target_path))
|
||||
return result.get()
|
||||
else:
|
||||
return _move_exist_remove_task(source_path, target_path)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return _move_exist_remove_task(source_path, target_path)
|
||||
|
||||
@celery.task
|
||||
def _move_exist_remove_task(source_path, target_path):
|
||||
try:
|
||||
target_file_path = os.path.join(target_path, os.path.basename(source_path))
|
||||
if os.path.exists(target_file_path):
|
||||
os.remove(source_path)
|
||||
return True
|
||||
logger.debug('_move_exist_remove:%s %s', source_path, target_path)
|
||||
shutil.move(source_path, target_path)
|
||||
logger.debug('_move_exist_remove end')
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
|
||||
def copytree(source_path, target_path):
|
||||
try:
|
||||
if app.config['config']['use_celery']:
|
||||
result = _copytree_task.apply_async((source_path, target_path))
|
||||
return result.get()
|
||||
else:
|
||||
return _copytree_task(source_path, target_path)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return _copytree_task(source_path, target_path)
|
||||
|
||||
@celery.task
|
||||
def _copytree_task(source_path, target_path):
|
||||
try:
|
||||
shutil.copytree(source_path, target_path)
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
# copy
|
||||
def copy(source_path, target_path):
|
||||
try:
|
||||
if app.config['config']['use_celery']:
|
||||
result = _copy_task.apply_async((source_path, target_path))
|
||||
return result.get()
|
||||
else:
|
||||
return _copy_task(source_path, target_path)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return _copy_task(source_path, target_path)
|
||||
|
||||
@celery.task
|
||||
def _copy_task(source_path, target_path):
|
||||
try:
|
||||
shutil.copy(source_path, target_path)
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
# rmtree
|
||||
def rmtree(source_path):
|
||||
try:
|
||||
if app.config['config']['use_celery']:
|
||||
result = _rmtree_task.apply_async((source_path,))
|
||||
return result.get()
|
||||
else:
|
||||
return _rmtree_task(source_path)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return _rmtree_task(source_path)
|
||||
|
||||
@celery.task
|
||||
def _rmtree_task(source_path):
|
||||
try:
|
||||
shutil.rmtree(source_path)
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
|
||||
def remove(remove_path):
|
||||
try:
|
||||
logger.debug('CELERY os.remove start : %s', remove_path)
|
||||
if app.config['config']['use_celery']:
|
||||
result = _remove_task.apply_async((remove_path,))
|
||||
return result.get()
|
||||
else:
|
||||
return _remove_task(remove_path)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return _remove_task(remove_path)
|
||||
finally:
|
||||
logger.debug('CELERY os.remove end : %s', remove_path)
|
||||
|
||||
|
||||
@celery.task
|
||||
def _remove_task(remove_path):
|
||||
try:
|
||||
os.remove(remove_path)
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
1
lib/framework/common/util/__init__.py
Normal file
1
lib/framework/common/util/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .env import *
|
||||
72
lib/framework/common/util/env.py
Normal file
72
lib/framework/common/util/env.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import io
|
||||
import traceback
|
||||
import platform
|
||||
|
||||
from framework import app, logger
|
||||
|
||||
|
||||
def is_arm():
|
||||
try:
|
||||
ret = False
|
||||
import platform
|
||||
if platform.system() == 'Linux':
|
||||
if platform.platform().find('86') == -1 and platform.platform().find('64') == -1:
|
||||
ret = True
|
||||
if platform.platform().find('arch') != -1:
|
||||
ret = True
|
||||
if platform.platform().find('arm') != -1:
|
||||
ret = True
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def is_native():
|
||||
try:
|
||||
return (app.config['config']['running_type'] == 'native')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def is_termux():
|
||||
try:
|
||||
return (app.config['config']['running_type'] == 'termux')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def is_windows():
|
||||
try:
|
||||
return (app.config['config']['running_type'] == 'native' and platform.system() == 'Windows')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def is_mac():
|
||||
try:
|
||||
return (app.config['config']['running_type'] == 'native' and platform.system() == 'Darwin')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def is_docker():
|
||||
try:
|
||||
return (app.config['config']['running_type'] == 'docker')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def is_linux():
|
||||
try:
|
||||
# return (app.config['config']['running_type'] == 'native' and platform.system() == 'Linux')
|
||||
return (platform.system() == 'Linux')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
18
lib/framework/init_args.py
Normal file
18
lib/framework/init_args.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
import sys
|
||||
|
||||
args = None
|
||||
|
||||
|
||||
def process_args():
|
||||
from gommi import process_args
|
||||
|
||||
args = process_args()
|
||||
return args
|
||||
|
||||
|
||||
if sys.argv[0] == "gommi.py":
|
||||
args = process_args()
|
||||
else:
|
||||
args = None
|
||||
60
lib/framework/init_celery.py
Normal file
60
lib/framework/init_celery.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import os, sys, platform
|
||||
|
||||
from framework import app, logger, args
|
||||
|
||||
try:
|
||||
from celery import Celery
|
||||
|
||||
# if app.config['config']['use_celery'] == False: # 변수 할당 전
|
||||
if (
|
||||
args is not None and args.use_gevent == False
|
||||
) or platform.system() == "Windows":
|
||||
raise Exception("no celery")
|
||||
try:
|
||||
redis_port = os.environ["REDIS_PORT"]
|
||||
except:
|
||||
redis_port = "6379"
|
||||
|
||||
app.config["CELERY_BROKER_URL"] = "redis://localhost:%s/0" % redis_port
|
||||
app.config["CELERY_RESULT_BACKEND"] = "redis://localhost:%s/0" % redis_port
|
||||
celery = Celery(
|
||||
app.name,
|
||||
broker=app.config["CELERY_BROKER_URL"],
|
||||
backend=app.config["CELERY_RESULT_BACKEND"],
|
||||
)
|
||||
# celery.conf.update(app.config)
|
||||
celery.conf["CELERY_ENABLE_UTC"] = False
|
||||
# celery.conf['CELERY_TIMEZONE'] = 'Asia/Seoul'
|
||||
celery.conf.update(
|
||||
task_serializer="pickle",
|
||||
result_serializer="pickle",
|
||||
accept_content=["pickle"],
|
||||
timezone="Asia/Seoul",
|
||||
)
|
||||
|
||||
except:
|
||||
"""
|
||||
from functools import wraps
|
||||
class DummyCelery:
|
||||
def task(self, original_function):
|
||||
@wraps(original_function)
|
||||
def wrapper_function(*args, **kwargs): #1
|
||||
return original_function(*args, **kwargs) #2
|
||||
return wrapper_function
|
||||
|
||||
celery = DummyCelery()
|
||||
"""
|
||||
|
||||
def ffff():
|
||||
pass
|
||||
|
||||
class celery(object):
|
||||
class task(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if len(args) > 0:
|
||||
self.f = args[0]
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if len(args) > 0 and type(args[0]) == type(ffff):
|
||||
return args[0]
|
||||
self.f(*args, **kwargs)
|
||||
129
lib/framework/init_etc.py
Normal file
129
lib/framework/init_etc.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import os, traceback, sys # noqa F401
|
||||
|
||||
|
||||
def check_api(original_function):
|
||||
pass
|
||||
|
||||
|
||||
def make_default_dir(path_data):
|
||||
try:
|
||||
if not os.path.exists(path_data):
|
||||
os.mkdir(path_data)
|
||||
tmp = os.path.join(path_data, "tmp")
|
||||
try:
|
||||
import shutil
|
||||
|
||||
if os.path.exists(tmp):
|
||||
shutil.rmtree(tmp)
|
||||
except Exception:
|
||||
pass
|
||||
sub = ["db", "log", "download", "command", "custom", "output", "tmp"]
|
||||
for item in sub:
|
||||
tmp = os.path.join(path_data, item)
|
||||
if not os.path.exists(tmp):
|
||||
os.mkdir(tmp)
|
||||
except Exception as exception:
|
||||
print("Exception:%s", exception)
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
def config_initialize(action):
|
||||
from . import logger, app
|
||||
|
||||
if action == "start":
|
||||
logger.debug("=========== action: strart ===========================")
|
||||
# sjva server 초기에 필요한 정보 불러오는 루틴
|
||||
init_define()
|
||||
app.config["config"]["run_by_real"] = (
|
||||
True
|
||||
if sys.argv[0] == "sjva.py" or sys.argv[0] == "sjva3.py"
|
||||
else False
|
||||
)
|
||||
# app.config['config']['run_by_migration'] = True if sys.argv[-2] == 'db' else False
|
||||
app.config["config"]["run_by_worker"] = (
|
||||
True if sys.argv[0].find("celery") != -1 else False
|
||||
)
|
||||
app.config["config"]["run_by_init_db"] = (
|
||||
True if sys.argv[-1] == "init_db" else False
|
||||
)
|
||||
if sys.version_info[0] == 2:
|
||||
app.config["config"]["pip"] = "pip"
|
||||
app.config["config"]["is_py2"] = True
|
||||
app.config["config"]["is_py3"] = False
|
||||
else:
|
||||
app.config["config"]["is_py2"] = False
|
||||
app.config["config"]["is_py3"] = True
|
||||
app.config["config"]["pip"] = "pip3"
|
||||
|
||||
app.config["config"]["is_debug"] = False
|
||||
app.config["config"]["repeat"] = -1
|
||||
|
||||
if app.config["config"]["run_by_real"]:
|
||||
try:
|
||||
if len(sys.argv) > 2:
|
||||
app.config["config"]["repeat"] = int(sys.argv[2])
|
||||
except:
|
||||
app.config["config"]["repeat"] = 0
|
||||
if len(sys.argv) > 3:
|
||||
try:
|
||||
app.config["config"]["is_debug"] = sys.argv[-1] == "debug"
|
||||
except:
|
||||
app.config["config"]["is_debug"] = False
|
||||
app.config["config"]["use_celery"] = True
|
||||
for tmp in sys.argv:
|
||||
if tmp == "no_celery":
|
||||
app.config["config"]["use_celery"] = False
|
||||
break
|
||||
# logger.debug('use_celery : %s', app.config['config']['use_celery'])
|
||||
logger.debug("======================================")
|
||||
elif action == "auth":
|
||||
from system.logic_auth import SystemLogicAuth
|
||||
|
||||
# 2021-08-11 로딩시 인증 실행 여부
|
||||
# SystemLogicAuth.do_auth()
|
||||
|
||||
tmp = SystemLogicAuth.get_auth_status()
|
||||
app.config["config"]["auth_status"] = tmp["ret"]
|
||||
app.config["config"]["auth_desc"] = tmp["desc"]
|
||||
app.config["config"]["level"] = tmp["level"]
|
||||
app.config["config"]["point"] = tmp["point"]
|
||||
# app.config['config']['auth_status'] = True
|
||||
elif action == "system_loading_after":
|
||||
from . import SystemModelSetting
|
||||
|
||||
try:
|
||||
app.config["config"]["is_server"] = (
|
||||
SystemModelSetting.get("ddns")
|
||||
== app.config["DEFINE"]["MAIN_SERVER_URL"]
|
||||
)
|
||||
except Exception:
|
||||
app.config["config"]["is_server"] = False
|
||||
|
||||
if (
|
||||
app.config["config"]["is_server"]
|
||||
or app.config["config"]["is_debug"]
|
||||
):
|
||||
app.config["config"]["server"] = True
|
||||
app.config["config"]["is_admin"] = True
|
||||
else:
|
||||
app.config["config"]["server"] = False
|
||||
app.config["config"]["is_admin"] = False
|
||||
|
||||
app.config["config"]["running_type"] = "native"
|
||||
# print(f"os.environ:: {os.environ}")
|
||||
|
||||
if "SJVA_RUNNING_TYPE" in os.environ:
|
||||
app.config["config"]["running_type"] = os.environ[
|
||||
"SJVA_RUNNING_TYPE"
|
||||
]
|
||||
else:
|
||||
import platform
|
||||
|
||||
if platform.system() == "Windows":
|
||||
app.config["config"]["running_type"] = "windows"
|
||||
|
||||
|
||||
def init_define():
|
||||
from . import logger, app
|
||||
|
||||
app.config["DEFINE"] = {}
|
||||
306
lib/framework/init_plugin.py
Normal file
306
lib/framework/init_plugin.py
Normal file
@@ -0,0 +1,306 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ==================================================================
|
||||
# python
|
||||
# ==================================================================
|
||||
import os, sys, traceback, threading, platform
|
||||
from framework import app, db, logger, plugin_instance_list, plugin_menu
|
||||
import system
|
||||
|
||||
# ==================================================================
|
||||
# plugin
|
||||
# ==================================================================
|
||||
|
||||
|
||||
def is_include_menu(plugin_name):
|
||||
try:
|
||||
if plugin_name not in [
|
||||
"daum_tv",
|
||||
"ffmpeg",
|
||||
"fileprocess_movie",
|
||||
"gdrive_scan",
|
||||
"ktv",
|
||||
"plex",
|
||||
"rclone",
|
||||
]:
|
||||
return True
|
||||
if (
|
||||
system.SystemLogic.get_setting_value("use_plugin_%s" % plugin_name)
|
||||
== "True"
|
||||
):
|
||||
return True
|
||||
elif (
|
||||
system.SystemLogic.get_setting_value("use_plugin_%s" % plugin_name)
|
||||
== "False"
|
||||
):
|
||||
return False
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return True
|
||||
|
||||
|
||||
def plugin_init():
|
||||
try:
|
||||
if not app.config["config"]["auth_status"]:
|
||||
return
|
||||
import inspect
|
||||
|
||||
plugin_path = os.path.join(
|
||||
os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
),
|
||||
"plugin",
|
||||
)
|
||||
sys.path.insert(0, plugin_path)
|
||||
from . import SystemModelSetting
|
||||
|
||||
"""
|
||||
try:
|
||||
from . import SystemModelSetting
|
||||
plugins = ['command', 'mod']
|
||||
for plugin in os.listdir(plugin_path):
|
||||
plugins.append(plugin)
|
||||
except:
|
||||
plugins = os.listdir(plugin_path)
|
||||
"""
|
||||
plugins = os.listdir(plugin_path)
|
||||
|
||||
pass_include = []
|
||||
except_plugin_list = []
|
||||
|
||||
# 2019-07-17
|
||||
try:
|
||||
plugin_path = os.path.join(
|
||||
os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
),
|
||||
"data",
|
||||
"custom",
|
||||
)
|
||||
sys.path.insert(1, plugin_path)
|
||||
tmps = os.listdir(plugin_path)
|
||||
add_plugin_list = []
|
||||
for t in tmps:
|
||||
if not t.startswith("_") and os.path.isdir(
|
||||
os.path.join(plugin_path, t)
|
||||
):
|
||||
add_plugin_list.append(t)
|
||||
plugins = plugins + add_plugin_list
|
||||
pass_include = pass_include + add_plugin_list
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# 2018-09-04
|
||||
try:
|
||||
plugin_path = SystemModelSetting.get("plugin_dev_path")
|
||||
if plugin_path != "":
|
||||
if os.path.exists(plugin_path):
|
||||
sys.path.insert(0, plugin_path)
|
||||
tmps = os.listdir(plugin_path)
|
||||
add_plugin_list = []
|
||||
for t in tmps:
|
||||
if not t.startswith("_") and os.path.isdir(
|
||||
os.path.join(plugin_path, t)
|
||||
):
|
||||
add_plugin_list.append(t)
|
||||
if app.config["config"]["level"] < 4:
|
||||
break
|
||||
plugins = plugins + add_plugin_list
|
||||
pass_include = pass_include + add_plugin_list
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
plugins = sorted(plugins)
|
||||
|
||||
logger.debug(plugins)
|
||||
for plugin_name in plugins:
|
||||
# logger.debug(len(system.LogicPlugin.current_loading_plugin_list))
|
||||
if plugin_name.startswith("_"):
|
||||
continue
|
||||
if plugin_name == "terminal" and platform.system() == "Windows":
|
||||
continue
|
||||
if plugin_name in except_plugin_list:
|
||||
logger.debug("Except plugin : %s" % plugin_menu)
|
||||
continue
|
||||
logger.debug(f"[+] PLUGIN LOADING Start.. [{plugin_name}]")
|
||||
try:
|
||||
mod = __import__("%s" % (plugin_name), fromlist=[])
|
||||
mod_plugin_info = None
|
||||
# 2021-12-31
|
||||
if (
|
||||
plugin_name
|
||||
not in system.LogicPlugin.current_loading_plugin_list
|
||||
):
|
||||
system.LogicPlugin.current_loading_plugin_list[
|
||||
plugin_name
|
||||
] = {"status": "loading"}
|
||||
|
||||
try:
|
||||
mod_plugin_info = getattr(mod, "plugin_info")
|
||||
if (
|
||||
"category" not in mod_plugin_info
|
||||
and "category_name" in mod_plugin_info
|
||||
):
|
||||
mod_plugin_info["category"] = mod_plugin_info[
|
||||
"category_name"
|
||||
]
|
||||
if "policy_point" in mod_plugin_info:
|
||||
if (
|
||||
mod_plugin_info["policy_point"]
|
||||
> app.config["config"]["point"]
|
||||
):
|
||||
system.LogicPlugin.current_loading_plugin_list[
|
||||
plugin_name
|
||||
]["status"] = "violation_policy_point"
|
||||
continue
|
||||
if "policy_level" in mod_plugin_info:
|
||||
if (
|
||||
mod_plugin_info["policy_level"]
|
||||
> app.config["config"]["level"]
|
||||
):
|
||||
system.LogicPlugin.current_loading_plugin_list[
|
||||
plugin_name
|
||||
]["status"] = "violation_policy_level"
|
||||
continue
|
||||
if (
|
||||
"category" in mod_plugin_info
|
||||
and mod_plugin_info["category"] == "beta"
|
||||
):
|
||||
if SystemModelSetting.get_bool("use_beta") == False:
|
||||
system.LogicPlugin.current_loading_plugin_list[
|
||||
plugin_name
|
||||
]["status"] = "violation_beta"
|
||||
continue
|
||||
except Exception as exception:
|
||||
# logger.error('Exception:%s', exception)
|
||||
# logger.error(traceback.format_exc())
|
||||
logger.debug(f"[!] PLUGIN_INFO not exist : [{plugin_name}]")
|
||||
|
||||
try:
|
||||
mod_blue_print = getattr(mod, "blueprint")
|
||||
if mod_blue_print:
|
||||
if plugin_name in pass_include or is_include_menu(
|
||||
plugin_name
|
||||
):
|
||||
app.register_blueprint(mod_blue_print)
|
||||
except Exception as exception:
|
||||
# logger.error('Exception:%s', exception)
|
||||
# logger.error(traceback.format_exc())
|
||||
logger.debug(f"[!] BLUEPRINT not exist : [{plugin_name}]")
|
||||
plugin_instance_list[plugin_name] = mod
|
||||
system.LogicPlugin.current_loading_plugin_list[plugin_name][
|
||||
"status"
|
||||
] = "success"
|
||||
system.LogicPlugin.current_loading_plugin_list[plugin_name][
|
||||
"info"
|
||||
] = mod_plugin_info
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.debug("no blueprint")
|
||||
|
||||
# from tool_base import d
|
||||
# logger.error(d(system.LogicPlugin.current_loading_plugin_list))
|
||||
# 2021-07-01 모듈에 있는 DB 테이블 생성이 안되는 문제
|
||||
# 기존 구조 : db.create_all() => 모듈 plugin_load => celery task 등록 후 리턴
|
||||
# 변경 구조 : 모듈 plugin_load => db.create_all() => celery인 경우 리턴
|
||||
|
||||
# plugin_load 를 해야 하위 로직에 있는 DB가 로딩된다.
|
||||
# plugin_load 에 db는 사용하는 코드가 있으면 안된다. (테이블도 없을 때 에러발생)
|
||||
try:
|
||||
# logger.warning('module plugin_load in celery ')
|
||||
plugin_instance_list["mod"].plugin_load()
|
||||
except Exception as exception:
|
||||
logger.debug("mod plugin_load error!!")
|
||||
# logger.error('Exception:%s', exception)
|
||||
# logger.error(traceback.format_exc())
|
||||
|
||||
# import가 끝나면 DB를 만든다.
|
||||
# 플러그인 로드시 DB 초기화를 할 수 있다.
|
||||
if not app.config["config"]["run_by_worker"]:
|
||||
try:
|
||||
db.create_all()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.debug("db.create_all error")
|
||||
|
||||
if not app.config["config"]["run_by_real"]:
|
||||
# 2021-06-03
|
||||
# 모듈의 로직에 있는 celery 함수는 등록해주어야한다.
|
||||
# try:
|
||||
# logger.warning('module plugin_load in celery ')
|
||||
# plugin_instance_list['mod'].plugin_load()
|
||||
# except Exception as exception:
|
||||
# logger.error('module plugin_load error')
|
||||
# logger.error('Exception:%s', exception)
|
||||
# logger.error(traceback.format_exc())
|
||||
# 2021-07-01
|
||||
# db때문에 위에서 로딩함.
|
||||
return
|
||||
|
||||
for key, mod in plugin_instance_list.items():
|
||||
try:
|
||||
mod_plugin_load = getattr(mod, "plugin_load")
|
||||
if mod_plugin_load and (
|
||||
key in pass_include or is_include_menu(key)
|
||||
):
|
||||
|
||||
def func(mod, key):
|
||||
try:
|
||||
logger.debug(
|
||||
f"[!] plugin_load threading start : [{key}]"
|
||||
)
|
||||
mod.plugin_load()
|
||||
logger.debug(
|
||||
f"[!] plugin_load threading end : [{key}]"
|
||||
)
|
||||
except Exception as exception:
|
||||
logger.error("### plugin_load exception : %s", key)
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# mod는 위에서 로딩
|
||||
if key != "mod":
|
||||
t = threading.Thread(target=func, args=(mod, key))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
# if key == 'mod':
|
||||
# t.join()
|
||||
except Exception as exception:
|
||||
logger.debug(f"[!] PLUGIN_LOAD function not exist : [{key}]")
|
||||
# logger.error('Exception:%s', exception)
|
||||
# logger.error(traceback.format_exc())
|
||||
# logger.debug('no init_scheduler')
|
||||
try:
|
||||
mod_menu = getattr(mod, "menu")
|
||||
if mod_menu and (key in pass_include or is_include_menu(key)):
|
||||
plugin_menu.append(mod_menu)
|
||||
except Exception as exception:
|
||||
logger.debug("no menu")
|
||||
logger.debug(
|
||||
"### plugin_load threading all start.. : %s ",
|
||||
len(plugin_instance_list),
|
||||
)
|
||||
# 모든 모듈을 로드한 이후에 app 등록, table 생성, start
|
||||
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def plugin_unload():
|
||||
for key, mod in plugin_instance_list.items():
|
||||
try:
|
||||
# if plugin_name == 'rss':
|
||||
# continue
|
||||
mod_plugin_unload = getattr(mod, "plugin_unload")
|
||||
if mod_plugin_unload:
|
||||
mod.plugin_unload()
|
||||
except Exception as exception:
|
||||
logger.error("module:%s", key)
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
system.plugin_unload()
|
||||
97
lib/framework/init_route.py
Normal file
97
lib/framework/init_route.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import traceback
|
||||
|
||||
# third-party
|
||||
from flask import (
|
||||
redirect,
|
||||
render_template,
|
||||
Response,
|
||||
request,
|
||||
jsonify,
|
||||
send_from_directory,
|
||||
)
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
|
||||
# sjva 공용
|
||||
from framework import (
|
||||
app,
|
||||
db,
|
||||
version,
|
||||
USERS,
|
||||
login_manager,
|
||||
logger,
|
||||
path_data,
|
||||
check_api,
|
||||
)
|
||||
import system
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
remember = request.form["remember"] == "True"
|
||||
if username not in USERS:
|
||||
return jsonify("no_id")
|
||||
elif not USERS[username].can_login(password):
|
||||
return jsonify("wrong_password")
|
||||
else:
|
||||
USERS[username].authenticated = True
|
||||
login_user(USERS[username], remember=remember)
|
||||
return jsonify("redirect")
|
||||
else:
|
||||
if (
|
||||
db.session.query(system.ModelSetting)
|
||||
.filter_by(key="use_login")
|
||||
.first()
|
||||
.value
|
||||
== "False"
|
||||
):
|
||||
username = (
|
||||
db.session.query(system.ModelSetting)
|
||||
.filter_by(key="id")
|
||||
.first()
|
||||
.value
|
||||
)
|
||||
logger.debug(f"username:: {username}")
|
||||
logger.debug(f"USERS:: {USERS}")
|
||||
USERS[username].authenticated = True
|
||||
login_user(USERS[username], remember=True)
|
||||
# current_user = USERS[username]
|
||||
return redirect(request.args.get("next"))
|
||||
|
||||
return render_template("login.html", next=request.args.get("next"))
|
||||
|
||||
|
||||
@app.errorhandler(401)
|
||||
def custom_401(error):
|
||||
|
||||
# return Response('<Why access is denied string goes here...>', 401, {'WWW-Authenticate':'Basic realm="Login Required"'})
|
||||
return "login_required"
|
||||
|
||||
|
||||
# important!!
|
||||
@login_manager.user_loader
|
||||
def user_loader(user_id):
|
||||
return USERS[user_id]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# API
|
||||
# ============================================================================
|
||||
@app.route("/")
|
||||
@app.route("/None")
|
||||
@app.route("/home")
|
||||
def home():
|
||||
logger.warning(request.host_url)
|
||||
return redirect("/system/home")
|
||||
|
||||
|
||||
@app.route("/version")
|
||||
def get_version():
|
||||
# return jsonify(version)
|
||||
return version
|
||||
118
lib/framework/init_web.py
Normal file
118
lib/framework/init_web.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import re
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
def get_menu(full_query):
|
||||
match = re.compile(r"\/(?P<menu>.*?)\/manual\/(?P<sub2>.*?)($|\?)").match(
|
||||
full_query
|
||||
)
|
||||
if match:
|
||||
return match.group("menu"), "manual", match.group("sub2")
|
||||
|
||||
match = re.compile(
|
||||
r"\/(?P<menu>.*?)\/(?P<sub>.*?)\/(?P<sub2>.*?)($|\/|\?)"
|
||||
).match(full_query)
|
||||
if match:
|
||||
return match.group("menu"), match.group("sub"), match.group("sub2")
|
||||
|
||||
match = re.compile(r"\/(?P<menu>.*?)\/(?P<sub>.*?)($|\/|\?)").match(
|
||||
full_query
|
||||
)
|
||||
if match:
|
||||
return match.group("menu"), match.group("sub"), None
|
||||
|
||||
match = re.compile(r"\/(?P<menu>.*?)($|\/|\?)").match(full_query)
|
||||
if match:
|
||||
return match.group("menu"), None, None
|
||||
return "home", None, None
|
||||
|
||||
|
||||
def get_theme():
|
||||
|
||||
theme_list = {
|
||||
"Default": 56,
|
||||
"Cerulean": 56,
|
||||
"Cosmo": 54,
|
||||
"Cyborg": 54,
|
||||
"Darkly": 70,
|
||||
"Flatly": 70,
|
||||
"Journal": 56,
|
||||
"Litera": 57,
|
||||
"Lumen": 56,
|
||||
"Lux": 88,
|
||||
"Materia": 80,
|
||||
"Minty": 56,
|
||||
"Morph": 56,
|
||||
"Pulse": 75,
|
||||
"Quartz": 92,
|
||||
"Sandstone": 53,
|
||||
"Simplex": 67,
|
||||
"Sketchy": 56,
|
||||
"Slate": 53,
|
||||
"Solar": 56,
|
||||
"Spacelab": 58,
|
||||
"Superhero": 48,
|
||||
"United": 56,
|
||||
"Vapor": 56,
|
||||
"Yeti": 54,
|
||||
"Zephyr": 68,
|
||||
}
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
theme = SystemModelSetting.get("theme")
|
||||
return [theme, theme_list[theme]]
|
||||
|
||||
|
||||
def get_login_status():
|
||||
if current_user is None:
|
||||
return False
|
||||
return current_user.is_authenticated
|
||||
|
||||
|
||||
def get_web_title():
|
||||
try:
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
return SystemModelSetting.get("web_title")
|
||||
except Exception:
|
||||
return "GOMMI Assitant"
|
||||
|
||||
|
||||
def show_menu():
|
||||
from flask import request
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
if SystemModelSetting.get_bool("hide_menu"):
|
||||
if request.full_path.find("/login") != -1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_https():
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
return SystemModelSetting.get("ddns").find("https://") != -1
|
||||
|
||||
|
||||
def jinja_initialize(app):
|
||||
from .menu import get_menu_map, get_plugin_menu
|
||||
|
||||
app.jinja_env.globals.update(get_menu=get_menu)
|
||||
app.jinja_env.globals.update(get_theme=get_theme)
|
||||
app.jinja_env.globals.update(get_menu_map=get_menu_map)
|
||||
app.jinja_env.globals.update(get_login_status=get_login_status)
|
||||
app.jinja_env.globals.update(get_web_title=get_web_title)
|
||||
app.jinja_env.globals.update(get_plugin_menu=get_plugin_menu)
|
||||
app.jinja_env.globals.update(show_menu=show_menu)
|
||||
app.jinja_env.globals.update(is_https=is_https)
|
||||
app.jinja_env.filters["get_menu"] = get_menu
|
||||
app.jinja_env.filters["get_theme"] = get_theme
|
||||
app.jinja_env.filters["get_menu_map"] = get_menu_map
|
||||
app.jinja_env.filters["get_login_status"] = get_login_status
|
||||
app.jinja_env.filters["get_web_title"] = get_web_title
|
||||
app.jinja_env.filters["get_plugin_menu"] = get_plugin_menu
|
||||
app.jinja_env.filters["show_menu"] = show_menu
|
||||
app.jinja_env.filters["is_https"] = is_https
|
||||
app.jinja_env.auto_reload = True
|
||||
|
||||
app.jinja_env.add_extension("jinja2.ext.loopcontrols")
|
||||
121
lib/framework/job.py
Normal file
121
lib/framework/job.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import traceback
|
||||
import threading
|
||||
|
||||
from datetime import datetime
|
||||
from pytz import timezone
|
||||
from random import randint
|
||||
|
||||
# third-party
|
||||
|
||||
# sjva 공용
|
||||
from framework import scheduler, app
|
||||
from framework.logger import get_logger
|
||||
|
||||
# 패키지
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split(".")[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
|
||||
def multiprocessing_target(*a, **b):
|
||||
job_id = a[0]
|
||||
job = scheduler.get_job_instance(job_id)
|
||||
if job.args is None:
|
||||
job.target_function()
|
||||
else:
|
||||
job.target_function(job.args)
|
||||
|
||||
|
||||
class Job(object):
|
||||
def __init__(
|
||||
self,
|
||||
plugin,
|
||||
job_id,
|
||||
interval,
|
||||
target_function,
|
||||
description,
|
||||
can_remove_by_framework,
|
||||
args=None,
|
||||
):
|
||||
self.plugin = plugin
|
||||
self.job_id = job_id
|
||||
self.interval = "%s" % interval
|
||||
self.interval_seconds = randint(1, 59)
|
||||
self.target_function = target_function
|
||||
self.description = description
|
||||
self.can_remove_by_framework = can_remove_by_framework
|
||||
self.is_running = False
|
||||
self.thread = None
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.running_timedelta = None
|
||||
self.status = None
|
||||
self.count = 0
|
||||
self.make_time = datetime.now(timezone("Asia/Seoul"))
|
||||
if len(self.interval.strip().split(" ")) == 5:
|
||||
self.is_cron = True
|
||||
self.is_interval = False
|
||||
else:
|
||||
self.is_cron = False
|
||||
self.is_interval = True
|
||||
if self.is_interval:
|
||||
if app.config["config"]["is_py2"]:
|
||||
# if isinstance(self.interval, unicode) or isinstance(self.interval, str):
|
||||
# self.interval = int(self.interval)
|
||||
if type(self.interval) == type("") or type(
|
||||
self.interval
|
||||
) == type(""):
|
||||
self.interval = int(self.interval)
|
||||
else:
|
||||
if isinstance(self.interval, str):
|
||||
self.interval = int(self.interval)
|
||||
|
||||
self.args = args
|
||||
self.run = True
|
||||
|
||||
def job_function(self):
|
||||
try:
|
||||
# if self.count > 1:
|
||||
# logger.debug(hm.check('JOB START %s' % self.job_id))
|
||||
# logger.debug(hm.getHeap())
|
||||
self.is_running = True
|
||||
self.start_time = datetime.now(timezone("Asia/Seoul"))
|
||||
# import gipc
|
||||
# from multiprocessing import Process
|
||||
if self.args is None:
|
||||
self.thread = threading.Thread(
|
||||
target=self.target_function, args=()
|
||||
)
|
||||
# self.thread = Process(target=multiprocessing_target, args=(self.job_id,))
|
||||
# self.thread = gipc.start_process(target=multiprocessing_target, args=(self.job_id,), daemon=True)
|
||||
# self.target_function()
|
||||
else:
|
||||
self.thread = threading.Thread(
|
||||
target=self.target_function, args=(self.args,)
|
||||
)
|
||||
# self.thread = Process(target=multiprocessing_target, args=(self.job_id,))
|
||||
# self.thread = gipc.start_process(target=multiprocessing_target, args=(self.job_id,), daemon=True)
|
||||
# self.target_function(self.args)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
self.thread.join()
|
||||
self.end_time = datetime.now(timezone("Asia/Seoul"))
|
||||
self.running_timedelta = self.end_time - self.start_time
|
||||
self.status = "success"
|
||||
if not scheduler.is_include(self.job_id):
|
||||
scheduler.remove_job_instance(self.job_id)
|
||||
self.count += 1
|
||||
except Exception as exception:
|
||||
self.status = "exception"
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
finally:
|
||||
self.is_running = False
|
||||
# if self.count > 1:
|
||||
# logger.debug(hm.check('JOB END %s' % self.job_id))
|
||||
# logger.debug(hm.getHeap())
|
||||
166
lib/framework/log_viewer.py
Normal file
166
lib/framework/log_viewer.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import time
|
||||
import threading
|
||||
|
||||
# third-party
|
||||
from flask import request
|
||||
from flask_socketio import SocketIO, emit
|
||||
|
||||
# sjva 공용
|
||||
from framework import app, socketio, path_data, logger
|
||||
from framework.logger import get_logger
|
||||
from framework.util import SingletonClass
|
||||
|
||||
|
||||
# 패키지
|
||||
|
||||
# 로그
|
||||
|
||||
#########################################################
|
||||
|
||||
namespace = "log"
|
||||
|
||||
|
||||
@socketio.on("connect", namespace="/%s" % namespace)
|
||||
def socket_connect():
|
||||
logger.debug("log connect")
|
||||
|
||||
|
||||
@socketio.on("start", namespace="/%s" % namespace)
|
||||
def socket_file(data):
|
||||
try:
|
||||
package = filename = None
|
||||
if "package" in data:
|
||||
package = data["package"]
|
||||
else:
|
||||
filename = data["filename"]
|
||||
LogViewer.instance().start(package, filename, request.sid)
|
||||
logger.debug(
|
||||
"start package:%s filename:%s sid:%s",
|
||||
package,
|
||||
filename,
|
||||
request.sid,
|
||||
)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
@socketio.on("disconnect", namespace="/%s" % namespace)
|
||||
def disconnect():
|
||||
try:
|
||||
LogViewer.instance().disconnect(request.sid)
|
||||
logger.debug("disconnect sid:%s", request.sid)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
class WatchThread(threading.Thread):
|
||||
def __init__(self, package, filename):
|
||||
super(WatchThread, self).__init__()
|
||||
self.stop_flag = False
|
||||
self.package = package
|
||||
self.filename = filename
|
||||
self.daemon = True
|
||||
|
||||
def stop(self):
|
||||
self.stop_flag = True
|
||||
|
||||
def run(self):
|
||||
logger.debug("WatchThread.. Start %s", self.package)
|
||||
if self.package is not None:
|
||||
logfile = os.path.join(path_data, "log", "%s.log" % self.package)
|
||||
key = "package"
|
||||
value = self.package
|
||||
else:
|
||||
logfile = os.path.join(path_data, "log", self.filename)
|
||||
key = "filename"
|
||||
value = self.filename
|
||||
if os.path.exists(logfile):
|
||||
with open(logfile, "r") as f:
|
||||
f.seek(0, os.SEEK_END)
|
||||
while not self.stop_flag:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
time.sleep(0.1) # Sleep briefly
|
||||
continue
|
||||
socketio.emit(
|
||||
"add",
|
||||
{key: value, "data": line},
|
||||
namespace="/log",
|
||||
broadcast=True,
|
||||
)
|
||||
logger.debug("WatchThread.. End %s", value)
|
||||
else:
|
||||
socketio.emit(
|
||||
"add",
|
||||
{key: value, "data": "not exist logfile"},
|
||||
namespace="/log",
|
||||
broadcast=True,
|
||||
)
|
||||
|
||||
|
||||
class LogViewer(SingletonClass):
|
||||
|
||||
watch_list = {}
|
||||
|
||||
@classmethod
|
||||
def start(cls, package, filename, sid):
|
||||
# 2019-04-02 간만에 봤더니 헷깔려서 적는다
|
||||
# 이 쓰레드는 오픈시 이전 데이타만을 보내는 쓰레드다. 실시간보는거 아님.
|
||||
|
||||
def thread_function():
|
||||
if package is not None:
|
||||
logfile = os.path.join(path_data, "log", "%s.log" % package)
|
||||
else:
|
||||
logfile = os.path.join(path_data, "log", filename)
|
||||
if os.path.exists(logfile):
|
||||
ins_file = open(logfile, "r", encoding="utf8") ## 3)
|
||||
line = ins_file.read()
|
||||
socketio.emit("on_start", {"data": line}, namespace="/log")
|
||||
logger.debug("on_start end")
|
||||
else:
|
||||
socketio.emit(
|
||||
"on_start", {"data": "not exist logfile"}, namespace="/log"
|
||||
)
|
||||
|
||||
if package is not None:
|
||||
key = package
|
||||
else:
|
||||
key = filename
|
||||
thread = threading.Thread(target=thread_function, args=())
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
if key not in cls.watch_list:
|
||||
cls.watch_list[key] = {}
|
||||
cls.watch_list[key]["sid"] = []
|
||||
cls.watch_list[key]["thread"] = WatchThread(package, filename)
|
||||
cls.watch_list[key]["thread"].start()
|
||||
cls.watch_list[key]["sid"].append(sid)
|
||||
|
||||
@classmethod
|
||||
def disconnect(cls, sid):
|
||||
find = False
|
||||
find_key = None
|
||||
for key, value in cls.watch_list.items():
|
||||
logger.debug("key:%s value:%s", key, value)
|
||||
for s in value["sid"]:
|
||||
if sid == s:
|
||||
find = True
|
||||
find_key = key
|
||||
value["sid"].remove(s)
|
||||
break
|
||||
if find:
|
||||
break
|
||||
if not find:
|
||||
return
|
||||
if not cls.watch_list[find_key]["sid"]:
|
||||
logger.debug("thread kill")
|
||||
cls.watch_list[find_key]["thread"].stop()
|
||||
del cls.watch_list[find_key]
|
||||
76
lib/framework/logger.py
Normal file
76
lib/framework/logger.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
import logging
|
||||
import logging.handlers
|
||||
from datetime import datetime
|
||||
from framework import path_data
|
||||
|
||||
from pytz import timezone, utc
|
||||
from support.logger import CustomFormatter
|
||||
|
||||
level_unset_logger_list = []
|
||||
logger_list = []
|
||||
|
||||
|
||||
def get_logger(name):
|
||||
logger = logging.getLogger(name)
|
||||
# print(logger.handlers)
|
||||
if not logger.handlers:
|
||||
global level_unset_logger_list
|
||||
global logger_list
|
||||
level = logging.DEBUG
|
||||
from framework import flag_system_loading
|
||||
|
||||
try:
|
||||
if flag_system_loading:
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
level = logging.DEBUG
|
||||
if level_unset_logger_list is not None:
|
||||
for item in level_unset_logger_list:
|
||||
item.setLevel(level)
|
||||
level_unset_logger_list = None
|
||||
else:
|
||||
level_unset_logger_list.append(logger)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.setLevel(level)
|
||||
formatter = logging.Formatter(
|
||||
"[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s"
|
||||
)
|
||||
|
||||
def customTime(*args):
|
||||
utc_dt = utc.localize(datetime.utcnow())
|
||||
my_tz = timezone("Asia/Seoul")
|
||||
converted = utc_dt.astimezone(my_tz)
|
||||
return converted.timetuple()
|
||||
|
||||
formatter.converter = customTime
|
||||
file_max_bytes = 1 * 1024 * 1024
|
||||
fileHandler = logging.handlers.RotatingFileHandler(
|
||||
filename=os.path.join(path_data, "log", "%s.log" % name),
|
||||
maxBytes=file_max_bytes,
|
||||
backupCount=5,
|
||||
encoding="utf8",
|
||||
delay=True,
|
||||
)
|
||||
streamHandler = logging.StreamHandler()
|
||||
|
||||
# handler에 fommater 세팅
|
||||
fileHandler.setFormatter(formatter)
|
||||
streamHandler.setFormatter(CustomFormatter())
|
||||
|
||||
# Handler를 logging에 추가
|
||||
logger.addHandler(fileHandler)
|
||||
logger.addHandler(streamHandler)
|
||||
return logger
|
||||
|
||||
|
||||
def set_level(level):
|
||||
global logger_list
|
||||
try:
|
||||
for log in logger_list:
|
||||
log.setLevel(level)
|
||||
except Exception:
|
||||
pass
|
||||
427
lib/framework/menu.py
Normal file
427
lib/framework/menu.py
Normal file
@@ -0,0 +1,427 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import copy
|
||||
|
||||
MENU_MAP = [
|
||||
{
|
||||
"category": "토렌트",
|
||||
"name": "torrent",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "rss2", "name": "RSS2"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "downloader", "name": "다운로드"},
|
||||
{"type": "plugin", "plugin": "rss_downloader", "name": "RSS 다운로드"},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "bot_downloader_ktv",
|
||||
"name": "봇 다운로드 - TV",
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "bot_downloader_movie",
|
||||
"name": "봇 다운로드 - 영화",
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "bot_downloader_av",
|
||||
"name": "봇 다운로드 - AV",
|
||||
},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "offcloud2", "name": "Offcloud2"},
|
||||
{"type": "plugin", "plugin": "torrent_info", "name": "토렌트 정보"},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "VOD",
|
||||
"name": "vod",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "ffmpeg", "name": "FFMPEG"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "wavve", "name": "웨이브"},
|
||||
{"type": "plugin", "plugin": "tving", "name": "티빙"},
|
||||
{"type": "plugin", "plugin": "nsearch", "name": "검색"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "ani24", "name": "애니24"},
|
||||
{"type": "plugin", "plugin": "youtube-dl", "name": "youtube-dl"},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "파일처리",
|
||||
"name": "fileprocess",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "ktv", "name": "국내방송"},
|
||||
{"type": "plugin", "plugin": "fileprocess_movie", "name": "영화"},
|
||||
{"type": "plugin", "plugin": "fileprocess_av", "name": "AV"},
|
||||
{"type": "plugin", "plugin": "musicProc", "name": "음악"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "smi2srt", "name": "SMI to SRT"},
|
||||
{"type": "plugin", "plugin": "synoindex", "name": "Synoindex"},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "PLEX",
|
||||
"name": "plex",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "plex", "name": "PLEX"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "gdrive_scan", "name": "GDrive 스캔"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "av_agent", "name": "AV Agent"},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "TV",
|
||||
"name": "tv",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "klive", "name": "KLive"},
|
||||
{"type": "plugin", "plugin": "tvheadend", "name": "Tvheadend"},
|
||||
{"type": "plugin", "plugin": "hdhomerun", "name": "HDHomerun"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "epg", "name": "EPG"},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "서비스",
|
||||
"name": "service",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "kthoom", "name": "kthoom"},
|
||||
{"type": "plugin", "plugin": "manamoa", "name": "manamoa"},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "webtoon_naver",
|
||||
"name": "webtoon_naver",
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "webtoon_daum",
|
||||
"name": "webtoon_daum",
|
||||
},
|
||||
{"type": "divider"},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "podcast_rss_maker",
|
||||
"name": "podcast_rss_maker",
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"plugin": "gd_share_client",
|
||||
"name": "gd_share_client",
|
||||
},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "툴",
|
||||
"name": "tool",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "rclone", "name": "Rclone"},
|
||||
{"type": "plugin", "plugin": "vnStat", "name": "vnStat"},
|
||||
{"type": "plugin", "plugin": "aria2", "name": "aria2"},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "daum_tv", "name": "Daum TV"},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "런처",
|
||||
"name": "launcher",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "베타",
|
||||
"name": "beta",
|
||||
"type": "plugin",
|
||||
"position": "left",
|
||||
"list": [],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "Custom",
|
||||
"name": "custom",
|
||||
"type": "custom",
|
||||
"position": "left",
|
||||
"list": [],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "링크",
|
||||
"name": "link",
|
||||
"type": "link",
|
||||
"position": "right",
|
||||
"list": [
|
||||
{"type": "link", "name": "PLEX", "link": "https://app.plex.tv"},
|
||||
{"type": "divider"},
|
||||
{
|
||||
"type": "link",
|
||||
"name": "나스당",
|
||||
"link": "https://www.clien.net/service/board/cm_nas",
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
"name": "mk802카페",
|
||||
"link": "https://cafe.naver.com/mk802",
|
||||
},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
{
|
||||
"category": "시스템",
|
||||
"name": "system",
|
||||
"type": "system",
|
||||
"position": "right",
|
||||
"list": [
|
||||
{"type": "plugin", "plugin": "system", "name": "설정"},
|
||||
# {'type':'direct', 'name' : u'설정', 'link':'/system/setting'},
|
||||
# {'type':'direct', 'name' : u'플러그인', 'link':'/system/plugin'},
|
||||
# {'type':'direct', 'name' : u'정보', 'link':'/system/information'},
|
||||
{"type": "divider"},
|
||||
{"type": "plugin", "plugin": "mod", "name": "모듈"},
|
||||
{"type": "plugin", "plugin": "command", "name": "Command"},
|
||||
{"type": "divider"},
|
||||
{"type": "link", "name": "Terminal", "link": "/terminal"},
|
||||
{"type": "direct", "name": "파일 매니저", "link": "/flaskfilemanager"},
|
||||
{"type": "direct", "name": "편집기", "link": "/flaskcode"},
|
||||
{"type": "divider"},
|
||||
# {'type':'link', 'name' : u'FileManager', 'link':'/iframe/file_manager'},
|
||||
# {'type':'system_value', 'name' : u'FileBrowser.xyz', 'link':'url_filebrowser'},
|
||||
# {'type':'system_value', 'name' : u'Celery Monitoring', 'link':'url_celery_monitoring'},
|
||||
# {'type':'divider'},
|
||||
# {'type':'link', 'name':u'위키', 'link':'https://sjva.me/wiki/public/start'},
|
||||
# {'type':'divider'},
|
||||
{"type": "direct", "name": "로그아웃", "link": "/logout"},
|
||||
{"type": "direct", "name": "재시작(업데이트)", "link": "/system/restart"},
|
||||
{
|
||||
"type": "direct",
|
||||
"name": "종료",
|
||||
"link": "javascript:shutdown_confirm();",
|
||||
},
|
||||
],
|
||||
"count": 0,
|
||||
},
|
||||
]
|
||||
|
||||
DEFINE_MENU_MAP = copy.deepcopy(MENU_MAP)
|
||||
|
||||
|
||||
def init_menu(plugin_menus):
|
||||
global MENU_MAP
|
||||
from framework import logger
|
||||
|
||||
for plugin_menu in plugin_menus:
|
||||
find = False
|
||||
for category in MENU_MAP:
|
||||
for category_child in category["list"]:
|
||||
if category_child["type"] != "plugin":
|
||||
continue
|
||||
if category_child["plugin"] == plugin_menu["main"][0]:
|
||||
find = True
|
||||
category_child["name"] = plugin_menu["main"][1]
|
||||
category_child["sub"] = plugin_menu["sub"]
|
||||
category_child["sub2"] = (
|
||||
plugin_menu["sub2"] if "sub2" in plugin_menu else None
|
||||
)
|
||||
category_child["exist"] = True
|
||||
category["count"] += 1
|
||||
break
|
||||
if find:
|
||||
break
|
||||
else:
|
||||
if (
|
||||
"category" in plugin_menu
|
||||
and plugin_menu["category"] == category["name"]
|
||||
):
|
||||
cc = {}
|
||||
cc["type"] = "plugin"
|
||||
cc["plugin"] = plugin_menu["main"][0]
|
||||
cc["name"] = plugin_menu["main"][1]
|
||||
cc["sub"] = plugin_menu["sub"]
|
||||
cc["sub2"] = (
|
||||
plugin_menu["sub2"] if "sub2" in plugin_menu else None
|
||||
)
|
||||
cc["exist"] = True
|
||||
category["count"] += 1
|
||||
category["list"].append(cc)
|
||||
find = True
|
||||
|
||||
if find:
|
||||
continue
|
||||
else:
|
||||
# 카테고리를 발견하지 못하였다면..
|
||||
c = MENU_MAP[9]
|
||||
cc = {}
|
||||
cc["type"] = "plugin"
|
||||
cc["plugin"] = plugin_menu["main"][0]
|
||||
cc["name"] = plugin_menu["main"][1]
|
||||
cc["sub"] = plugin_menu["sub"]
|
||||
cc["sub2"] = plugin_menu["sub2"] if "sub2" in plugin_menu else None
|
||||
cc["exist"] = True
|
||||
c["count"] += 1
|
||||
c["list"].append(cc)
|
||||
|
||||
tmp = copy.deepcopy(MENU_MAP)
|
||||
MENU_MAP = []
|
||||
for category in tmp:
|
||||
if category["type"] in ["link"]:
|
||||
MENU_MAP.append(category)
|
||||
elif category["type"] in ["system"]:
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
for t in category["list"]:
|
||||
if t["type"] == "system_value":
|
||||
t["type"] = "link"
|
||||
t["link"] = SystemModelSetting.get(t["link"])
|
||||
MENU_MAP.append(category)
|
||||
else:
|
||||
if category["count"] > 0:
|
||||
MENU_MAP.append(category)
|
||||
|
||||
for category in MENU_MAP:
|
||||
if category["category"] in ["system", "link", "custom"]:
|
||||
continue
|
||||
flag_custom = False
|
||||
total_plugin_count = 0
|
||||
exist_plugin_count = 0
|
||||
for category_child in category["list"]:
|
||||
total_plugin_count += 1
|
||||
if category_child["type"] == "plugin":
|
||||
if (
|
||||
"exist" not in category_child
|
||||
or category_child["exist"] == False
|
||||
):
|
||||
flag_custom = True
|
||||
else:
|
||||
exist_plugin_count += 1
|
||||
if exist_plugin_count == 0:
|
||||
# 올수없다
|
||||
|
||||
continue
|
||||
|
||||
if flag_custom:
|
||||
tmp = copy.deepcopy(category["list"])
|
||||
category["list"] = []
|
||||
for category_child in tmp:
|
||||
if category_child["type"] != "plugin":
|
||||
category["list"].append(category_child)
|
||||
if (
|
||||
"exist" in category_child
|
||||
and category_child["exist"] == True
|
||||
):
|
||||
category["list"].append(category_child)
|
||||
|
||||
try:
|
||||
import flaskfilemanager
|
||||
except Exception:
|
||||
# del MENU_MAP[-1]['list'][2]
|
||||
try:
|
||||
index = -1
|
||||
for idx, item in enumerate(MENU_MAP[-1]["list"]):
|
||||
if "link" in item and item["link"] == "/flaskfilemanager" != -1:
|
||||
item["link"] = "/system/plugin?install=flaskfilemanager"
|
||||
break
|
||||
if index != -1:
|
||||
del MENU_MAP[-1]["list"][index]
|
||||
except Exception as exception:
|
||||
import traceback
|
||||
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
try:
|
||||
import flaskcode
|
||||
except Exception:
|
||||
# del MENU_MAP[-1]['list'][2]
|
||||
try:
|
||||
index = -1
|
||||
for idx, item in enumerate(MENU_MAP[-1]["list"]):
|
||||
if "link" in item and item["link"] == "/flaskcode" != -1:
|
||||
item["link"] = "/system/plugin?install=flaskcode"
|
||||
break
|
||||
if index != -1:
|
||||
del MENU_MAP[-1]["list"][index]
|
||||
except Exception as exception:
|
||||
import traceback
|
||||
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
import platform
|
||||
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
index = -1
|
||||
for idx, item in enumerate(MENU_MAP[-1]["list"]):
|
||||
if "link" in item and item["link"] == "/terminal":
|
||||
index = idx
|
||||
break
|
||||
if index != -1:
|
||||
del MENU_MAP[-1]["list"][index]
|
||||
except Exception as exception:
|
||||
import traceback
|
||||
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
try:
|
||||
## 선 제거
|
||||
for category in MENU_MAP:
|
||||
new_category = []
|
||||
flag = -1
|
||||
first = False
|
||||
for idx, item in enumerate(category["list"]):
|
||||
if (idx == 0 or idx == len(category["list"]) - 1) and item[
|
||||
"type"
|
||||
] == "divider":
|
||||
continue
|
||||
if first == False and item["type"] == "divider":
|
||||
continue
|
||||
if item["type"] == "divider":
|
||||
if flag == 1:
|
||||
continue
|
||||
else:
|
||||
flag = 1
|
||||
else:
|
||||
first = True
|
||||
flag = 0
|
||||
new_category.append(item)
|
||||
if new_category[-1]["type"] == "divider":
|
||||
new_category = new_category[:-1]
|
||||
category["list"] = new_category
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_menu_map():
|
||||
global MENU_MAP
|
||||
return MENU_MAP
|
||||
|
||||
|
||||
def get_plugin_menu(plugin_name):
|
||||
global MENU_MAP
|
||||
for category in MENU_MAP:
|
||||
for category_child in category["list"]:
|
||||
if category_child["type"] != "plugin":
|
||||
continue
|
||||
if category_child["plugin"] == plugin_name:
|
||||
return category_child
|
||||
21
lib/framework/py_version_func.py
Normal file
21
lib/framework/py_version_func.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
import Queue as py_queue
|
||||
import urllib2 as py_urllib2 # Request, urlopen
|
||||
import urllib as py_urllib # quote, unuote, urlencode
|
||||
|
||||
py_reload = reload
|
||||
|
||||
def py_unicode(v):
|
||||
return unicode(v)
|
||||
|
||||
else:
|
||||
import queue as py_queue
|
||||
import urllib.request as py_urllib2
|
||||
import urllib.parse as py_urllib # urlencode
|
||||
from importlib import reload as py_reload
|
||||
|
||||
def py_unicode(v):
|
||||
return str(v)
|
||||
35
lib/framework/user.py
Normal file
35
lib/framework/user.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(
|
||||
self, user_id, email=None, passwd_hash=None, authenticated=True
|
||||
):
|
||||
self.user_id = user_id
|
||||
self.email = email
|
||||
self.passwd_hash = passwd_hash
|
||||
self.authenticated = authenticated
|
||||
|
||||
def __repr__(self):
|
||||
r = {
|
||||
"user_id": self.user_id,
|
||||
"email": self.email,
|
||||
"passwd_hash": self.passwd_hash,
|
||||
"authenticated": self.authenticated,
|
||||
}
|
||||
return str(r)
|
||||
|
||||
def can_login(self, passwd_hash):
|
||||
return self.passwd_hash == passwd_hash
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
def get_id(self):
|
||||
return self.user_id
|
||||
|
||||
def is_authenticated(self):
|
||||
return self.authenticated
|
||||
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
217
lib/framework/util.py
Normal file
217
lib/framework/util.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import json
|
||||
import traceback
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
# third-party
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
|
||||
# sjva 공용
|
||||
from framework import app, logger
|
||||
|
||||
#########################################################
|
||||
|
||||
|
||||
class Util(object):
|
||||
@staticmethod
|
||||
def sizeof_fmt(num, suffix="Bytes"):
|
||||
"""
|
||||
파일크기, 다운로드 속도 표시시 사용
|
||||
"""
|
||||
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
|
||||
if abs(num) < 1024.0:
|
||||
return "%3.1f%s%s" % (num, unit, suffix)
|
||||
num /= 1024.0
|
||||
return "%.1f%s%s" % (num, "Y", suffix)
|
||||
|
||||
@staticmethod
|
||||
def db_list_to_dict(db_list):
|
||||
"""
|
||||
세팅DB에서 사용, (key, value) dict로 변환
|
||||
"""
|
||||
ret = {}
|
||||
for item in db_list:
|
||||
ret[item.key] = item.value
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def db_to_dict(db_list):
|
||||
ret = []
|
||||
for item in db_list:
|
||||
ret.append(item.as_dict())
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def get_paging_info(count, current_page, page_size):
|
||||
try:
|
||||
paging = {}
|
||||
paging["prev_page"] = True
|
||||
paging["next_page"] = True
|
||||
if current_page <= 10:
|
||||
paging["prev_page"] = False
|
||||
|
||||
paging["total_page"] = int(count / page_size) + 1
|
||||
if count % page_size == 0:
|
||||
paging["total_page"] -= 1
|
||||
paging["start_page"] = int((current_page - 1) / 10) * 10 + 1
|
||||
paging["last_page"] = (
|
||||
paging["total_page"]
|
||||
if paging["start_page"] + 9 > paging["total_page"]
|
||||
else paging["start_page"] + 9
|
||||
)
|
||||
if paging["last_page"] == paging["total_page"]:
|
||||
paging["next_page"] = False
|
||||
paging["current_page"] = current_page
|
||||
paging["count"] = count
|
||||
logger.debug(
|
||||
"paging : c:%s %s %s %s %s %s",
|
||||
count,
|
||||
paging["total_page"],
|
||||
paging["prev_page"],
|
||||
paging["next_page"],
|
||||
paging["start_page"],
|
||||
paging["last_page"],
|
||||
)
|
||||
return paging
|
||||
except Exception as exception:
|
||||
logger.debug("Exception:%s", exception)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_list_except_empty(source):
|
||||
tmp = []
|
||||
for _ in source:
|
||||
if _.strip().startswith("#"):
|
||||
continue
|
||||
if _.strip() != "":
|
||||
tmp.append(_.strip())
|
||||
return tmp
|
||||
|
||||
@staticmethod
|
||||
def save_from_dict_to_json(d, filename):
|
||||
from tool_base import ToolUtil
|
||||
|
||||
ToolUtil.save_dict(d, filename)
|
||||
|
||||
# list형태
|
||||
@staticmethod
|
||||
def execute_command(command):
|
||||
from tool_base import ToolSubprocess
|
||||
|
||||
return ToolSubprocess.execute_command_return(command)
|
||||
|
||||
@staticmethod
|
||||
def change_text_for_use_filename(text):
|
||||
from tool_base import ToolBaseFile
|
||||
|
||||
return ToolBaseFile.text_for_filename(text)
|
||||
|
||||
# 토렌트 인포에서 최대 크기 파일과 폴더명을 리턴한다
|
||||
@staticmethod
|
||||
def get_max_size_fileinfo(torrent_info):
|
||||
try:
|
||||
ret = {}
|
||||
max_size = -1
|
||||
max_filename = None
|
||||
for t in torrent_info["files"]:
|
||||
if t["size"] > max_size:
|
||||
max_size = t["size"]
|
||||
max_filename = str(t["path"])
|
||||
t = max_filename.split("/")
|
||||
ret["filename"] = t[-1]
|
||||
if len(t) == 1:
|
||||
ret["dirname"] = ""
|
||||
elif len(t) == 2:
|
||||
ret["dirname"] = t[0]
|
||||
else:
|
||||
ret["dirname"] = max_filename.replace(
|
||||
"/%s" % ret["filename"], ""
|
||||
)
|
||||
ret["max_size"] = max_size
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# 압축할 폴더 경로를 인자로 받음. 폴더명.zip 생성
|
||||
@staticmethod
|
||||
def makezip(zip_path, zip_extension="zip"):
|
||||
import zipfile
|
||||
|
||||
try:
|
||||
if os.path.isdir(zip_path):
|
||||
zipfilename = os.path.join(
|
||||
os.path.dirname(zip_path),
|
||||
"%s.%s" % (os.path.basename(zip_path), zip_extension),
|
||||
)
|
||||
fantasy_zip = zipfile.ZipFile(zipfilename, "w")
|
||||
for f in os.listdir(zip_path):
|
||||
# if f.endswith('.jpg') or f.endswith('.png'):
|
||||
src = os.path.join(zip_path, f)
|
||||
fantasy_zip.write(
|
||||
src,
|
||||
os.path.basename(src),
|
||||
compress_type=zipfile.ZIP_DEFLATED,
|
||||
)
|
||||
fantasy_zip.close()
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(zip_path)
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def make_apikey(url):
|
||||
from framework import SystemModelSetting
|
||||
|
||||
url = url.format(ddns=SystemModelSetting.get("ddns"))
|
||||
if SystemModelSetting.get_bool("auth_use_apikey"):
|
||||
if url.find("?") == -1:
|
||||
url += "?"
|
||||
else:
|
||||
url += "&"
|
||||
url += "apikey=%s" % SystemModelSetting.get("auth_apikey")
|
||||
return url
|
||||
|
||||
|
||||
class SingletonClass(object):
|
||||
__instance = None
|
||||
|
||||
@classmethod
|
||||
def __getInstance(cls):
|
||||
return cls.__instance
|
||||
|
||||
@classmethod
|
||||
def instance(cls, *args, **kargs):
|
||||
cls.__instance = cls(*args, **kargs)
|
||||
cls.instance = cls.__getInstance
|
||||
return cls.__instance
|
||||
|
||||
|
||||
class AlchemyEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj.__class__, DeclarativeMeta):
|
||||
# an SQLAlchemy class
|
||||
fields = {}
|
||||
for field in [
|
||||
x for x in dir(obj) if not x.startswith("_") and x != "metadata"
|
||||
]:
|
||||
data = obj.__getattribute__(field)
|
||||
try:
|
||||
json.dumps(
|
||||
data
|
||||
) # this will fail on non-encodable values, like other classes
|
||||
fields[field] = data
|
||||
except TypeError:
|
||||
fields[field] = None
|
||||
# a json-encodable dict
|
||||
return fields
|
||||
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
8
lib/plugin/__init__.py
Normal file
8
lib/plugin/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from framework import logger
|
||||
from .route import (
|
||||
default_route,
|
||||
default_route_socketio,
|
||||
default_route_socketio_sub,
|
||||
default_route_single_module,
|
||||
)
|
||||
from .ffmpeg_queue import FfmpegQueueEntity, FfmpegQueue
|
||||
293
lib/plugin/ffmpeg_queue.py
Normal file
293
lib/plugin/ffmpeg_queue.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os, sys, traceback
|
||||
import threading, time
|
||||
from datetime import datetime
|
||||
import abc
|
||||
from framework import py_queue
|
||||
|
||||
# third-party
|
||||
# sjva 공용
|
||||
#########################################################
|
||||
|
||||
|
||||
class FfmpegQueueEntity(abc.ABCMeta("ABC", (object,), {"__slots__": ()})):
|
||||
def __init__(self, P, module_logic, info):
|
||||
self.P = P
|
||||
self.module_logic = module_logic
|
||||
self.entity_id = -1 # FfmpegQueueEntity.static_index
|
||||
self.info = info
|
||||
self.url = None
|
||||
self.ffmpeg_status = -1
|
||||
self.ffmpeg_status_kor = "대기중"
|
||||
self.ffmpeg_percent = 0
|
||||
self.ffmpeg_arg = None
|
||||
self.cancel = False
|
||||
self.created_time = datetime.now().strftime("%m-%d %H:%M:%S")
|
||||
self.savepath = None
|
||||
self.filename = None
|
||||
self.filepath = None
|
||||
self.quality = None
|
||||
self.headers = None
|
||||
# FfmpegQueueEntity.static_index += 1
|
||||
# FfmpegQueueEntity.entity_list.append(self)
|
||||
|
||||
def get_video_url(self):
|
||||
return self.url
|
||||
|
||||
def get_video_filepath(self):
|
||||
return self.filepath
|
||||
|
||||
@abc.abstractmethod
|
||||
def refresh_status(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def info_dict(self, tmp):
|
||||
pass
|
||||
|
||||
def donwload_completed(self):
|
||||
pass
|
||||
|
||||
def as_dict(self):
|
||||
tmp = {}
|
||||
tmp["entity_id"] = self.entity_id
|
||||
tmp["url"] = self.url
|
||||
tmp["ffmpeg_status"] = self.ffmpeg_status
|
||||
tmp["ffmpeg_status_kor"] = self.ffmpeg_status_kor
|
||||
tmp["ffmpeg_percent"] = self.ffmpeg_percent
|
||||
tmp["ffmpeg_arg"] = self.ffmpeg_arg
|
||||
tmp["cancel"] = self.cancel
|
||||
tmp["created_time"] = self.created_time # .strftime('%m-%d %H:%M:%S')
|
||||
tmp["savepath"] = self.savepath
|
||||
tmp["filename"] = self.filename
|
||||
tmp["filepath"] = self.filepath
|
||||
tmp["quality"] = self.quality
|
||||
# tmp['current_speed'] = self.ffmpeg_arg['current_speed'] if self.ffmpeg_arg is not None else ''
|
||||
tmp = self.info_dict(tmp)
|
||||
return tmp
|
||||
|
||||
|
||||
class FfmpegQueue(object):
|
||||
def __init__(self, P, max_ffmpeg_count):
|
||||
self.P = P
|
||||
self.static_index = 1
|
||||
self.entity_list = []
|
||||
self.current_ffmpeg_count = 0
|
||||
self.download_queue = None
|
||||
self.download_thread = None
|
||||
self.max_ffmpeg_count = max_ffmpeg_count
|
||||
if self.max_ffmpeg_count is None or self.max_ffmpeg_count == "":
|
||||
self.max_ffmpeg_count = 1
|
||||
|
||||
def queue_start(self):
|
||||
try:
|
||||
if self.download_queue is None:
|
||||
self.download_queue = py_queue.Queue()
|
||||
if self.download_thread is None:
|
||||
self.download_thread = threading.Thread(
|
||||
target=self.download_thread_function, args=()
|
||||
)
|
||||
self.download_thread.daemon = True
|
||||
self.download_thread.start()
|
||||
except Exception as exception:
|
||||
self.P.logger.error("Exception:%s", exception)
|
||||
self.P.logger.error(traceback.format_exc())
|
||||
|
||||
def download_thread_function(self):
|
||||
while True:
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
if self.current_ffmpeg_count < self.max_ffmpeg_count:
|
||||
break
|
||||
time.sleep(5)
|
||||
except Exception as exception:
|
||||
self.P.logger.error("Exception:%s", exception)
|
||||
self.P.logger.error(traceback.format_exc())
|
||||
self.P.logger.error(
|
||||
"current_ffmpeg_count : %s",
|
||||
self.current_ffmpeg_count,
|
||||
)
|
||||
self.P.logger.error(
|
||||
"max_ffmpeg_count : %s", self.max_ffmpeg_count
|
||||
)
|
||||
break
|
||||
entity = self.download_queue.get()
|
||||
if entity.cancel:
|
||||
continue
|
||||
|
||||
# from .logic_ani24 import LogicAni24
|
||||
# entity.url = LogicAni24.get_video_url(entity.info['code'])
|
||||
video_url = entity.get_video_url()
|
||||
if video_url is None:
|
||||
entity.ffmpeg_status_kor = "URL실패"
|
||||
entity.refresh_status()
|
||||
# plugin.socketio_list_refresh()
|
||||
continue
|
||||
|
||||
import ffmpeg
|
||||
|
||||
# max_pf_count = 0
|
||||
# save_path = ModelSetting.get('download_path')
|
||||
# if ModelSetting.get('auto_make_folder') == 'True':
|
||||
# program_path = os.path.join(save_path, entity.info['filename'].split('.')[0])
|
||||
# save_path = program_path
|
||||
# try:
|
||||
# if not os.path.exists(save_path):
|
||||
# os.makedirs(save_path)
|
||||
# except:
|
||||
# logger.debug('program path make fail!!')
|
||||
# 파일 존재여부 체크
|
||||
filepath = entity.get_video_filepath()
|
||||
if os.path.exists(filepath):
|
||||
entity.ffmpeg_status_kor = "파일 있음"
|
||||
entity.ffmpeg_percent = 100
|
||||
entity.refresh_status()
|
||||
# plugin.socketio_list_refresh()
|
||||
continue
|
||||
dirname = os.path.dirname(filepath)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
f = ffmpeg.Ffmpeg(
|
||||
video_url,
|
||||
os.path.basename(filepath),
|
||||
plugin_id=entity.entity_id,
|
||||
listener=self.ffmpeg_listener,
|
||||
call_plugin=self.P.package_name,
|
||||
save_path=dirname,
|
||||
headers=entity.headers,
|
||||
)
|
||||
f.start()
|
||||
self.current_ffmpeg_count += 1
|
||||
self.download_queue.task_done()
|
||||
except Exception as exception:
|
||||
self.P.logger.error("Exception:%s", exception)
|
||||
self.P.logger.error(traceback.format_exc())
|
||||
|
||||
def ffmpeg_listener(self, **arg):
|
||||
import ffmpeg
|
||||
|
||||
entity = self.get_entity_by_entity_id(arg["plugin_id"])
|
||||
if entity is None:
|
||||
return
|
||||
if arg["type"] == "status_change":
|
||||
if arg["status"] == ffmpeg.Status.DOWNLOADING:
|
||||
pass
|
||||
elif arg["status"] == ffmpeg.Status.COMPLETED:
|
||||
entity.donwload_completed()
|
||||
elif arg["status"] == ffmpeg.Status.READY:
|
||||
pass
|
||||
elif arg["type"] == "last":
|
||||
self.current_ffmpeg_count += -1
|
||||
elif arg["type"] == "log":
|
||||
pass
|
||||
elif arg["type"] == "normal":
|
||||
pass
|
||||
|
||||
entity.ffmpeg_arg = arg
|
||||
entity.ffmpeg_status = int(arg["status"])
|
||||
entity.ffmpeg_status_kor = str(arg["status"])
|
||||
entity.ffmpeg_percent = arg["data"]["percent"]
|
||||
entity.ffmpeg_arg["status"] = str(arg["status"])
|
||||
# self.P.logger.debug(arg)
|
||||
# import plugin
|
||||
# arg['status'] = str(arg['status'])
|
||||
# plugin.socketio_callback('status', arg)
|
||||
entity.refresh_status()
|
||||
|
||||
# FfmpegQueueEntity.static_index += 1
|
||||
# FfmpegQueueEntity.entity_list.append(self)
|
||||
|
||||
def add_queue(self, entity):
|
||||
try:
|
||||
# entity = QueueEntity.create(info)
|
||||
# if entity is not None:
|
||||
# LogicQueue.download_queue.put(entity)
|
||||
# return True
|
||||
entity.entity_id = self.static_index
|
||||
self.static_index += 1
|
||||
self.entity_list.append(entity)
|
||||
self.download_queue.put(entity)
|
||||
return True
|
||||
except Exception as exception:
|
||||
self.P.logger.error("Exception:%s", exception)
|
||||
self.P.logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def set_max_ffmpeg_count(self, max_ffmpeg_count):
|
||||
self.max_ffmpeg_count = max_ffmpeg_count
|
||||
|
||||
def get_max_ffmpeg_count(self):
|
||||
return self.max_ffmpeg_count
|
||||
|
||||
def command(self, cmd, entity_id):
|
||||
self.P.logger.debug("command :%s %s", cmd, entity_id)
|
||||
ret = {}
|
||||
try:
|
||||
if cmd == "cancel":
|
||||
self.P.logger.debug("command :%s %s", cmd, entity_id)
|
||||
entity = self.get_entity_by_entity_id(entity_id)
|
||||
if entity is not None:
|
||||
if entity.ffmpeg_status == -1:
|
||||
entity.cancel = True
|
||||
entity.ffmpeg_status_kor = "취소"
|
||||
# entity.refresh_status()
|
||||
ret["ret"] = "refresh"
|
||||
elif entity.ffmpeg_status != 5:
|
||||
ret["ret"] = "notify"
|
||||
ret["log"] = "다운로드중 상태가 아닙니다."
|
||||
else:
|
||||
idx = entity.ffmpeg_arg["data"]["idx"]
|
||||
import ffmpeg
|
||||
|
||||
ffmpeg.Ffmpeg.stop_by_idx(idx)
|
||||
entity.refresh_status()
|
||||
ret["ret"] = "refresh"
|
||||
elif cmd == "reset":
|
||||
if self.download_queue is not None:
|
||||
with self.download_queue.mutex:
|
||||
self.download_queue.queue.clear()
|
||||
for _ in self.entity_list:
|
||||
if _.ffmpeg_status == 5:
|
||||
import ffmpeg
|
||||
|
||||
idx = _.ffmpeg_arg["data"]["idx"]
|
||||
ffmpeg.Ffmpeg.stop_by_idx(idx)
|
||||
self.entity_list = []
|
||||
ret["ret"] = "refresh"
|
||||
elif cmd == "delete_completed":
|
||||
new_list = []
|
||||
for _ in self.entity_list:
|
||||
if _.ffmpeg_status_kor in ["파일 있음", "취소", "사용자중지"]:
|
||||
continue
|
||||
if _.ffmpeg_status != 7:
|
||||
new_list.append(_)
|
||||
self.entity_list = new_list
|
||||
ret["ret"] = "refresh"
|
||||
elif cmd == "remove":
|
||||
new_list = []
|
||||
for _ in self.entity_list:
|
||||
if _.entity_id == entity_id:
|
||||
continue
|
||||
new_list.append(_)
|
||||
self.entity_list = new_list
|
||||
ret["ret"] = "refresh"
|
||||
return ret
|
||||
except Exception as exception:
|
||||
self.P.logger.error("Exception:%s", exception)
|
||||
self.P.logger.error(traceback.format_exc())
|
||||
|
||||
def get_entity_by_entity_id(self, entity_id):
|
||||
for _ in self.entity_list:
|
||||
if _.entity_id == entity_id:
|
||||
return _
|
||||
return None
|
||||
|
||||
def get_entity_list(self):
|
||||
ret = []
|
||||
for x in self.entity_list:
|
||||
tmp = x.as_dict()
|
||||
ret.append(tmp)
|
||||
return ret
|
||||
138
lib/plugin/logic_module_base.py
Normal file
138
lib/plugin/logic_module_base.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
|
||||
class LogicModuleBase(object):
|
||||
db_default = None
|
||||
|
||||
def __init__(self, P, first_menu, scheduler_desc=None):
|
||||
self.P = P
|
||||
self.scheduler_desc = scheduler_desc
|
||||
self.first_menu = first_menu
|
||||
self.name = None
|
||||
self.socketio_list = None
|
||||
self.sub_list = None
|
||||
|
||||
def process_menu(self, sub):
|
||||
pass
|
||||
|
||||
def process_ajax(self, sub, req):
|
||||
pass
|
||||
|
||||
def process_api(self, sub, req):
|
||||
pass
|
||||
|
||||
def process_normal(self, sub, req):
|
||||
pass
|
||||
|
||||
def scheduler_function(self):
|
||||
pass
|
||||
|
||||
def reset_db(self):
|
||||
pass
|
||||
|
||||
def plugin_load(self):
|
||||
pass
|
||||
|
||||
def plugin_unload(self):
|
||||
pass
|
||||
|
||||
def setting_save_after(self):
|
||||
pass
|
||||
|
||||
def process_telegram_data(self, data, target=None):
|
||||
pass
|
||||
|
||||
def migration(self):
|
||||
pass
|
||||
|
||||
#################################################################
|
||||
def get_scheduler_desc(self):
|
||||
return self.scheduler_desc
|
||||
|
||||
def get_scheduler_interval(self):
|
||||
if (
|
||||
self.P is not None
|
||||
and self.P.ModelSetting is not None
|
||||
and self.name is not None
|
||||
):
|
||||
return self.P.ModelSetting.get(
|
||||
"{module_name}_interval".format(module_name=self.name)
|
||||
)
|
||||
|
||||
def get_first_menu(self):
|
||||
return self.first_menu
|
||||
|
||||
def get_scheduler_name(self):
|
||||
return "%s_%s" % (self.P.package_name, self.name)
|
||||
|
||||
def dump(self, data):
|
||||
if type(data) in [type({}), type([])]:
|
||||
import json
|
||||
|
||||
return "\n" + json.dumps(data, indent=4, ensure_ascii=False)
|
||||
else:
|
||||
return str(data)
|
||||
|
||||
|
||||
class LogicSubModuleBase(object):
|
||||
db_default = None
|
||||
|
||||
def __init__(self, P, parent, name, scheduler_desc=None):
|
||||
self.P = P
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
self.scheduler_desc = scheduler_desc
|
||||
self.socketio_list = None
|
||||
|
||||
def process_ajax(self, sub, req):
|
||||
pass
|
||||
|
||||
def scheduler_function(self):
|
||||
pass
|
||||
|
||||
def plugin_load(self):
|
||||
pass
|
||||
|
||||
def plugin_unload(self):
|
||||
pass
|
||||
|
||||
def get_scheduler_desc(self):
|
||||
return self.scheduler_desc
|
||||
|
||||
def get_scheduler_interval(self):
|
||||
if (
|
||||
self.P is not None
|
||||
and self.P.ModelSetting is not None
|
||||
and self.parent.name is not None
|
||||
and self.name is not None
|
||||
):
|
||||
return self.P.ModelSetting.get(
|
||||
f"{self.parent.name}_{self.name}_interval"
|
||||
)
|
||||
|
||||
def get_scheduler_name(self):
|
||||
return f"{self.P.package_name}_{self.parent.name}_{self.name}"
|
||||
|
||||
def process_api(self, sub, req):
|
||||
pass
|
||||
|
||||
def process_normal(self, sub, req):
|
||||
pass
|
||||
|
||||
def reset_db(self):
|
||||
pass
|
||||
|
||||
def setting_save_after(self):
|
||||
pass
|
||||
|
||||
def process_telegram_data(self, data, target=None):
|
||||
pass
|
||||
|
||||
def migration(self):
|
||||
pass
|
||||
|
||||
#################################################################
|
||||
|
||||
def process_menu(self, sub):
|
||||
pass
|
||||
0
lib/plugin/route.py
Normal file
0
lib/plugin/route.py
Normal file
17
lib/system/__init__.py
Normal file
17
lib/system/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from .plugin import (
|
||||
blueprint,
|
||||
menu,
|
||||
plugin_load,
|
||||
plugin_unload,
|
||||
restart,
|
||||
shutdown,
|
||||
)
|
||||
from .logic import SystemLogic
|
||||
from .model import ModelSetting
|
||||
|
||||
|
||||
from .logic_plugin import LogicPlugin
|
||||
from .logic_selenium import SystemLogicSelenium
|
||||
from .logic_command import SystemLogicCommand
|
||||
|
||||
from .logic_site import SystemLogicSite
|
||||
BIN
lib/system/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic_auth.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic_auth.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic_command.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic_command.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic_command2.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic_command2.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic_plugin.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic_plugin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic_selenium.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic_selenium.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/logic_site.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/logic_site.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/model.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/model.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/system/__pycache__/plugin.cpython-310.pyc
Normal file
BIN
lib/system/__pycache__/plugin.cpython-310.pyc
Normal file
Binary file not shown.
586
lib/system/logic.py
Normal file
586
lib/system/logic.py
Normal file
@@ -0,0 +1,586 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import string
|
||||
import random
|
||||
import json
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
)
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
|
||||
# gommi 공용
|
||||
from framework.logger import get_logger, set_level
|
||||
from framework import (
|
||||
app,
|
||||
db,
|
||||
scheduler,
|
||||
version,
|
||||
path_app_root,
|
||||
path_data,
|
||||
USERS,
|
||||
)
|
||||
from framework.util import Util
|
||||
|
||||
from framework import USERS
|
||||
from framework.user import User
|
||||
from framework import db, scheduler
|
||||
from framework.job import Job
|
||||
|
||||
# 패키지
|
||||
from .model import ModelSetting
|
||||
import system
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split(".")[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
|
||||
class SystemLogic(object):
|
||||
point = 0
|
||||
db_default = {
|
||||
"db_version": "1",
|
||||
"port": "7771",
|
||||
"ddns": "http://localhost:7771",
|
||||
#'url_filebrowser' : 'http://localhost:9998',
|
||||
#'url_celery_monitoring' : 'http://localhost:9997',
|
||||
"id": "sjva",
|
||||
"pw": "sjva",
|
||||
"system_start_time": "",
|
||||
"repeat": "",
|
||||
"auto_restart_hour": "12",
|
||||
#'unique' : '',
|
||||
"theme": "Default",
|
||||
"log_level": "10",
|
||||
"use_login": "False",
|
||||
"link_json": '[{"type":"link","title":"위키","url":"https://sjva.me/wiki/public/start"}]',
|
||||
"plugin_dev_path": "",
|
||||
"plugin_tving_level2": "False",
|
||||
"web_title": "GOMMI Agent",
|
||||
"my_ip": "",
|
||||
"wavve_guid": "",
|
||||
# 번역
|
||||
"trans_type": "0",
|
||||
"trans_google_api_key": "",
|
||||
"trans_papago_key": "",
|
||||
# 인증
|
||||
"auth_use_apikey": "False",
|
||||
"auth_apikey": "",
|
||||
"hide_menu": "True",
|
||||
# Selenium
|
||||
"selenium_remote_url": "",
|
||||
"selenium_remote_default_option": "--no-sandbox\n--disable-gpu",
|
||||
"selenium_binary_default_option": "",
|
||||
# notify
|
||||
"notify_telegram_use": "False",
|
||||
"notify_telegram_token": "",
|
||||
"notify_telegram_chat_id": "",
|
||||
"notify_telegram_disable_notification": "False",
|
||||
"notify_discord_use": "False",
|
||||
"notify_discord_webhook": "",
|
||||
"notify_advaned_use": "False",
|
||||
"notify_advaned_policy": "# 각 플러그인 설정 설명에 명시되어 있는 ID = 형식\n# DEFAULT 부터 주석(#) 제거 후 작성\n\n# DEFAULT = ",
|
||||
# telegram
|
||||
"telegram_bot_token": "",
|
||||
"telegram_bot_auto_start": "False",
|
||||
"telegram_resend": "False",
|
||||
"telegram_resend_chat_id": "",
|
||||
# 홈페이지 연동 2020-06-07
|
||||
"sjva_me_user_id": "",
|
||||
"auth_status": "",
|
||||
"sjva_id": "",
|
||||
# site
|
||||
"site_daum_interval": "0 4 */3 * *",
|
||||
"site_daum_auto_start": "False",
|
||||
"site_daum_cookie": "TIARA=gaXEIPluo-wWAFlwZN6l8gN3yzhkoo_piP.Kymhuy.6QBt4Q6.cRtxbKDaWpWajcyteRHzrlTVpJRxLjwLoMvyYLVi_7xJ1L",
|
||||
"site_daum_test": "나쁜 녀석들",
|
||||
"site_daum_proxy": "",
|
||||
"site_wavve_id": "",
|
||||
"site_wavve_pw": "",
|
||||
"site_wavve_credential": "",
|
||||
"site_wavve_use_proxy": "False",
|
||||
"site_wavve_proxy_url": "",
|
||||
"site_tving_id": "",
|
||||
"site_tving_pw": "",
|
||||
"site_tving_login_type": "0",
|
||||
"site_tving_token": "",
|
||||
"site_tving_deviceid": "",
|
||||
"site_tving_use_proxy": "False",
|
||||
"site_tving_proxy_url": "",
|
||||
"site_tving_uuid": "",
|
||||
# memo
|
||||
"memo": "",
|
||||
# tool - decrypt
|
||||
"tool_crypt_use_user_key": "False",
|
||||
"tool_crypt_user_key": "",
|
||||
"tool_crypt_encrypt_word": "",
|
||||
"tool_crypt_decrypt_word": "",
|
||||
"use_beta": "False",
|
||||
}
|
||||
|
||||
db_default2 = {
|
||||
"use_category_vod": "True",
|
||||
"use_category_file_process": "True",
|
||||
"use_category_plex": "True",
|
||||
"use_category_tool": "True",
|
||||
}
|
||||
|
||||
db_default3 = {
|
||||
"use_plugin_ffmpeg": "False",
|
||||
"use_plugin_ktv": "False",
|
||||
"use_plugin_fileprocess_movie": "False",
|
||||
"use_plugin_plex": "False",
|
||||
"use_plugin_gdrive_scan": "False",
|
||||
"use_plugin_rclone": "False",
|
||||
"use_plugin_daum_tv": "False",
|
||||
}
|
||||
|
||||
recent_version = None
|
||||
|
||||
@staticmethod
|
||||
def plugin_load():
|
||||
try:
|
||||
SystemLogic.db_init()
|
||||
SystemLogic.init()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def db_init():
|
||||
try:
|
||||
logger.debug(
|
||||
"setting count : %s",
|
||||
db.session.query(ModelSetting).filter_by().count(),
|
||||
)
|
||||
is_first = False
|
||||
for key, value in SystemLogic.db_default.items():
|
||||
if db.session.query(ModelSetting).filter_by(key=key).count() == 0:
|
||||
if key == "port":
|
||||
is_first = True
|
||||
if key == "sjva_id" or key == "auth_apikey":
|
||||
value = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(10)
|
||||
)
|
||||
db.session.add(ModelSetting(key, value))
|
||||
db.session.commit()
|
||||
# 기존...사람들을 위해 토큰이 있는 사용자면 추가할때 True로 해준다
|
||||
for key, value in SystemLogic.db_default2.items():
|
||||
if db.session.query(ModelSetting).filter_by(key=key).count() == 0:
|
||||
tmp = value
|
||||
if is_first is False:
|
||||
tmp = "True"
|
||||
db.session.add(ModelSetting(key, tmp))
|
||||
db.session.commit()
|
||||
# db.session.commit()
|
||||
|
||||
for key, value in SystemLogic.db_default3.items():
|
||||
if db.session.query(ModelSetting).filter_by(key=key).count() == 0:
|
||||
tmp = value
|
||||
if is_first is False:
|
||||
tmp = "True"
|
||||
db.session.add(ModelSetting(key, tmp))
|
||||
db.session.commit()
|
||||
|
||||
# for key, value in SystemLogic.db_default_etc.items():
|
||||
# if db.session.query(ModelSetting).filter_by(key=key).count() == 0:
|
||||
# db.session.add(ModelSetting(key, value))
|
||||
# db.session.commit()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
try:
|
||||
if (
|
||||
app.config["config"]["repeat"] == 0
|
||||
or SystemLogic.get_setting_value("system_start_time") == ""
|
||||
):
|
||||
item = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key="system_start_time")
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
item.value = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
db.session.commit()
|
||||
|
||||
item = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key="repeat")
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
item.value = str(app.config["config"]["repeat"])
|
||||
db.session.commit()
|
||||
username = db.session.query(ModelSetting).filter_by(key="id").first().value
|
||||
passwd = db.session.query(ModelSetting).filter_by(key="pw").first().value
|
||||
USERS[username] = User(username, passwd_hash=passwd)
|
||||
|
||||
SystemLogic.set_restart_scheduler()
|
||||
# SystemLogic.set_statistics_scheduler()
|
||||
SystemLogic.set_scheduler_check_scheduler()
|
||||
SystemLogic.get_recent_version()
|
||||
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_recent_version():
|
||||
try:
|
||||
import requests
|
||||
|
||||
url = f"{app.config['DEFINE']['MAIN_SERVER_URL']}/version"
|
||||
if ModelSetting.get("ddns") == app.config["DEFINE"]["MAIN_SERVER_URL"]:
|
||||
url = "https://dev.soju6jan.com/version"
|
||||
SystemLogic.recent_version = requests.get(url).text
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def restart():
|
||||
import system
|
||||
|
||||
system.restart()
|
||||
|
||||
@staticmethod
|
||||
def get_info():
|
||||
info = {}
|
||||
import platform
|
||||
|
||||
info["platform"] = platform.platform()
|
||||
info["processor"] = platform.processor()
|
||||
|
||||
import sys
|
||||
|
||||
info["python_version"] = sys.version
|
||||
info["version"] = version
|
||||
info["recent_version"] = SystemLogic.recent_version
|
||||
info["path_app_root"] = path_app_root
|
||||
info["running_type"] = "%s. 비동기 작업 : %s" % (
|
||||
app.config["config"]["running_type"],
|
||||
"사용" if app.config["config"]["use_celery"] else "미사용",
|
||||
)
|
||||
import system
|
||||
|
||||
info["auth"] = app.config["config"]["auth_desc"]
|
||||
info["cpu_percent"] = "not supported"
|
||||
info["memory"] = "not supported"
|
||||
info["disk"] = "not supported"
|
||||
if app.config["config"]["running_type"] != "termux":
|
||||
try:
|
||||
import psutil
|
||||
from framework.util import Util
|
||||
|
||||
print("here")
|
||||
info["cpu_percent"] = "%s %%" % psutil.cpu_percent()
|
||||
tmp = psutil.virtual_memory()
|
||||
# info['memory'] = [Util.sizeof_fmt(tmp[0], suffix='B'), Util.sizeof_fmt(tmp[3]), Util.sizeof_fmt(tmp[1]), tmp[2]]
|
||||
info["memory"] = "전체 : %s 사용량 : %s 남은량 : %s (%s%%)" % (
|
||||
Util.sizeof_fmt(tmp[0], suffix="B"),
|
||||
Util.sizeof_fmt(tmp[3], suffix="B"),
|
||||
Util.sizeof_fmt(tmp[1], suffix="B"),
|
||||
tmp[2],
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import platform
|
||||
|
||||
if platform.system() == "Windows":
|
||||
s = os.path.splitdrive(path_app_root)
|
||||
root = s[0]
|
||||
else:
|
||||
root = "/"
|
||||
tmp = psutil.disk_usage(root)
|
||||
info["disk"] = "전체 : %s 사용량 : %s 남은량 : %s (%s%%) - 드라이브 (%s)" % (
|
||||
Util.sizeof_fmt(tmp[0], suffix="B"),
|
||||
Util.sizeof_fmt(tmp[1], suffix="B"),
|
||||
Util.sizeof_fmt(tmp[2], suffix="B"),
|
||||
tmp[3],
|
||||
root,
|
||||
)
|
||||
except Exception as exception:
|
||||
pass
|
||||
try:
|
||||
tmp = SystemLogic.get_setting_value("system_start_time")
|
||||
# logger.debug('SYSTEM_START_TIME:%s', tmp)
|
||||
tmp_datetime = datetime.strptime(tmp, "%Y-%m-%d %H:%M:%S")
|
||||
timedelta = datetime.now() - tmp_datetime
|
||||
info["time"] = "시작 : %s 경과 : %s 재시작 : %s" % (
|
||||
tmp,
|
||||
str(timedelta).split(".")[0],
|
||||
app.config["config"]["repeat"],
|
||||
)
|
||||
except Exception as exception:
|
||||
info["time"] = str(exception)
|
||||
return info
|
||||
|
||||
@staticmethod
|
||||
def setting_save_system(req):
|
||||
try:
|
||||
for key, value in req.form.items():
|
||||
logger.debug("Key:%s Value:%s", key, value)
|
||||
entity = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key=key)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
entity.value = value
|
||||
# if key == 'theme':
|
||||
# SystemLogic.change_theme(value)
|
||||
db.session.commit()
|
||||
lists = ModelSetting.query.all()
|
||||
SystemLogic.setting_list = Util.db_list_to_dict(lists)
|
||||
USERS[
|
||||
db.session.query(ModelSetting).filter_by(key="id").first().value
|
||||
] = User(
|
||||
db.session.query(ModelSetting).filter_by(key="id").first().value,
|
||||
passwd_hash=db.session.query(ModelSetting)
|
||||
.filter_by(key="pw")
|
||||
.first()
|
||||
.value,
|
||||
)
|
||||
SystemLogic.set_restart_scheduler()
|
||||
set_level(
|
||||
int(
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key="log_level")
|
||||
.first()
|
||||
.value
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def setting_save_after():
|
||||
try:
|
||||
USERS[ModelSetting.get("id")] = User(
|
||||
ModelSetting.get("id"), passwd_hash=ModelSetting.get("pw")
|
||||
)
|
||||
SystemLogic.set_restart_scheduler()
|
||||
set_level(
|
||||
int(
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key="log_level")
|
||||
.first()
|
||||
.value
|
||||
)
|
||||
)
|
||||
from .logic_site import SystemLogicSite
|
||||
|
||||
SystemLogicSite.get_daum_cookies(force=True)
|
||||
SystemLogicSite.create_tving_instance()
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def change_theme(theme):
|
||||
try:
|
||||
source = os.path.join(
|
||||
path_app_root,
|
||||
"static",
|
||||
"css",
|
||||
"theme",
|
||||
"%s_bootstrap.min.css" % theme,
|
||||
)
|
||||
target = os.path.join(path_app_root, "static", "css", "bootstrap.min.css")
|
||||
os.remove(target)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_setting_value(key):
|
||||
try:
|
||||
# logger.debug('get_setting_value:%s', key)
|
||||
entity = db.session.query(ModelSetting).filter_by(key=key).first()
|
||||
if entity is None:
|
||||
return None
|
||||
else:
|
||||
return entity.value
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error("error key : %s", key)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def set_restart_scheduler():
|
||||
name = "%s_restart" % (package_name)
|
||||
if scheduler.is_include(name):
|
||||
scheduler.remove_job(name)
|
||||
interval = ModelSetting.get("auto_restart_hour")
|
||||
if interval != "0":
|
||||
if len(interval.split(" ")) == 1:
|
||||
interval = "%s" % (int(interval) * 60)
|
||||
job_instance = Job(
|
||||
package_name,
|
||||
name,
|
||||
interval,
|
||||
SystemLogic.restart,
|
||||
"자동 재시작",
|
||||
True,
|
||||
)
|
||||
scheduler.add_job_instance(job_instance, run=False)
|
||||
|
||||
"""
|
||||
@staticmethod
|
||||
def set_statistics_scheduler():
|
||||
try:
|
||||
name = '%s_statistics' % (package_name)
|
||||
if scheduler.is_include(name):
|
||||
scheduler.remove_job(name)
|
||||
|
||||
job_instance = Job(package_name, name, 59, SystemLogic.statistics_scheduler_function, u"Update Check", True)
|
||||
scheduler.add_job_instance(job_instance, run=True)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def set_scheduler_check_scheduler():
|
||||
try:
|
||||
name = "scheduler_check"
|
||||
if scheduler.is_include(name):
|
||||
scheduler.remove_job(name)
|
||||
|
||||
job_instance = Job(
|
||||
package_name,
|
||||
name,
|
||||
2,
|
||||
scheduler.first_run_check_thread_function,
|
||||
"Scheduler Check",
|
||||
True,
|
||||
)
|
||||
scheduler.add_job_instance(job_instance, run=False)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def command_run(command_text):
|
||||
try:
|
||||
ret = {}
|
||||
tmp = command_text.strip().split(" ")
|
||||
if not tmp:
|
||||
ret["ret"] = "success"
|
||||
ret["log"] = "Empty.."
|
||||
return ret
|
||||
if tmp[0] == "set":
|
||||
if len(tmp) == 3:
|
||||
if tmp[1] == "token":
|
||||
tmp[1] = "unique"
|
||||
entity = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key=tmp[1])
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
if entity is None:
|
||||
ret["ret"] = "fail"
|
||||
ret["log"] = "%s not exist" % tmp[1]
|
||||
return ret
|
||||
entity.value = tmp[2] if tmp[2] != "EMPTY" else ""
|
||||
db.session.commit()
|
||||
ret["ret"] = "success"
|
||||
ret["log"] = "%s - %s" % (tmp[1], tmp[2])
|
||||
return ret
|
||||
|
||||
if tmp[0] == "set2":
|
||||
if tmp[1] == "klive":
|
||||
from klive import ModelSetting as KLiveModelSetting
|
||||
|
||||
if KLiveModelSetting.get(tmp[2]) is not None:
|
||||
KLiveModelSetting.set(tmp[2], tmp[3])
|
||||
ret["ret"] = "success"
|
||||
ret["log"] = f"KLive 설정 값 변경 : {tmp[2]} - {tmp[3]}"
|
||||
return ret
|
||||
|
||||
ret["ret"] = "fail"
|
||||
ret["log"] = "wrong command"
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret["ret"] = "fail"
|
||||
ret["log"] = str(exception)
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def link_save(link_data_str):
|
||||
try:
|
||||
data = json.loads(link_data_str)
|
||||
entity = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key="link_json")
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
entity.value = link_data_str
|
||||
db.session.commit()
|
||||
SystemLogic.apply_menu_link()
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def apply_menu_link():
|
||||
try:
|
||||
link_data_str = SystemLogic.get_setting_value("link_json")
|
||||
data = json.loads(link_data_str)
|
||||
from framework.menu import get_menu_map
|
||||
|
||||
menu_map = get_menu_map()
|
||||
for link_category in menu_map:
|
||||
if link_category["type"] == "link":
|
||||
break
|
||||
link_category["list"] = []
|
||||
for item in data:
|
||||
entity = {}
|
||||
entity["type"] = item["type"]
|
||||
if item["type"] == "link":
|
||||
entity["name"] = item["title"]
|
||||
entity["link"] = item["url"]
|
||||
link_category["list"].append(entity)
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
86
lib/system/logic_auth.py
Normal file
86
lib/system/logic_auth.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import traceback
|
||||
import random
|
||||
import json
|
||||
import string
|
||||
import codecs
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
# gommi 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import path_app_root, app
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
from .plugin import package_name, logger
|
||||
from .model import ModelSetting
|
||||
|
||||
|
||||
class SystemLogicAuth(object):
|
||||
@staticmethod
|
||||
def process_ajax(sub, req):
|
||||
logger.debug(sub)
|
||||
try:
|
||||
if sub == "apikey_generate":
|
||||
ret = SystemLogicAuth.apikey_generate()
|
||||
return jsonify(ret)
|
||||
elif sub == "do_auth":
|
||||
ret = SystemLogicAuth.do_auth()
|
||||
return jsonify(ret)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_ip():
|
||||
import socket
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(("10.255.255.255", 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = "127.0.0.1"
|
||||
finally:
|
||||
s.close()
|
||||
logger.debug("IP:%s", IP)
|
||||
return IP
|
||||
|
||||
@staticmethod
|
||||
def do_auth():
|
||||
try:
|
||||
# ret = {"ret": False, "msg": "", "level": 0, "point": 0}
|
||||
ret = {"ret": True, "msg": "", "level": 100, "point": 100}
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret["msg"] = "인증 실패"
|
||||
ret["level"] = -1
|
||||
ret["point"] = -1
|
||||
ModelSetting.set("auth_status", "auth_fail")
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def get_auth_status(retry=True):
|
||||
try:
|
||||
value = ModelSetting.get("auth_status")
|
||||
ret = {"ret": False, "desc": "", "level": 0, "point": 0}
|
||||
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
248
lib/system/logic_command.py
Executable file
248
lib/system/logic_command.py
Executable file
@@ -0,0 +1,248 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import threading
|
||||
import sys
|
||||
import io
|
||||
import time
|
||||
import json
|
||||
# third-party
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import path_app_root, socketio, py_queue, app
|
||||
|
||||
# 패키지
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
class SystemLogicCommand(object):
|
||||
|
||||
commands = None
|
||||
process = None
|
||||
stdout_queue = None
|
||||
thread = None
|
||||
send_to_ui_thread = None
|
||||
return_log = None
|
||||
@staticmethod
|
||||
def start(title, commands, clear=True, wait=False, show_modal=True):
|
||||
try:
|
||||
if show_modal:
|
||||
if clear:
|
||||
socketio.emit("command_modal_clear", None, namespace='/framework', broadcast=True)
|
||||
SystemLogicCommand.return_log = []
|
||||
SystemLogicCommand.title = title
|
||||
SystemLogicCommand.commands = commands
|
||||
SystemLogicCommand.thread = threading.Thread(target=SystemLogicCommand.execute_thread_function, args=(show_modal,))
|
||||
SystemLogicCommand.thread.setDaemon(True)
|
||||
SystemLogicCommand.thread.start()
|
||||
if wait:
|
||||
time.sleep(1)
|
||||
SystemLogicCommand.thread.join()
|
||||
return SystemLogicCommand.return_log
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def execute_thread_function(show_modal):
|
||||
try:
|
||||
#if wait:
|
||||
if show_modal:
|
||||
socketio.emit("loading_hide", None, namespace='/framework', broadcast=True)
|
||||
for command in SystemLogicCommand.commands:
|
||||
#logger.debug('Command :%s', command)
|
||||
if command[0] == 'msg':
|
||||
if show_modal:
|
||||
socketio.emit("command_modal_add_text", '%s\n\n' % command[1], namespace='/framework', broadcast=True)
|
||||
elif command[0] == 'system':
|
||||
if show_modal:
|
||||
socketio.emit("command_modal_add_text", '$ %s\n\n' % command[1], namespace='/framework', broadcast=True)
|
||||
os.system(command[1])
|
||||
else:
|
||||
show_command = True
|
||||
if command[0] == 'hide':
|
||||
show_command = False
|
||||
command = command[1:]
|
||||
#SystemLogicCommand.process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1)
|
||||
SystemLogicCommand.process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
SystemLogicCommand.start_communicate(command, show_command=show_command)
|
||||
SystemLogicCommand.send_queue_start(show_modal)
|
||||
if SystemLogicCommand.process is not None:
|
||||
SystemLogicCommand.process.wait()
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as exception:
|
||||
#logger.error('Exception:%s', exception)
|
||||
#logger.error(traceback.format_exc())
|
||||
if show_modal:
|
||||
socketio.emit("command_modal_show", SystemLogicCommand.title, namespace='/framework', broadcast=True)
|
||||
socketio.emit("command_modal_add_text", str(exception), namespace='/framework', broadcast=True)
|
||||
socketio.emit("command_modal_add_text", str(traceback.format_exc()), namespace='/framework', broadcast=True)
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def start_communicate(current_command, show_command=True):
|
||||
SystemLogicCommand.stdout_queue = py_queue.Queue()
|
||||
if show_command:
|
||||
SystemLogicCommand.stdout_queue.put('$ %s\n' % ' '.join(current_command))
|
||||
sout = io.open(SystemLogicCommand.process.stdout.fileno(), 'rb', closefd=False)
|
||||
#serr = io.open(process.stderr.fileno(), 'rb', closefd=False)
|
||||
|
||||
def Pump(stream):
|
||||
queue = py_queue.Queue()
|
||||
|
||||
def rdr():
|
||||
logger.debug('START RDR')
|
||||
while True:
|
||||
buf = SystemLogicCommand.process.stdout.read(1)
|
||||
if buf:
|
||||
queue.put( buf )
|
||||
else:
|
||||
queue.put( None )
|
||||
break
|
||||
logger.debug('END RDR')
|
||||
queue.put( None )
|
||||
time.sleep(1)
|
||||
|
||||
#Logic.command_close()
|
||||
def clct():
|
||||
active = True
|
||||
logger.debug('START clct')
|
||||
while active:
|
||||
r = queue.get()
|
||||
if r is None:
|
||||
break
|
||||
try:
|
||||
while True:
|
||||
r1 = queue.get(timeout=0.005)
|
||||
if r1 is None:
|
||||
active = False
|
||||
break
|
||||
else:
|
||||
r += r1
|
||||
except:
|
||||
pass
|
||||
if r is not None:
|
||||
try:
|
||||
r = r.decode('utf-8')
|
||||
except Exception as exception:
|
||||
#logger.error('Exception:%s', e)
|
||||
#logger.error(traceback.format_exc())
|
||||
try:
|
||||
r = r.decode('cp949')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
r = r.decode('euc-kr')
|
||||
except:
|
||||
pass
|
||||
|
||||
SystemLogicCommand.stdout_queue.put(r)
|
||||
#SystemLogicCommand.return_log.append(r)
|
||||
SystemLogicCommand.return_log += r.split('\n')
|
||||
logger.debug('IN:%s', r)
|
||||
SystemLogicCommand.stdout_queue.put('<END>')
|
||||
logger.debug('END clct')
|
||||
#Logic.command_close()
|
||||
for tgt in [rdr, clct]:
|
||||
th = threading.Thread(target=tgt)
|
||||
th.setDaemon(True)
|
||||
th.start()
|
||||
Pump(sout)
|
||||
#Pump(serr, 'stderr')
|
||||
|
||||
@staticmethod
|
||||
def send_queue_start(show_modal):
|
||||
def send_to_ui_thread_function():
|
||||
logger.debug('send_queue_thread_function START')
|
||||
if show_modal:
|
||||
socketio.emit("command_modal_show", SystemLogicCommand.title, namespace='/framework', broadcast=True)
|
||||
while SystemLogicCommand.stdout_queue:
|
||||
line = SystemLogicCommand.stdout_queue.get()
|
||||
logger.debug('Send to UI :%s', line)
|
||||
if line == '<END>':
|
||||
if show_modal:
|
||||
socketio.emit("command_modal_add_text", "\n", namespace='/framework', broadcast=True)
|
||||
break
|
||||
else:
|
||||
if show_modal:
|
||||
socketio.emit("command_modal_add_text", line, namespace='/framework', broadcast=True)
|
||||
SystemLogicCommand.send_to_ui_thread = None
|
||||
SystemLogicCommand.stdout_queue = None
|
||||
SystemLogicCommand.process = None
|
||||
logger.debug('send_to_ui_thread_function END')
|
||||
|
||||
if SystemLogicCommand.send_to_ui_thread is None:
|
||||
SystemLogicCommand.send_to_ui_thread = threading.Thread(target=send_to_ui_thread_function, args=())
|
||||
SystemLogicCommand.send_to_ui_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
if SystemLogicCommand.process is not None and SystemLogicCommand.process.poll() is None:
|
||||
import psutil
|
||||
process = psutil.Process(SystemLogicCommand.process.pid)
|
||||
for proc in SystemLogicCommand.process.children(recursive=True):
|
||||
proc.kill()
|
||||
SystemLogicCommand.process.kill()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
##################################
|
||||
# 외부 호출
|
||||
@staticmethod
|
||||
def execute_command_return(command, format=None, force_log=False):
|
||||
from tool_base import ToolSubprocess
|
||||
return ToolSubprocess.execute_command_return(command, format=format, force_log=force_log)
|
||||
"""
|
||||
try:
|
||||
logger.debug('execute_command_return : %s', ' '.join(command))
|
||||
|
||||
if app.config['config']['running_type'] == 'windows':
|
||||
command = ' '.join(command)
|
||||
|
||||
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
ret = []
|
||||
with process.stdout:
|
||||
for line in iter(process.stdout.readline, ''):
|
||||
ret.append(line.strip())
|
||||
if force_log:
|
||||
logger.debug(ret[-1])
|
||||
process.wait() # wait for the subprocess to exit
|
||||
|
||||
|
||||
if format is None:
|
||||
ret2 = '\n'.join(ret)
|
||||
elif format == 'json':
|
||||
try:
|
||||
index = 0
|
||||
for idx, tmp in enumerate(ret):
|
||||
#logger.debug(tmp)
|
||||
if tmp.startswith('{') or tmp.startswith('['):
|
||||
index = idx
|
||||
break
|
||||
ret2 = json.loads(''.join(ret[index:]))
|
||||
except:
|
||||
ret2 = None
|
||||
|
||||
return ret2
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error('command : %s', command)
|
||||
"""
|
||||
|
||||
208
lib/system/logic_command2.py
Executable file
208
lib/system/logic_command2.py
Executable file
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import threading
|
||||
import sys
|
||||
import io
|
||||
import time
|
||||
import json
|
||||
# third-party
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import path_app_root, socketio, logger, py_queue, app
|
||||
|
||||
# 패키지
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split('.')[0]
|
||||
#logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
class SystemLogicCommand2(object):
|
||||
instance_list = []
|
||||
|
||||
def __init__(self, title, commands, clear=True, wait=False, show_modal=True):
|
||||
self.title = title
|
||||
self.commands = commands
|
||||
self.clear = clear
|
||||
self.wait = wait
|
||||
self.show_modal = show_modal
|
||||
|
||||
self.process = None
|
||||
self.stdout_queue = None
|
||||
self.thread = None
|
||||
self.send_to_ui_thread = None
|
||||
self.return_log = []
|
||||
SystemLogicCommand2.instance_list.append(self)
|
||||
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
if self.show_modal:
|
||||
if self.clear:
|
||||
socketio.emit("command_modal_clear", None, namespace='/framework', broadcast=True)
|
||||
|
||||
self.thread = threading.Thread(target=self.execute_thread_function, args=())
|
||||
self.thread.setDaemon(True)
|
||||
self.thread.start()
|
||||
if self.wait:
|
||||
time.sleep(1)
|
||||
self.thread.join()
|
||||
return self.return_log
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def execute_thread_function(self):
|
||||
try:
|
||||
#if wait:
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_show", self.title, namespace='/framework', broadcast=True)
|
||||
socketio.emit("loading_hide", None, namespace='/framework', broadcast=True)
|
||||
|
||||
for command in self.commands:
|
||||
if command[0] == 'msg':
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_add_text", '%s\n\n' % command[1], namespace='/framework', broadcast=True)
|
||||
elif command[0] == 'system':
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_add_text", '$ %s\n\n' % command[1], namespace='/framework', broadcast=True)
|
||||
os.system(command[1])
|
||||
else:
|
||||
show_command = True
|
||||
if command[0] == 'hide':
|
||||
show_command = False
|
||||
command = command[1:]
|
||||
#self.process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1)
|
||||
self.process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
self.start_communicate(command, show_command=show_command)
|
||||
self.send_queue_start()
|
||||
if self.process is not None:
|
||||
self.process.wait()
|
||||
time.sleep(1)
|
||||
except Exception as exception:
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_show", self.title, namespace='/framework', broadcast=True)
|
||||
socketio.emit("command_modal_add_text", str(exception), namespace='/framework', broadcast=True)
|
||||
socketio.emit("command_modal_add_text", str(traceback.format_exc()), namespace='/framework', broadcast=True)
|
||||
|
||||
|
||||
def start_communicate(self, current_command, show_command=True):
|
||||
self.stdout_queue = py_queue.Queue()
|
||||
if show_command:
|
||||
self.stdout_queue.put('$ %s\n' % ' '.join(current_command))
|
||||
sout = io.open(self.process.stdout.fileno(), 'rb', closefd=False)
|
||||
#serr = io.open(process.stderr.fileno(), 'rb', closefd=False)
|
||||
|
||||
def Pump(stream):
|
||||
queue = py_queue.Queue()
|
||||
|
||||
def rdr():
|
||||
#logger.debug('START RDR')
|
||||
while True:
|
||||
buf = self.process.stdout.read(1)
|
||||
if buf:
|
||||
queue.put( buf )
|
||||
else:
|
||||
queue.put( None )
|
||||
break
|
||||
#logger.debug('END RDR')
|
||||
queue.put( None )
|
||||
time.sleep(1)
|
||||
|
||||
#Logic.command_close()
|
||||
def clct():
|
||||
active = True
|
||||
#logger.debug('START clct')
|
||||
while active:
|
||||
r = queue.get()
|
||||
if r is None:
|
||||
break
|
||||
try:
|
||||
while True:
|
||||
r1 = queue.get(timeout=0.005)
|
||||
if r1 is None:
|
||||
active = False
|
||||
break
|
||||
else:
|
||||
r += r1
|
||||
except:
|
||||
pass
|
||||
if r is not None:
|
||||
if app.config['config']['is_py2']:
|
||||
try:
|
||||
r = r.decode('utf-8')
|
||||
except Exception as exception:
|
||||
#logger.error('Exception:%s', e)
|
||||
#logger.error(traceback.format_exc())
|
||||
try:
|
||||
r = r.decode('cp949')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
r = r.decode('euc-kr')
|
||||
except:
|
||||
pass
|
||||
|
||||
self.stdout_queue.put(r)
|
||||
self.return_log += r.split('\n')
|
||||
#logger.debug('IN:%s', r)
|
||||
self.stdout_queue.put('<END>')
|
||||
#logger.debug('END clct')
|
||||
#Logic.command_close()
|
||||
for tgt in [rdr, clct]:
|
||||
th = threading.Thread(target=tgt)
|
||||
th.setDaemon(True)
|
||||
th.start()
|
||||
Pump(sout)
|
||||
#Pump(serr, 'stderr')
|
||||
|
||||
def send_queue_start(self):
|
||||
def send_to_ui_thread_function():
|
||||
#logger.debug('send_queue_thread_function START')
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_show", self.title, namespace='/framework', broadcast=True)
|
||||
while self.stdout_queue:
|
||||
line = self.stdout_queue.get()
|
||||
#logger.debug('Send to UI :%s', line)
|
||||
if line == '<END>':
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_add_text", "\n", namespace='/framework', broadcast=True)
|
||||
break
|
||||
else:
|
||||
if self.show_modal:
|
||||
socketio.emit("command_modal_add_text", line, namespace='/framework', broadcast=True)
|
||||
self.send_to_ui_thread = None
|
||||
self.stdout_queue = None
|
||||
self.process = None
|
||||
#logger.debug('send_to_ui_thread_function END')
|
||||
|
||||
if self.send_to_ui_thread is None:
|
||||
self.send_to_ui_thread = threading.Thread(target=send_to_ui_thread_function, args=())
|
||||
self.send_to_ui_thread.start()
|
||||
|
||||
|
||||
@classmethod
|
||||
def plugin_unload(cls):
|
||||
for instance in cls.instance_list:
|
||||
try:
|
||||
if instance.process is not None and instance.process.poll() is None:
|
||||
import psutil
|
||||
process = psutil.Process(instance.process.pid)
|
||||
for proc in instance.process.children(recursive=True):
|
||||
proc.kill()
|
||||
instance.process.kill()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
finally:
|
||||
try: instance.process.kill()
|
||||
except: pass
|
||||
346
lib/system/logic_plugin.py
Normal file
346
lib/system/logic_plugin.py
Normal file
@@ -0,0 +1,346 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
import json
|
||||
import zipfile
|
||||
import time
|
||||
import platform
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
)
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
|
||||
# gommi 공용
|
||||
from framework.logger import get_logger, set_level
|
||||
from framework import (
|
||||
app,
|
||||
db,
|
||||
scheduler,
|
||||
version,
|
||||
path_app_root,
|
||||
path_data,
|
||||
USERS,
|
||||
)
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
from .model import ModelSetting
|
||||
import system
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split(".")[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
|
||||
class LogicPlugin(object):
|
||||
plugin_loading = False
|
||||
|
||||
current_loading_plugin_list = {}
|
||||
|
||||
"""
|
||||
custom_plugin_list = []
|
||||
@staticmethod
|
||||
def loading():
|
||||
try:
|
||||
custom_path = os.path.join(path_data, 'custom')
|
||||
plugin_list = os.listdir(custom_path)
|
||||
logger.debug(plugin_list)
|
||||
for name in plugin_list:
|
||||
try:
|
||||
p = {}
|
||||
p['name'] = name
|
||||
p['plugin_name'] = name
|
||||
mod = __import__('%s' % (p['plugin_name']), fromlist=[])
|
||||
p['local_info'] = getattr(mod, 'plugin_info')
|
||||
p['status'] = 'latest'
|
||||
LogicPlugin.custom_plugin_list.append(p)
|
||||
except Exception as exception:
|
||||
logger.error('NO Exception:%s', exception)
|
||||
logger.debug('plunin not import : %s', p['plugin_name'])
|
||||
p['local_info'] = None
|
||||
p['status'] = 'no'
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_plugin_list():
|
||||
return LogicPlugin.current_loading_plugin_list
|
||||
"""
|
||||
try:
|
||||
if not LogicPlugin.plugin_loading:
|
||||
LogicPlugin.loading()
|
||||
LogicPlugin.plugin_loading = True
|
||||
return LogicPlugin.custom_plugin_list
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_plugin_info(plugin_name):
|
||||
try:
|
||||
lists = LogicPlugin.get_plugin_list()
|
||||
for key, value in lists.items():
|
||||
if key == plugin_name:
|
||||
return value["info"]
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
"""
|
||||
@staticmethod
|
||||
def plugin_install(plugin_name):
|
||||
logger.debug('plugin_name : %s', plugin_name)
|
||||
try:
|
||||
plugin_info = LogicPlugin.get_plugin_info(plugin_name)
|
||||
|
||||
custom_path = os.path.join(path_data, 'custom')
|
||||
|
||||
if 'platform' in plugin_info:
|
||||
if platform.system() not in plugin_info['platform']:
|
||||
return 'not_support_os'
|
||||
if 'running_type' in plugin_info:
|
||||
if app.config['config']['running_type'] not in plugin_info['running_type']:
|
||||
return 'not_support_running_type'
|
||||
git_clone_flag = True
|
||||
if git_clone_flag:
|
||||
# git clone
|
||||
command = ['git', '-C', custom_path, 'clone', plugin_info['git'], '--depth', '1']
|
||||
ret = Util.execute_command(command)
|
||||
return 'success'
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def plugin_uninstall(plugin_name):
|
||||
logger.debug("plugin_name : %s", plugin_name)
|
||||
try:
|
||||
mod = __import__("%s" % (plugin_name), fromlist=[])
|
||||
mod_plugin_unload = getattr(mod, "plugin_unload")
|
||||
mod_plugin_unload()
|
||||
time.sleep(1)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
custom_path = os.path.join(path_data, "custom")
|
||||
plugin_path = os.path.join(custom_path, plugin_name)
|
||||
if os.path.exists(plugin_path):
|
||||
try:
|
||||
import framework.common.celery as celery_task
|
||||
|
||||
celery_task.rmtree(plugin_path)
|
||||
except Exception as exception:
|
||||
try:
|
||||
logger.debug("plugin_uninstall")
|
||||
os.system('rmdir /S /Q "%s"' % plugin_path)
|
||||
except Exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
if os.path.exists(plugin_path):
|
||||
return "fail"
|
||||
else:
|
||||
return "success"
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def custom_plugin_update():
|
||||
try:
|
||||
if os.environ.get("UPDATE_STOP") == "true":
|
||||
return
|
||||
if os.environ.get("PLUGIN_UPDATE_FROM_PYTHON") == "false":
|
||||
return
|
||||
custom_path = os.path.join(path_data, "custom")
|
||||
tmps = os.listdir(custom_path)
|
||||
for t in tmps:
|
||||
plugin_path = os.path.join(custom_path, t)
|
||||
try:
|
||||
if t == "torrent_info":
|
||||
os.remove(os.path.join(plugin_path, "info.json"))
|
||||
except Exception:
|
||||
pass
|
||||
if t.startswith("_"):
|
||||
continue
|
||||
if os.path.exists(os.path.join(plugin_path, ".git")):
|
||||
command = [
|
||||
"git",
|
||||
"-C",
|
||||
plugin_path,
|
||||
"reset",
|
||||
"--hard",
|
||||
"HEAD",
|
||||
]
|
||||
ret = Util.execute_command(command)
|
||||
command = ["git", "-C", plugin_path, "pull"]
|
||||
ret = Util.execute_command(command)
|
||||
logger.debug("%s\n%s", plugin_path, ret)
|
||||
else:
|
||||
logger.debug(f"{plugin_path} is not git repo")
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_install_by_api(plugin_git, zip_url, zip_filename):
|
||||
logger.debug("plugin_git : %s", plugin_git)
|
||||
logger.debug("zip_url : %s", zip_url)
|
||||
logger.debug("zip_filename : %s", zip_filename)
|
||||
|
||||
is_git = True if plugin_git != None and plugin_git != "" else False
|
||||
ret = {}
|
||||
try:
|
||||
if is_git:
|
||||
name = plugin_git.split("/")[-1]
|
||||
else:
|
||||
name = zip_filename.split(".")[0]
|
||||
|
||||
custom_path = os.path.join(path_data, "custom")
|
||||
plugin_path = os.path.join(custom_path, name)
|
||||
logger.debug(plugin_path)
|
||||
plugin_info = None
|
||||
if os.path.exists(plugin_path):
|
||||
ret["ret"] = "already_exist"
|
||||
ret["log"] = "이미 설치되어 있습니다."
|
||||
else:
|
||||
if plugin_git and plugin_git.startswith("http"):
|
||||
for tag in ["main", "master"]:
|
||||
try:
|
||||
info_url = (
|
||||
plugin_git.replace(
|
||||
"github.com", "raw.githubusercontent.com"
|
||||
)
|
||||
+ "/%s/info.json" % tag
|
||||
)
|
||||
plugin_info = requests.get(info_url).json()
|
||||
if plugin_info is not None:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if zip_filename and zip_filename != "":
|
||||
import zipfile
|
||||
from tool_base import ToolBaseFile
|
||||
|
||||
zip_filepath = os.path.join(path_data, "tmp", zip_filename)
|
||||
extract_filepath = os.path.join(path_data, "tmp", name)
|
||||
logger.error(zip_url)
|
||||
logger.warning(zip_filepath)
|
||||
if ToolBaseFile.download(zip_url, zip_filepath):
|
||||
# logger.warning(os.path.exists(zip_filepath))
|
||||
with zipfile.ZipFile(zip_filepath, "r") as zip_ref:
|
||||
zip_ref.extractall(extract_filepath)
|
||||
plugin_info_filepath = os.path.join(
|
||||
extract_filepath, "info.json"
|
||||
)
|
||||
if os.path.exists(plugin_info_filepath):
|
||||
plugin_info = ToolBaseFile.read_json(
|
||||
plugin_info_filepath
|
||||
)
|
||||
if plugin_info == None:
|
||||
plugin_info = {}
|
||||
flag = True
|
||||
if "platform" in plugin_info:
|
||||
if platform.system() not in plugin_info["platform"]:
|
||||
ret["ret"] = "not_support_os"
|
||||
ret["log"] = "설치 가능한 OS가 아닙니다."
|
||||
flag = False
|
||||
if flag and "running_type" in plugin_info:
|
||||
if (
|
||||
app.config["config"]["running_type"]
|
||||
not in plugin_info["running_type"]
|
||||
):
|
||||
ret["ret"] = "not_support_running_type"
|
||||
ret["log"] = "설치 가능한 실행타입이 아닙니다."
|
||||
flag = False
|
||||
if flag and "policy_level" in plugin_info:
|
||||
if (
|
||||
plugin_info["policy_level"]
|
||||
> app.config["config"]["level"]
|
||||
):
|
||||
ret["ret"] = "policy_level"
|
||||
ret["log"] = "설치 가능 회원등급보다 낮습니다."
|
||||
flag = False
|
||||
if flag and "policy_point" in plugin_info:
|
||||
if (
|
||||
plugin_info["policy_level"]
|
||||
> app.config["config"]["point"]
|
||||
):
|
||||
ret["ret"] = "policy_level"
|
||||
ret["log"] = "설치 가능 포인트보다 낮습니다."
|
||||
flag = False
|
||||
|
||||
if flag:
|
||||
if plugin_git and plugin_git.startswith("http"):
|
||||
command = [
|
||||
"git",
|
||||
"-C",
|
||||
custom_path,
|
||||
"clone",
|
||||
plugin_git + ".git",
|
||||
"--depth",
|
||||
"1",
|
||||
]
|
||||
log = Util.execute_command(command)
|
||||
if zip_filename and zip_filename != "":
|
||||
import shutil
|
||||
|
||||
if os.path.exists(plugin_path) == False:
|
||||
shutil.move(extract_filepath, plugin_path)
|
||||
else:
|
||||
for tmp in os.listdir(extract_filepath):
|
||||
shutil.move(
|
||||
os.path.join(extract_filepath, tmp),
|
||||
plugin_path,
|
||||
)
|
||||
log = ""
|
||||
logger.debug(plugin_info)
|
||||
# 2021-12-31
|
||||
if "dependency" in plugin_info:
|
||||
for dep in plugin_info["dependency"]:
|
||||
for (
|
||||
key,
|
||||
value,
|
||||
) in LogicPlugin.get_plugin_list().items():
|
||||
if key == dep["name"]:
|
||||
logger.debug(
|
||||
f"Dependency 설치 - 이미 설치됨 : {dep['name']}"
|
||||
)
|
||||
break
|
||||
else:
|
||||
logger.debug(f"Dependency 설치 : {dep['home']}")
|
||||
LogicPlugin.plugin_install_by_api(
|
||||
dep["home"],
|
||||
dep.get("zip_url"),
|
||||
dep.get("zip_filename"),
|
||||
)
|
||||
# command = ['git', '-C', custom_path, 'clone', dep['home'], '--depth', '1']
|
||||
# ret = Util.execute_command(command)
|
||||
ret["ret"] = "success"
|
||||
ret["log"] = ["정상적으로 설치하였습니다. 재시작시 적용됩니다.", log]
|
||||
ret["log"] = "<br>".join(ret["log"])
|
||||
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret["ret"] = "exception"
|
||||
ret["log"] = str(exception)
|
||||
|
||||
return ret
|
||||
445
lib/system/logic_selenium.py
Executable file
445
lib/system/logic_selenium.py
Executable file
@@ -0,0 +1,445 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
import platform
|
||||
import time
|
||||
import base64
|
||||
|
||||
# third-party
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
try:
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from PIL import Image
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
except:
|
||||
pass
|
||||
from io import BytesIO
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import path_app_root, path_data
|
||||
|
||||
# 패키지
|
||||
from .plugin import logger, package_name
|
||||
from .model import ModelSetting
|
||||
|
||||
#########################################################
|
||||
# apk --no-cache add --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing firefox
|
||||
# https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz
|
||||
# curl -s -L "$url" | tar -xz
|
||||
|
||||
|
||||
class SystemLogicSelenium(object):
|
||||
chrome_driver = None
|
||||
chrome_driver_list = []
|
||||
|
||||
@staticmethod
|
||||
def process_ajax(sub, req):
|
||||
try:
|
||||
if sub == "selenium_test_go":
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
driver.get(req.form["url"])
|
||||
return jsonify("success")
|
||||
elif sub == "capture":
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
img = Image.open(BytesIO((driver.get_screenshot_as_png())))
|
||||
|
||||
timestamp = time.time()
|
||||
timestamp = str(timestamp).split(".")[0]
|
||||
tmp = os.path.join(path_data, "tmp", "%s.png" % timestamp)
|
||||
img.save(tmp)
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
ddns = SystemModelSetting.get("ddns")
|
||||
url = "%s/open_file%s" % (ddns, tmp)
|
||||
logger.debug(url)
|
||||
ret = {}
|
||||
ret["ret"] = "success"
|
||||
ret["data"] = url
|
||||
return jsonify(ret)
|
||||
elif sub == "full_capture":
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
img = SystemLogicSelenium.full_screenshot(driver)
|
||||
|
||||
timestamp = time.time()
|
||||
timestamp = str(timestamp).split(".")[0]
|
||||
tmp = os.path.join(path_data, "tmp", "%s.png" % timestamp)
|
||||
img.save(tmp)
|
||||
return send_file(tmp, mimetype="image/png")
|
||||
elif sub == "cookie":
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
data = driver.get_cookies()
|
||||
return jsonify(data)
|
||||
elif sub == "daum_capcha":
|
||||
daum_capcha = req.form["daum_capcha"]
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
# driver.find_element_by_xpath('//div[@class="secret_viewer"]/p/img').screenshot("captcha.png")
|
||||
driver.find_element_by_xpath('//input[@id="answer"]').send_keys(
|
||||
daum_capcha
|
||||
)
|
||||
driver.find_element_by_xpath(
|
||||
'//input[@value="%s"]' % "확인"
|
||||
).click()
|
||||
return jsonify({"ret": "success"})
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify("exception")
|
||||
|
||||
@staticmethod
|
||||
def get_pagesoruce_by_selenium(url, wait_xpath, retry=True):
|
||||
try:
|
||||
logger.debug("get_pagesoruce_by_selenium:%s %s", url, wait_xpath)
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
# logger.debug('driver : %s', driver)
|
||||
driver.get(url)
|
||||
|
||||
WebDriverWait(driver, 10).until(
|
||||
lambda driver: driver.find_element_by_xpath(wait_xpath)
|
||||
)
|
||||
# import time
|
||||
# driver.save_screenshot('%s.png' % time.time())
|
||||
# logger.debug('return page_source')
|
||||
return driver.page_source
|
||||
except Exception as exception:
|
||||
# logger.debug(driver.page_source)
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
SystemLogicSelenium.close_driver()
|
||||
# SystemLogicSelenium.chrome_driver = None
|
||||
if retry:
|
||||
return SystemLogicSelenium.get_pagesoruce_by_selenium(
|
||||
url, wait_xpath, retry=False
|
||||
)
|
||||
|
||||
# 1회성
|
||||
@staticmethod
|
||||
def get_driver(chrome_options=None):
|
||||
try:
|
||||
if SystemLogicSelenium.chrome_driver is None:
|
||||
SystemLogicSelenium.chrome_driver = (
|
||||
SystemLogicSelenium.inner_create_driver(chrome_options)
|
||||
)
|
||||
return SystemLogicSelenium.chrome_driver
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# 플러그인이 점유
|
||||
@staticmethod
|
||||
def create_driver(chrome_options=None):
|
||||
try:
|
||||
driver = SystemLogicSelenium.inner_create_driver(chrome_options)
|
||||
if driver is not None:
|
||||
SystemLogicSelenium.chrome_driver_list.append(driver)
|
||||
return driver
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def close_driver():
|
||||
try:
|
||||
# if SystemLogicSelenium.chrome_driver is not None:
|
||||
# SystemLogicSelenium.chrome_driver.quit()
|
||||
# SystemLogicSelenium.chrome_driver = None
|
||||
if SystemLogicSelenium.chrome_driver is not None:
|
||||
try:
|
||||
SystemLogicSelenium.chrome_driver.close()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(2)
|
||||
try:
|
||||
SystemLogicSelenium.chrome_driver.quit()
|
||||
except Exception:
|
||||
pass
|
||||
SystemLogicSelenium.chrome_driver = None
|
||||
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def inner_create_driver(chrome_options):
|
||||
try:
|
||||
driver = None
|
||||
remote_url = ModelSetting.get("selenium_remote_url")
|
||||
if remote_url.endswith("/wd/hub/"):
|
||||
remote_url = remote_url[:-1]
|
||||
if remote_url != "":
|
||||
if chrome_options is None:
|
||||
chrome_options = webdriver.ChromeOptions()
|
||||
tmp = ModelSetting.get_list(
|
||||
"selenium_remote_default_option"
|
||||
)
|
||||
for t in tmp:
|
||||
chrome_options.add_argument(t)
|
||||
driver = webdriver.Remote(
|
||||
command_executor=remote_url,
|
||||
desired_capabilities=chrome_options.to_capabilities(),
|
||||
)
|
||||
driver.set_window_size(1920, 1080)
|
||||
logger.debug("Using Remote :%s", driver)
|
||||
else:
|
||||
path_chrome = os.path.join(
|
||||
path_app_root, "bin", platform.system(), "chromedriver"
|
||||
)
|
||||
if platform.system() == "Windows":
|
||||
path_chrome += ".exe"
|
||||
if chrome_options is None:
|
||||
chrome_options = webdriver.ChromeOptions()
|
||||
tmp = ModelSetting.get_list(
|
||||
"selenium_binary_default_option"
|
||||
)
|
||||
for t in tmp:
|
||||
chrome_options.add_argument(t)
|
||||
driver = webdriver.Chrome(
|
||||
path_chrome, chrome_options=chrome_options
|
||||
)
|
||||
logger.debug("Using local bin :%s", driver)
|
||||
if driver is not None:
|
||||
return driver
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
SystemLogicSelenium.close_driver()
|
||||
# logger.debug(SystemLogicSelenium.chrome_driver)
|
||||
# if SystemLogicSelenium.chrome_driver is not None:
|
||||
# SystemLogicSelenium.chrome_driver.quit()
|
||||
# logger.debug(SystemLogicSelenium.chrome_driver)
|
||||
|
||||
for tmp in SystemLogicSelenium.chrome_driver_list:
|
||||
if tmp is not None:
|
||||
tmp.quit()
|
||||
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_text_excluding_children(driver, element):
|
||||
return driver.execute_script(
|
||||
"""
|
||||
return jQuery(arguments[0]).contents().filter(function() {
|
||||
return this.nodeType == Node.TEXT_NODE;
|
||||
}).text();
|
||||
""",
|
||||
element,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def full_screenshot(driver, low_offset=0):
|
||||
try:
|
||||
# initiate value
|
||||
# save_path = save_path + '.png' if save_path[-4::] != '.png' else save_path
|
||||
img_li = [] # to store image fragment
|
||||
offset = 0 # where to start
|
||||
|
||||
# js to get height
|
||||
height = driver.execute_script(
|
||||
"return Math.max("
|
||||
"document.documentElement.clientHeight, window.innerHeight);"
|
||||
)
|
||||
# height = height - low_offset
|
||||
# js to get the maximum scroll height
|
||||
# Ref--> https://stackoverflow.com/questions/17688595/finding-the-maximum-scroll-position-of-a-page
|
||||
max_window_height = driver.execute_script(
|
||||
"return Math.max("
|
||||
"document.body.scrollHeight, "
|
||||
"document.body.offsetHeight, "
|
||||
"document.documentElement.clientHeight, "
|
||||
"document.documentElement.scrollHeight, "
|
||||
"document.documentElement.offsetHeight);"
|
||||
)
|
||||
|
||||
# looping from top to bottom, append to img list
|
||||
# Ref--> https://gist.github.com/fabtho/13e4a2e7cfbfde671b8fa81bbe9359fb
|
||||
|
||||
while offset < max_window_height:
|
||||
|
||||
# Scroll to height
|
||||
driver.execute_script(
|
||||
"""
|
||||
window.scrollTo(0, arguments[0]);
|
||||
""",
|
||||
offset,
|
||||
)
|
||||
img = Image.open(BytesIO((driver.get_screenshot_as_png())))
|
||||
|
||||
if low_offset != 0:
|
||||
img = img.crop(
|
||||
(0, 0, img.width, img.height - low_offset)
|
||||
) # defines crop points
|
||||
|
||||
img_li.append(img)
|
||||
offset += height
|
||||
logger.debug("offset : %s / %s", offset, max_window_height)
|
||||
|
||||
# Stitch image into one
|
||||
# Set up the full screen frame
|
||||
img_frame_height = sum([img_frag.size[1] for img_frag in img_li])
|
||||
img_frame = Image.new("RGB", (img_li[0].size[0], img_frame_height))
|
||||
offset = 0
|
||||
for img_frag in img_li:
|
||||
img_frame.paste(img_frag, (0, offset))
|
||||
offset += img_frag.size[1]
|
||||
logger.debug("paste offset : %s ", offset)
|
||||
# img_frame.save(save_path)
|
||||
# return
|
||||
return img_frame
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def remove_element(driver, element):
|
||||
driver.execute_script(
|
||||
"""
|
||||
var element = arguments[0];
|
||||
element.parentNode.removeChild(element);
|
||||
""",
|
||||
element,
|
||||
)
|
||||
|
||||
######################################################################
|
||||
@staticmethod
|
||||
def __get_downloaded_files(driver=None):
|
||||
if driver is None:
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
if not driver.current_url.startswith("chrome://downloads"):
|
||||
driver.get("chrome://downloads/")
|
||||
# driver.implicitly_wait(4)
|
||||
return driver.execute_script(
|
||||
"return downloads.Manager.get().items_ "
|
||||
" .filter(e => e.state === 'COMPLETE') "
|
||||
" .map(e => e.filePath || e.file_path); "
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_file_content(path, driver=None):
|
||||
if driver is None:
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
|
||||
elem = driver.execute_script(
|
||||
"var input = window.document.createElement('INPUT'); "
|
||||
"input.setAttribute('type', 'file'); "
|
||||
"input.hidden = true; "
|
||||
"input.onchange = function (e) { e.stopPropagation() }; "
|
||||
"return window.document.documentElement.appendChild(input); "
|
||||
)
|
||||
|
||||
elem._execute("sendKeysToElement", {"value": [path], "text": path})
|
||||
|
||||
result = driver.execute_async_script(
|
||||
"var input = arguments[0], callback = arguments[1]; "
|
||||
"var reader = new FileReader(); "
|
||||
"reader.onload = function (ev) { callback(reader.result) }; "
|
||||
"reader.onerror = function (ex) { callback(ex.message) }; "
|
||||
"reader.readAsDataURL(input.files[0]); "
|
||||
"input.remove(); ",
|
||||
elem,
|
||||
)
|
||||
|
||||
if not result.startswith("data:"):
|
||||
raise Exception("Failed to get file content: %s" % result)
|
||||
|
||||
return base64.b64decode(result[result.find("base64,") + 7 :])
|
||||
|
||||
@staticmethod
|
||||
def get_downloaded_files(driver=None):
|
||||
if driver is None:
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
|
||||
# files = WebDriverWait(driver, 20, 1).until(SystemLogicSelenium.__get_downloaded_files)
|
||||
files = SystemLogicSelenium.__get_downloaded_files()
|
||||
return files
|
||||
|
||||
@staticmethod
|
||||
def waitUntilDownloadCompleted(maxTime=600, driver=None):
|
||||
if driver is None:
|
||||
driver = SystemLogicSelenium.get_driver()
|
||||
driver.execute_script("window.open()")
|
||||
# switch to new tab
|
||||
driver.switch_to.window(driver.window_handles[-1])
|
||||
# navigate to chrome downloads
|
||||
driver.get("chrome://downloads")
|
||||
# define the endTime
|
||||
endTime = time.time() + maxTime
|
||||
while True:
|
||||
try:
|
||||
# get the download percentage
|
||||
downloadPercentage = driver.execute_script(
|
||||
"return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('#progress').value"
|
||||
)
|
||||
# check if downloadPercentage is 100 (otherwise the script will keep waiting)
|
||||
if downloadPercentage == 100:
|
||||
# exit the method once it's completed
|
||||
return downloadPercentage
|
||||
except:
|
||||
pass
|
||||
# wait for 1 second before checking the percentage next time
|
||||
time.sleep(1)
|
||||
# exit method if the download not completed with in MaxTime.
|
||||
if time.time() > endTime:
|
||||
break
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
driver = webdriver.Chrome(desired_capabilities=capabilities_chrome)
|
||||
#driver = webdriver.Remote('http://127.0.0.1:5555/wd/hub', capabilities_chrome)
|
||||
|
||||
# download a pdf file
|
||||
driver.get("https://www.mozilla.org/en-US/foundation/documents")
|
||||
driver.find_element_by_css_selector("[href$='.pdf']").click()
|
||||
|
||||
# list all the completed remote files (waits for at least one)
|
||||
files = WebDriverWait(driver, 20, 1).until(get_downloaded_files)
|
||||
|
||||
# get the content of the first file remotely
|
||||
content = get_file_content(driver, files[0])
|
||||
|
||||
# save the content in a local file in the working directory
|
||||
with open(os.path.basename(files[0]), 'wb') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
capabilities_chrome = { \
|
||||
'browserName': 'chrome',
|
||||
# 'proxy': { \
|
||||
# 'proxyType': 'manual',
|
||||
# 'sslProxy': '50.59.162.78:8088',
|
||||
# 'httpProxy': '50.59.162.78:8088'
|
||||
# },
|
||||
'goog:chromeOptions': { \
|
||||
'args': [
|
||||
],
|
||||
'prefs': { \
|
||||
# 'download.default_directory': "",
|
||||
# 'download.directory_upgrade': True,
|
||||
'download.prompt_for_download': False,
|
||||
'plugins.always_open_pdf_externally': True,
|
||||
'safebrowsing_for_trusted_sources_enabled': False
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
100
lib/system/logic_site.py
Normal file
100
lib/system/logic_site.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
import platform
|
||||
import time
|
||||
|
||||
# third-party
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import path_app_root, path_data, socketio, scheduler
|
||||
from framework.job import Job
|
||||
from tool_base import d
|
||||
|
||||
|
||||
# 패키지
|
||||
from .plugin import logger, package_name
|
||||
from .model import ModelSetting
|
||||
|
||||
|
||||
class SystemLogicSite(object):
|
||||
# pass
|
||||
daum_cookie = None
|
||||
|
||||
@staticmethod
|
||||
def process_ajax(sub, req):
|
||||
try:
|
||||
ret = {}
|
||||
if sub == "site_daum_test":
|
||||
pass
|
||||
elif sub == "scheduler":
|
||||
go = req.form["scheduler"]
|
||||
if go == "true":
|
||||
SystemLogicSite.scheduler_start()
|
||||
else:
|
||||
SystemLogicSite.scheduler_stop()
|
||||
return jsonify(go)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret["ret"] = False
|
||||
ret["log"] = str(traceback.format_exc())
|
||||
return jsonify(ret)
|
||||
|
||||
@staticmethod
|
||||
def plugin_load():
|
||||
|
||||
SystemLogicSite.get_daum_cookies(force=True)
|
||||
if ModelSetting.get_bool("site_daum_auto_start"):
|
||||
SystemLogicSite.scheduler_start()
|
||||
|
||||
@staticmethod
|
||||
def scheduler_function():
|
||||
try:
|
||||
data = SystemLogicSite.get_daum_cookie_by_selenium()
|
||||
if data["ret"]:
|
||||
ModelSetting.set("site_daum_cookie", data["data"])
|
||||
SystemLogicSite.get_daum_cookies(force=True)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_daum_cookies(force=False):
|
||||
try:
|
||||
if SystemLogicSite.daum_cookie is None or force:
|
||||
ret = {}
|
||||
tmp = ModelSetting.get("site_daum_cookie")
|
||||
tmps = tmp.split(";")
|
||||
for t in tmps:
|
||||
t2 = t.split("=")
|
||||
if len(t2) == 2:
|
||||
ret[t2[0]] = t2[1]
|
||||
SystemLogicSite.daum_cookie = ret
|
||||
return SystemLogicSite.daum_cookie
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return {
|
||||
"TIARA": "gaXEIPluo-wWAFlwZN6l8gN3yzhkoo_piP.Kymhuy.6QBt4Q6.cRtxbKDaWpWajcyteRHzrlTVpJRxLjwLoMvyYLVi_7xJ1L"
|
||||
}
|
||||
|
||||
daum_headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||
}
|
||||
158
lib/system/model.py
Normal file
158
lib/system/model.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# =========================================================
|
||||
# python
|
||||
import traceback
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
# gommi 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import app, db, scheduler
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split(".")[0]
|
||||
logger = get_logger(package_name)
|
||||
# ==============================================================
|
||||
|
||||
|
||||
class ModelSetting(db.Model):
|
||||
__tablename__ = "system_setting"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(100), unique=True, nullable=False)
|
||||
value = db.Column(db.String(100), nullable=False)
|
||||
|
||||
def __init__(self, key, value):
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return "<SystemSetting(id:%s, key:%s, value:%s)>" % (
|
||||
self.id,
|
||||
self.key,
|
||||
self.value,
|
||||
)
|
||||
|
||||
def as_dict(self):
|
||||
return {x.name: getattr(self, x.name) for x in self.__table__.columns}
|
||||
|
||||
@staticmethod
|
||||
def get(key):
|
||||
try:
|
||||
ret = db.session.query(ModelSetting).filter_by(key=key).first()
|
||||
if ret is not None:
|
||||
return ret.value.strip()
|
||||
else:
|
||||
return ""
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s %s", exception, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_int(key):
|
||||
try:
|
||||
return int(ModelSetting.get(key))
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s %s", exception, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_bool(key):
|
||||
try:
|
||||
return ModelSetting.get(key) == "True"
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s %s", exception, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def set(key, value):
|
||||
try:
|
||||
logger.debug(key)
|
||||
item = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key=key)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
if item is not None:
|
||||
item.value = value.strip() if value is not None else value
|
||||
db.session.commit()
|
||||
else:
|
||||
db.session.add(ModelSetting(key, value.strip()))
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s %s", exception, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def to_dict():
|
||||
try:
|
||||
from framework.util import Util
|
||||
|
||||
arg = Util.db_list_to_dict(db.session.query(ModelSetting).all())
|
||||
arg["package_name"] = package_name
|
||||
return arg
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def setting_save(req):
|
||||
try:
|
||||
for key, value in req.form.items():
|
||||
if key in ["scheduler", "is_running"]:
|
||||
continue
|
||||
if key.startswith("tmp_"):
|
||||
continue
|
||||
logger.debug("Key:%s Value:%s", key, value)
|
||||
entity = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key=key)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
entity.value = value
|
||||
db.session.commit()
|
||||
return True
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.debug("Error Key:%s Value:%s", key, value)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_list(key):
|
||||
try:
|
||||
value = ModelSetting.get(key)
|
||||
values = [
|
||||
x.strip().replace(" ", "").strip()
|
||||
for x in value.replace("\n", "|").split("|")
|
||||
]
|
||||
values = Util.get_list_except_empty(values)
|
||||
return values
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error("Error Key:%s Value:%s", key, value)
|
||||
|
||||
@staticmethod
|
||||
def make_apikey(url):
|
||||
url = url.format(ddns=ModelSetting.get("ddns"))
|
||||
if ModelSetting.get_bool("auth_use_apikey"):
|
||||
if url.find("?") == -1:
|
||||
url += "?"
|
||||
else:
|
||||
url += "&"
|
||||
url += "apikey=%s" % ModelSetting.get("auth_apikey")
|
||||
return url
|
||||
440
lib/system/plugin.py
Normal file
440
lib/system/plugin.py
Normal file
@@ -0,0 +1,440 @@
|
||||
# third-party
|
||||
import requests
|
||||
import traceback
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
render_template,
|
||||
redirect,
|
||||
jsonify,
|
||||
stream_with_context,
|
||||
)
|
||||
|
||||
# gommi 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import (
|
||||
app,
|
||||
db,
|
||||
scheduler,
|
||||
socketio,
|
||||
check_api,
|
||||
path_app_root,
|
||||
path_data,
|
||||
) # , celery
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
from framework.util import Util, SingletonClass
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split(".")[0]
|
||||
logger = get_logger(package_name)
|
||||
|
||||
# package
|
||||
from .logic import SystemLogic
|
||||
from .model import ModelSetting
|
||||
from .logic_plugin import LogicPlugin
|
||||
from .logic_selenium import SystemLogicSelenium
|
||||
from .logic_command import SystemLogicCommand
|
||||
from .logic_command2 import SystemLogicCommand2
|
||||
from .logic_auth import SystemLogicAuth
|
||||
|
||||
# celery 때문에 import
|
||||
from .logic_site import SystemLogicSite
|
||||
|
||||
|
||||
# ========================================================================
|
||||
# plugin public
|
||||
# ========================================================================
|
||||
blueprint = Blueprint(
|
||||
package_name,
|
||||
package_name,
|
||||
url_prefix="/%s" % package_name,
|
||||
template_folder="templates",
|
||||
)
|
||||
menu = {
|
||||
"main": [package_name, "설정"],
|
||||
"sub": [
|
||||
["setting", "일반설정"],
|
||||
["plugin", "플러그인"],
|
||||
["tool", "Tool"],
|
||||
["log", "로그"],
|
||||
],
|
||||
"sub2": {
|
||||
"setting": [
|
||||
["basic", "기본"],
|
||||
["auth", "인증"],
|
||||
["env", "시스템"],
|
||||
["notify", "알림"],
|
||||
["telegram_bot", "텔레그램 봇"],
|
||||
["selenium", "Selenium"],
|
||||
["trans", "번역"],
|
||||
["site", "Site"],
|
||||
["memo", "메모"],
|
||||
["terminal", "Terminal"],
|
||||
],
|
||||
"rss": [["setting", "설정"], ["job", "작업"], ["list", "목록"]],
|
||||
"cache": [["setting", "설정"], ["list", "목록"]],
|
||||
"tool": [["crypt", "암호화"]],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def plugin_load():
|
||||
logger.debug("plugin_load:%s", package_name)
|
||||
SystemLogic.plugin_load()
|
||||
# SystemLogicTelegramBot.plugin_load()
|
||||
SystemLogicSite.plugin_load()
|
||||
|
||||
|
||||
def plugin_unload():
|
||||
logger.debug("plugin_load:%s", package_name)
|
||||
SystemLogicSelenium.plugin_unload()
|
||||
SystemLogicCommand.plugin_unload()
|
||||
SystemLogicCommand2.plugin_unload()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Web menu
|
||||
# ============================================================
|
||||
@blueprint.route("/")
|
||||
def normal():
|
||||
return redirect("/%s/setting" % package_name)
|
||||
|
||||
|
||||
@login_required
|
||||
def home():
|
||||
return render_template("info.html", arg=None)
|
||||
|
||||
|
||||
@blueprint.route("/<sub>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def first_menu(sub):
|
||||
logger.debug("System SUB:%s", sub)
|
||||
arg = None
|
||||
if sub == "home":
|
||||
return render_template("%s_%s.html" % (package_name, sub), arg=None)
|
||||
elif sub == "setting":
|
||||
return redirect("/%s/%s/basic" % (package_name, sub))
|
||||
elif sub == "restart":
|
||||
restart()
|
||||
return render_template(
|
||||
"system_restart.html",
|
||||
sub=sub,
|
||||
referer=request.headers.get("Referer"),
|
||||
)
|
||||
elif sub == "plugin":
|
||||
arg = ModelSetting.to_dict()
|
||||
logger.debug = f"arg:: {arg}"
|
||||
arg["install"] = request.args.get("install", "")
|
||||
|
||||
return render_template("system_plugin.html", arg=arg)
|
||||
|
||||
return render_template("sample.html", title="%s - %s" % (package_name, sub))
|
||||
|
||||
|
||||
@blueprint.route("/<sub>/<sub2>")
|
||||
@login_required
|
||||
def second_menu(sub, sub2):
|
||||
try:
|
||||
if sub == "setting":
|
||||
arg = ModelSetting.to_dict()
|
||||
arg["sub"] = sub2
|
||||
if sub2 == "basic":
|
||||
arg["point"] = SystemLogic.point
|
||||
return render_template(
|
||||
"%s_%s_%s.html" % (package_name, sub, sub2), arg=arg
|
||||
)
|
||||
elif sub2 == "auth":
|
||||
arg["auth_result"] = SystemLogicAuth.get_auth_status()
|
||||
return render_template(
|
||||
"%s_%s_%s.html" % (package_name, sub, sub2), arg=arg
|
||||
)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
#########################################################
|
||||
# For UI
|
||||
#########################################################
|
||||
@blueprint.route("/ajax/<sub>/<sub2>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def second_ajax(sub, sub2):
|
||||
logger.debug("System AJAX sub:%s", sub)
|
||||
try:
|
||||
if sub == "trans":
|
||||
from .logic_trans import SystemLogicTrans
|
||||
|
||||
return SystemLogicTrans.process_ajax(sub2, request)
|
||||
elif sub == "auth":
|
||||
from .logic_auth import SystemLogicAuth
|
||||
|
||||
return SystemLogicAuth.process_ajax(sub2, request)
|
||||
elif sub == "selenium":
|
||||
return SystemLogicSelenium.process_ajax(sub2, request)
|
||||
elif sub == "notify":
|
||||
return SystemLogicNotify.process_ajax(sub2, request)
|
||||
elif sub == "telegram_bot":
|
||||
return SystemLogicTelegramBot.process_ajax(sub2, request)
|
||||
elif sub == "env":
|
||||
return SystemLogicEnv.process_ajax(sub2, request)
|
||||
elif sub == "site":
|
||||
return SystemLogicSite.process_ajax(sub2, request)
|
||||
elif sub == "crypt":
|
||||
return SystemLogicToolDecrypt.process_ajax(sub2, request)
|
||||
elif sub == "terminal":
|
||||
return SystemLogicTerminal.process_ajax(sub2, request)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
@blueprint.route("/ajax/<sub>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def ajax(sub):
|
||||
# logger.debug('System AJAX sub:%s', sub)
|
||||
try:
|
||||
if sub == "info":
|
||||
try:
|
||||
ret = {}
|
||||
ret["system"] = SystemLogic.get_info()
|
||||
ret["scheduler"] = scheduler.get_job_list_info()
|
||||
|
||||
# logger.debug(ret)
|
||||
return jsonify(ret)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify()
|
||||
elif sub == "setting_save_system":
|
||||
try:
|
||||
ret = SystemLogic.setting_save_system(request)
|
||||
return jsonify(ret)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "setting_save":
|
||||
ret = ModelSetting.setting_save(request)
|
||||
SystemLogic.setting_save_after()
|
||||
return jsonify(ret)
|
||||
elif sub == "ddns_test":
|
||||
try:
|
||||
url = request.form["ddns"] + "/version"
|
||||
res = requests.get(url)
|
||||
data = res.text
|
||||
# data = res.json()
|
||||
# logger.debug(data)
|
||||
return jsonify(data)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify("fail")
|
||||
elif sub == "celery_test":
|
||||
try:
|
||||
# result = add_together.delay(10, 20)
|
||||
# print(result.wait())
|
||||
# return 'Welcome to my app!'
|
||||
try:
|
||||
import framework
|
||||
|
||||
framework.exit_code = 1
|
||||
socketio.stop()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
# os.environ['SJVA_REPEAT_TYPE'] = 'update'
|
||||
|
||||
return jsonify()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "command_run":
|
||||
try:
|
||||
command_text = request.form["command_text"]
|
||||
ret = SystemLogic.command_run(command_text)
|
||||
return jsonify(ret)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "get_link_list":
|
||||
try:
|
||||
link_json = SystemLogic.get_setting_value("link_json")
|
||||
j = json.loads(link_json)
|
||||
return jsonify(j)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "link_save":
|
||||
try:
|
||||
link_data_str = request.form["link_data"]
|
||||
# j = json.loads(link_data)
|
||||
ret = SystemLogic.link_save(link_data_str)
|
||||
return jsonify(ret)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "plugin_list":
|
||||
try:
|
||||
return jsonify(LogicPlugin.get_plugin_list())
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
elif sub == "plugin_install":
|
||||
try:
|
||||
# plugin_name = request.form['plugin_name']
|
||||
plugin_git = request.form["plugin_git"]
|
||||
return jsonify(
|
||||
LogicPlugin.plugin_install_by_api(
|
||||
plugin_git,
|
||||
request.form.get("zip_url"),
|
||||
request.form.get("zip_filename"),
|
||||
)
|
||||
)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "plugin_uninstall":
|
||||
try:
|
||||
plugin_name = request.form["plugin_name"]
|
||||
return jsonify(LogicPlugin.plugin_uninstall(plugin_name))
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
elif sub == "recent_version":
|
||||
ret = SystemLogic.get_recent_version()
|
||||
ret = {"ret": ret, "version": SystemLogic.recent_version}
|
||||
return jsonify(ret)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def restart():
|
||||
try:
|
||||
try:
|
||||
import framework
|
||||
|
||||
framework.exit_code = 1
|
||||
app_close()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def shutdown():
|
||||
try:
|
||||
try:
|
||||
nginx_kill = "/app/data/custom/nginx/files/kill.sh"
|
||||
if os.path.exists(nginx_kill):
|
||||
SystemLogicCommand.execute_command_return([nginx_kill])
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
import framework
|
||||
|
||||
framework.exit_code = 0
|
||||
app_close()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def app_close():
|
||||
try:
|
||||
from framework.init_plugin import plugin_unload
|
||||
|
||||
plugin_unload()
|
||||
socketio.stop()
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
@socketio.on("connect", namespace="/%s" % package_name)
|
||||
def connect():
|
||||
try:
|
||||
InfoProcess.instance().connect(request.sid)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
@socketio.on("disconnect", namespace="/%s" % package_name)
|
||||
def disconnect():
|
||||
try:
|
||||
InfoProcess.instance().disconnect(request.sid)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
@socketio.on("connect", namespace="/system_restart")
|
||||
def connect_system_restart():
|
||||
try:
|
||||
socketio.emit(
|
||||
"on_connect", "restart", namespace="/system_restart", broadcast=True
|
||||
)
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
@socketio.on("disconnect", namespace="/system_restart")
|
||||
def disconnect_system_restart():
|
||||
try:
|
||||
pass
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
class InfoThread(threading.Thread):
|
||||
def __init__(self):
|
||||
super(InfoThread, self).__init__()
|
||||
self.stop_flag = False
|
||||
self.daemon = True
|
||||
|
||||
def stop(self):
|
||||
self.stop_flag = True
|
||||
|
||||
def run(self) -> None:
|
||||
while not self.stop_flag:
|
||||
ret = {}
|
||||
ret["system"] = SystemLogic.get_info()
|
||||
ret["scheduler"] = scheduler.get_job_list_info()
|
||||
socketio.emit(
|
||||
"status", ret, namespace="/system", broadcast=True
|
||||
)
|
||||
# logger.debug('InfoThread')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
class InfoProcess(SingletonClass):
|
||||
sid_list = []
|
||||
thread = None
|
||||
|
||||
@classmethod
|
||||
def connect(cls, sid):
|
||||
logger.debug("Info connect:%s", InfoProcess.sid_list)
|
||||
if not InfoProcess.sid_list:
|
||||
InfoProcess.thread = InfoThread()
|
||||
InfoProcess.thread.start()
|
||||
InfoProcess.sid_list.append(sid)
|
||||
|
||||
@classmethod
|
||||
def disconnect(cls, sid):
|
||||
logger.debug("Info disconnect:%s", InfoProcess.sid_list)
|
||||
InfoProcess.sid_list.remove(sid)
|
||||
if not InfoProcess.sid_list:
|
||||
InfoProcess.thread.stop()
|
||||
153
lib/system/templates/system_home.html
Executable file
153
lib/system/templates/system_home.html
Executable file
@@ -0,0 +1,153 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById("plugin_first_menu").innerHTML = '';
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h3>시스템</h3>
|
||||
<hr>
|
||||
{{ macros.info_text('python_version', 'Python 버전') }}
|
||||
{{ macros.info_text('platform', 'Platform') }}
|
||||
{{ macros.info_text('processor', 'Processor') }}
|
||||
{{ macros.info_text_and_buttons('version', 'SJVA 버전', [['changelog_btn', '업데이트 내역'], ['recent_version_btn', '최신버전 확인']]) }}
|
||||
{{ macros.info_text('path_app_root', 'SJVA 폴더') }}
|
||||
{{ macros.info_text('running_type', '실행타입') }}
|
||||
{{ macros.info_text('auth', '인증') }}
|
||||
<div class="d-inline-block"></div>
|
||||
|
||||
<h3>모니터링</h3>
|
||||
<hr>
|
||||
{{ macros.info_text('time', '시간') }}
|
||||
{{ macros.info_text('cpu_percent', 'CPU 사용량') }}
|
||||
{{ macros.info_text('memory', '메모리') }}
|
||||
{{ macros.info_text('disk', '디스크') }}
|
||||
<div class="d-inline-block"></div>
|
||||
|
||||
<h3>스케쥴러</h3>
|
||||
<hr>
|
||||
<div id="scheduler_list_div"></div>
|
||||
</div> <!--전체-->
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
|
||||
function make_scheduler_list(data) {
|
||||
|
||||
str = m_row_start(p='0');
|
||||
str += m_col(1, '<strong>NO</strong>');
|
||||
str += m_col(2, '<strong>플러그인 & ID</strong>');
|
||||
//str += m_col(2, '<strong>생성 & 다음 실행</strong>');
|
||||
str += m_col(2, '<strong>다음 실행 (남은시간)</strong>');
|
||||
str += m_col(1, '<strong>이전소요/횟수</strong>');
|
||||
str += m_col(2, '<strong>Interval & Cron</strong>');
|
||||
str += m_col(1, '<strong>상태</strong>');
|
||||
str += m_col(3, '<strong>설 명</strong>');
|
||||
str += m_row_end();
|
||||
str += m_hr();
|
||||
|
||||
for(var i in data) {
|
||||
if (data[i].is_running) {
|
||||
str += m_row_start_color2(0);
|
||||
} else {
|
||||
str += m_row_start(p='0');
|
||||
}
|
||||
|
||||
str += m_col(1, data[i].no);
|
||||
tmp = '<strong>'+data[i].plugin+'</strong><br>' + data[i].id;
|
||||
str += m_col(2, tmp);
|
||||
|
||||
//tmp = ''+''+'' + data[i].make_time + '<br>';
|
||||
//tmp += ''+''+'' + data[i].next_run_time + '<br>';
|
||||
tmp = ''+''+'' + data[i].next_run_time + '<br>';
|
||||
if (data[i].remain_time != '') {
|
||||
tmp += '('+data[i].remain_time+')';
|
||||
}
|
||||
str += m_col(2, tmp);
|
||||
tmp = ''+''+'' + data[i].running_timedelta + '초 / ';
|
||||
tmp += ''+''+'' + data[i].count + '회';
|
||||
str += m_col(1, tmp);
|
||||
|
||||
tmp = ''+''+'' + data[i].interval + ' <br>';
|
||||
str += m_col(2, tmp);
|
||||
tmp = ''+''+'' + ((data[i].is_running) ?'실행중':'대기중') + '';
|
||||
if (data[i].run == false) {
|
||||
tmp += '(F)'
|
||||
}
|
||||
str += m_col(1, tmp);
|
||||
str += m_col(3, data[i].description);
|
||||
str += m_row_end();
|
||||
str += m_hr();
|
||||
}
|
||||
document.getElementById("scheduler_list_div").innerHTML = str;
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
$.ajax({
|
||||
url: '/system/ajax/info',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
make_system(data);
|
||||
}
|
||||
});
|
||||
|
||||
var protocol = window.location.protocol;
|
||||
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/system")
|
||||
socket.on('status', function(data) {
|
||||
make_system(data);
|
||||
make_scheduler_list(data.scheduler);
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#changelog_btn', function(e){
|
||||
e.preventDefault();
|
||||
//window.location.href='/system/information';
|
||||
window.open('https://sjva.me/wiki/public/changelog', "_blank");
|
||||
});
|
||||
|
||||
$("body").on('click', '#recent_version_btn', function(e){
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: '/system/ajax/recent_version',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (data.ret)
|
||||
$.notify('<strong>최신버전 : '+data.version+'</strong>', {type: 'success'});
|
||||
else
|
||||
$.notify('<strong>확인 실패</strong>', {type: 'warning'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function make_system(data) {
|
||||
for(var key in data.system) {
|
||||
if (key == 'auth' && data.system[key].startsWith('인증')==false)
|
||||
document.getElementById(key).innerHTML = '<span style="color:red;font-weight: bold;font-size: xx-large ;">' + data.system[key]+ '</span>';
|
||||
else {
|
||||
if (document.getElementById(key) != null)
|
||||
document.getElementById(key).innerHTML = data.system[key];
|
||||
}
|
||||
tmp = data.system['version']
|
||||
if (data.system['version'] == data.system['recent_version']) {
|
||||
tmp += ' <span style="color:red;font-weight: bold;">(최신버전)</span>';
|
||||
} else {
|
||||
tmp += ' <span style="color:red;font-weight: bold;">(최신버전이 아닙니다. 최신:'+data.system['recent_version']+')</span>';
|
||||
}
|
||||
document.getElementById('version').innerHTML = tmp;
|
||||
}
|
||||
//console.log(document.getElementById('version'))
|
||||
//document.getElementById('version').innerHTML = data.system.version + data.system.recent_version;
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
1
lib/system/templates/system_home_test.html
Normal file
1
lib/system/templates/system_home_test.html
Normal file
@@ -0,0 +1 @@
|
||||
{% extends "base.html" %} {% block content %} hihi {% endblock %}
|
||||
184
lib/system/templates/system_plugin.html
Executable file
184
lib/system/templates/system_plugin.html
Executable file
@@ -0,0 +1,184 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<style type="text/css">
|
||||
.my_hover:hover{
|
||||
background-color: #ffff00;
|
||||
transition: all 0.01s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<nav>
|
||||
{{ macros.m_tab_head_start() }}
|
||||
{{ macros.m_tab_head2('normal', '일반', false) }}
|
||||
{{ macros.m_tab_head2('list', '플러그인 목록', true) }}
|
||||
{{ macros.m_tab_head_end() }}
|
||||
</nav>
|
||||
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
|
||||
{{ macros.m_tab_content_start('normal', false) }}
|
||||
<form id='setting' name='setting'>
|
||||
{{ macros.setting_input_text('plugin_dev_path', '개발용 플러그인 경로', value=arg['plugin_dev_path'], desc=['플러그인을 개발할 때 사용하는 경로'], col='9') }}
|
||||
{{ macros.setting_button([['setting_save', '저장']]) }}
|
||||
|
||||
</form>
|
||||
</form>
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
{{ macros.m_tab_content_start('list', true) }}
|
||||
{{ macros.setting_input_text_and_buttons('plugin_git', '플러그인 수동 설치', [['plugin_install_btn', '설치']], value='https://github.com/', desc=['SJVA.ME 플러그인 게시판에 있는 링크 주소를 입력하세요.']) }}
|
||||
|
||||
{{ macros.m_hr_head_top() }}
|
||||
{{ macros.m_row_start('0') }}
|
||||
{{ macros.m_col(3, macros.m_strong('Name')) }}
|
||||
{{ macros.m_col(1, macros.m_strong('Dev.')) }}
|
||||
{{ macros.m_col(1, macros.m_strong('Category')) }}
|
||||
{{ macros.m_col(1, macros.m_strong('Version')) }}
|
||||
{{ macros.m_col(6, macros.m_strong('Description')) }}
|
||||
{{ macros.m_row_end() }}
|
||||
{{ macros.m_hr_head_bottom() }}
|
||||
<div id="plugin_list_div"></div>
|
||||
{{ macros.m_tab_content_end() }}
|
||||
</div><!--tab-content-->
|
||||
</div> <!--전체-->
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = 'system';
|
||||
var current_data;
|
||||
var install = "{{arg['install']}}";
|
||||
|
||||
$(document).ready(function(){
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/plugin_list',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: {},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
current_data = data
|
||||
make_plugin_list();
|
||||
if (install != '') {
|
||||
$('#plugin_git').val(install);
|
||||
//notify('플러그인이 설치되어 있지 않습니다.', 'danger');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//설정 저장
|
||||
$("#setting_save").click(function(e) {
|
||||
e.preventDefault();
|
||||
var formData = get_formdata('#setting');
|
||||
setting_save_func(formData, true)
|
||||
//
|
||||
});
|
||||
|
||||
|
||||
function setting_save_func(formData, noti) {
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/setting_save',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
success: function (ret) {
|
||||
if (ret) {
|
||||
if (noti) {
|
||||
$.notify('<strong>설정을 저장하였습니다.</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
window.location.href = "/"
|
||||
}
|
||||
} else {
|
||||
$.notify('<strong>설정 저장에 실패하였습니다.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function make_plugin_list() {
|
||||
str = ''
|
||||
console.log(current_data)
|
||||
for (i in current_data) {
|
||||
console.log(i)
|
||||
console.log(current_data[i])
|
||||
str += m_row_start();
|
||||
str += m_col(3, i)
|
||||
|
||||
if (current_data[i].info != null) {
|
||||
str += m_col(1, current_data[i].info.developer);
|
||||
str += m_col(1, current_data[i].info.category);
|
||||
str += m_col(1, current_data[i].info.version);
|
||||
tmp = ''
|
||||
tmp += m_button('plugin_uninstall_btn', '삭제', [{'key':'plugin_name', 'value':current_data[i].info.name}]);
|
||||
if (current_data[i].info.local_info != null) {
|
||||
tmp += m_button('global_link_btn', 'GIT', [{'key':'url', 'value':current_data[i].info.local_info.home}]);
|
||||
if (current_data[i].info.local_info.home != current_data[i].info.local_info.more && current_data[i].info.local_info.more.startsWith('http'))
|
||||
tmp += m_button('global_link_btn', 'MORE', [{'key':'url', 'value':current_data[i].info.local_info.more}]);
|
||||
}
|
||||
|
||||
tmp = m_button_group(tmp)
|
||||
str += m_col(6, current_data[i].info.description +'<br><br>'+ tmp)
|
||||
}
|
||||
str += m_row_end();
|
||||
if (i != current_data.length -1) str += m_hr(0);
|
||||
}
|
||||
document.getElementById("plugin_list_div").innerHTML = str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$("body").on('click', '#plugin_install_btn', function(e){
|
||||
e.preventDefault();
|
||||
plugin_git = document.getElementById("plugin_git").value
|
||||
|
||||
console.log(plugin_git)
|
||||
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/plugin_install',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{"plugin_git": plugin_git},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
t = (data.ret == 'success') ? 'success' : 'warning'
|
||||
$.notify('<strong>'+data.log+'</strong>', {
|
||||
type: t
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("body").on('click', '#plugin_uninstall_btn', function(e){
|
||||
e.preventDefault();
|
||||
plugin_name = $(this).data('plugin_name')
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/plugin_uninstall',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{plugin_name:plugin_name},
|
||||
success: function (data) {
|
||||
if (data == 'success') {
|
||||
$.notify('<strong>재시작시 적용됩니다.</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
$.notify('<strong>실패하였습니다.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
32
lib/system/templates/system_restart.html
Executable file
32
lib/system/templates/system_restart.html
Executable file
@@ -0,0 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div>
|
||||
<h4>
|
||||
{% if sub == 'restart' %}
|
||||
시스템 재시작 중입니다. <br>
|
||||
완료시 이전 페이지로 이동합니다.
|
||||
|
||||
{% elif sub == 'shutdown' %}
|
||||
시스템이 종료되었습니다.
|
||||
{% endif %}
|
||||
</h4>
|
||||
</div>
|
||||
{% if sub == 'restart' %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
var referer = "{{referer}}"
|
||||
$('#loading').show();
|
||||
setTimeout(function(){
|
||||
// 1초 후 작동해야할 코드
|
||||
}, 2000);
|
||||
|
||||
var protocol = window.location.protocol;
|
||||
var socket2 = io.connect(protocol + "//" + document.domain + ":" + location.port + "/system_restart");
|
||||
socket2.on('on_connect', function(data){
|
||||
//window.location.href = protocol + "//" + document.domain + ":" + location.port;
|
||||
window.location.href = referer;
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
85
lib/system/templates/system_setting_auth.html
Executable file
85
lib/system/templates/system_setting_auth.html
Executable file
@@ -0,0 +1,85 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div>
|
||||
{{ macros.m_button_group([['global_setting_save_btn', '설정 저장']])}}
|
||||
{{ macros.m_row_start('5') }}
|
||||
{{ macros.m_row_end() }}
|
||||
<nav>
|
||||
{{ macros.m_tab_head_start() }}
|
||||
{{ macros.m_tab_head2('normal', '웹 로그인', true) }}
|
||||
{{ macros.m_tab_head2('api', 'API & 홈페이지 인증', false) }}
|
||||
{{ macros.m_tab_head_end() }}
|
||||
</nav>
|
||||
<form id='setting' name='setting'>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
{{ macros.m_tab_content_start('normal', true) }}
|
||||
{{ macros.setting_checkbox('use_login', '로그인 사용', value=arg['use_login']) }}
|
||||
<div id="use_login_div" class="collapse">
|
||||
{{ macros.setting_input_text('id', '로그인 ID', value=arg['id'], desc=['초기값은 sjva입니다.'], col='3') }}
|
||||
{{ macros.setting_input_text('pw', '로그인 암호', value=arg['pw'], col='3', type='password', desc=['초기값은 sjva입니다.']) }}
|
||||
{{ macros.setting_checkbox('hide_menu', '로그인 화면 메뉴 표시 안함', value=arg['hide_menu']) }}
|
||||
</div>
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
{{ macros.m_tab_content_start('api', false) }}
|
||||
{{ macros.setting_checkbox('auth_use_apikey', 'APIKEY 사용', value=arg['auth_use_apikey'], desc=['On : 모든 API 요청시 apikey 값을 입력해야합니다.', '없거나 틀릴 경우 에러코드 403리턴']) }}
|
||||
{{ macros.setting_input_text_and_buttons('auth_apikey', 'APIKEY', [['generate_btn', '자동생성']], col='4', value=arg['auth_apikey']) }}
|
||||
{{ macros.m_hr() }}
|
||||
{{ macros.setting_input_text('sjva_me_user_id', 'sjva.me 홈페이지 ID', value=arg['sjva_me_user_id'], desc=['ID변경, APIKEY 변경시 재인증해야합니다.'], col='3') }}
|
||||
{{ macros.info_text('sjva_id', 'SJVA ID', value=arg['sjva_id'], desc=['SJVA별로 자동 생성되는 값입니다. 수정불가'] ) }}
|
||||
{{ macros.info_text_and_buttons('auth_result', '인증상태', [['do_auth_btn', '인증하기']], value=arg['auth_result']['desc'], desc=['APIKEY, 홈페이지ID 값을 저장한 후에 시도하세요.']) }}
|
||||
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
</div><!--tab-content-->
|
||||
</form>
|
||||
</div> <!--전체-->
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = "{{arg['package_name']}}";
|
||||
var sub = "{{arg['sub'] }}";
|
||||
|
||||
$(document).ready(function(){
|
||||
use_collapse("use_login");
|
||||
use_collapse("auth_use_apikey");
|
||||
});
|
||||
|
||||
$('#use_login').change(function() {
|
||||
use_collapse('use_login');
|
||||
});
|
||||
|
||||
$("body").on('click', '#generate_btn', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/'+sub+'/apikey_generate',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: {},
|
||||
dataType: "json",
|
||||
success: function (ret) {
|
||||
document.getElementById("auth_apikey").value = ret
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#do_auth_btn', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/'+sub+'/do_auth',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: {},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (data.ret) {
|
||||
$.notify('<strong>인증되었습니다. 재시작하세요.<br>'+data.msg+'</strong><br>', {type: 'success'});
|
||||
} else {
|
||||
$.notify('<strong>인증에 실패하였습니다.<br>'+data.msg+'</strong><br>', {type: 'warning'});
|
||||
$.notify('<strong>설정을 먼저 저장하고 시도하세요.</strong><br>', {type: 'warning'});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
420
lib/system/templates/system_setting_basic.html
Executable file
420
lib/system/templates/system_setting_basic.html
Executable file
@@ -0,0 +1,420 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<style type="text/css">
|
||||
.my_hover:hover{
|
||||
background-color: #ffff00;
|
||||
transition: all 0.01s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<nav>
|
||||
{{ macros.m_tab_head_start() }}
|
||||
{{ macros.m_tab_head2('normal', '일반', true) }}
|
||||
{{ macros.m_tab_head2('web', '웹', false) }}
|
||||
{{ macros.m_tab_head2('menu', '메뉴', false) }}
|
||||
{{ macros.m_tab_head2('link', '링크', false) }}
|
||||
{{ macros.m_tab_head2('download', '다운로드', false) }}
|
||||
{{ macros.m_tab_head_end() }}
|
||||
</nav>
|
||||
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
{{ macros.m_tab_content_start('normal', true) }}
|
||||
<form id='setting' name='setting'>
|
||||
{{ macros.setting_input_int('port', 'Port', value=arg['port'], min='1', placeholder='Port', desc=['포트 번호입니다.', '네이티브 설치 혹은 도커 네트워크 타입이 호스트일 경우 반영됩니다.', '도커 브릿지 모드인 경우는 docker run -p 옵션에서 변경하시기 바랍니다.', '경고 : -p 브릿지 모드로 사용중 일 경우 9999번을 절대 변경하지 마세요.']) }}
|
||||
{{ macros.setting_input_text_and_buttons('ddns', 'DDNS', [['ddns_test_btn', '테스트']], value=arg['ddns'], desc=['외부에서 접근시 사용할 DDNS. http:// 나 https:// 로 시작해야합니다.', 'RSS, Plex Callback, KLive 등에서 URL생성시 사용합니다.', '테스트 버튼 클릭 후 버전을 확인 할 수 있어야 합니다.']) }}
|
||||
{{ macros.setting_input_text('auto_restart_hour', '자동 재시작 시간', value=arg['auto_restart_hour'], col='3', desc=['자동 재시작 간격(시간단위)이나 Cron 설정을 입력합니다.', '0이면 재시작 안함.']) }}
|
||||
{{ macros.setting_select('log_level', '로그 레벨', [['10', 'DEBUG'],['20', 'INFO'],['30', 'WARNING'],['40', 'ERROR'], ['50', 'CRITICAL'] ], value=arg['log_level'], col='3') }}
|
||||
{{ macros.setting_button([['setting_save', '저장']]) }}
|
||||
</form>
|
||||
</form>
|
||||
{{ macros.m_hr() }}
|
||||
{{ macros.setting_input_text_and_buttons('command_text', 'Command', [['command_run_btn', 'Run']], value='', desc='') }}
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
|
||||
{{ macros.m_tab_content_start('web', false) }}
|
||||
<form id='setting2' name='setting2'>
|
||||
{{ macros.setting_select('theme', '테마 선택', [['Default','Default'], ['Cerulean','Cerulean'], ['Cosmo','Cosmo'], ['Cyborg','Cyborg'], ['Darkly','Darkly'], ['Flatly','Flatly'], ['Journal','Journal'], ['Litera','Litera'], ['Lumen','Lumen'], ['Lux','Lux'], ['Materia','Materia'], ['Minty','Minty'], ['Morph','Morph'],['Pulse','Pulse'], ['Quartz','Quartz'], ['Sandstone','Sandstone'], ['Simplex','Simplex'], ['Sketchy','Sketchy'], ['Slate','Slate'], ['Solar','Solar'], ['Spacelab','Spacelab'], ['Superhero','Superhero'], ['United','United'], ['Vapor','Vapor'], ['Yeti','Yeti'], ['Zephyr','Zephyr']], value=arg['theme'], desc=['https://bootswatch.com'], col='6') }}
|
||||
{{ macros.setting_input_text('web_title', '웹 타이틀', value=arg['web_title']) }}
|
||||
{{ macros.setting_button([['setting_save2', '저장']]) }}
|
||||
</form>
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
|
||||
{{ macros.m_tab_content_start('menu', false) }}
|
||||
<form id='setting3' name='setting3'>
|
||||
{% if arg['use_category_vod'] == 'True' %}
|
||||
{{ macros.m_hr() }}
|
||||
{{ macros.setting_button_with_info([['menu_toggle_btn', 'Toggle', [{'key':'category', 'value':'vod'}]]], left='VOD', desc=None) }}
|
||||
<div id="menu_vod_div" class="collapse">
|
||||
{{ macros.setting_checkbox('use_plugin_ffmpeg', 'FFMPEG', value=arg['use_plugin_ffmpeg']) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if arg['use_category_file_process'] == 'True' %}
|
||||
{{ macros.m_hr() }}
|
||||
{{ macros.setting_button_with_info([['menu_toggle_btn', 'Toggle', [{'key':'category', 'value':'file_process'}]]], left='파일처리', desc=None) }}
|
||||
<div id="menu_file_process_div" class="collapse">
|
||||
{{ macros.setting_checkbox('use_plugin_ktv', '국내TV', value=arg['use_plugin_ktv']) }}
|
||||
{{ macros.setting_checkbox('use_plugin_fileprocess_movie', '영화', value=arg['use_plugin_fileprocess_movie']) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if arg['use_category_plex'] == 'True' %}
|
||||
{{ macros.m_hr() }}
|
||||
{{ macros.setting_button_with_info([['menu_toggle_btn', 'Toggle', [{'key':'category', 'value':'plex'}]]], left='PLEX', desc=None) }}
|
||||
<div id="menu_plex_div" class="collapse">
|
||||
{{ macros.setting_checkbox('use_plugin_plex', 'PLEX', value=arg['use_plugin_plex']) }}
|
||||
{{ macros.setting_checkbox('use_plugin_gdrive_scan', 'GDrive 스캔', value=arg['use_plugin_gdrive_scan']) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if arg['use_category_tool'] == 'True' %}
|
||||
{{ macros.m_hr() }}
|
||||
{{ macros.setting_button_with_info([['menu_toggle_btn', 'Toggle', [{'key':'category', 'value':'tool'}]]], left='툴', desc=None) }}
|
||||
<div id="menu_tool_div" class="collapse">
|
||||
{{ macros.setting_checkbox('use_plugin_rclone', 'RClone', value=arg['use_plugin_rclone']) }}
|
||||
{{ macros.setting_checkbox('use_plugin_daum_tv', 'Daum TV', value=arg['use_plugin_daum_tv']) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ macros.setting_button([['setting_save3', '저장']]) }}
|
||||
</form>
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
|
||||
{{ macros.m_tab_content_start('link', false) }}
|
||||
{{ macros.m_button_group([['link_add_btn', '추가'], ['link_add_divider_btn', 'Divider Line 추가'], ['link_save_btn', '저장'], ['link_reset_btn', '초기화']])}}
|
||||
{{ macros.m_row_start('0') }}
|
||||
{{ macros.m_row_end() }}
|
||||
|
||||
{{ macros.m_hr_head_top() }}
|
||||
{{ macros.m_row_start('0') }}
|
||||
{{ macros.m_col(1, macros.m_strong('Idx')) }}
|
||||
{{ macros.m_col(4, macros.m_strong('Title')) }}
|
||||
{{ macros.m_col(4, macros.m_strong('URL')) }}
|
||||
{{ macros.m_col(3, macros.m_strong('Action')) }}
|
||||
{{ macros.m_row_end() }}
|
||||
{{ macros.m_hr_head_bottom() }}
|
||||
<form id="link_form" name="link_form">
|
||||
<div id="link_list_div"></div>
|
||||
</form>
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
{{ macros.m_tab_content_start('download', false) }}
|
||||
{{ macros.setting_button_with_info([['global_link_btn', '다운로드', [{'key':'url', 'value':'https://github.com/soju6jan/soju6jan.github.io/blob/master/etc/hdhomerun_scan_191214.zip'}]], ['global_link_btn', '매뉴얼', [{'key':'url', 'value':'.'}]]], left='HDHomerun Scan Tool', desc=['HDHomerun 스캔하여 TVH용 프리셋 파일을 만들어주는 Windows용 프로그램', '8VSB 지원 케이블용']) }}
|
||||
<!--
|
||||
{{ macros.setting_button_with_info([['global_link_btn', '다운로드', [{'key':'url', 'value':'https://github.com/soju6jan/soju6jan.github.io/raw/master/etc/sjva_lc_0.1.1.apk'}]], ['global_link_btn', '매뉴얼', [{'key':'url', 'value':'.'}]]], left='SJVA for Live Channels', desc=['Android TV Live Channels 앱에 채널 소스를 제공하는 앱.', 'Klive, Plex 지원']) }}
|
||||
{{ macros.setting_button_with_info([['global_link_btn', '티빙 애드온', [{'key':'url', 'value':'https://github.com/soju6jan/soju6jan.github.io/blob/master/kodi_plugin/plugin.video.tving.zip'}]]], left='KODI', desc=None) }}
|
||||
-->
|
||||
{{ macros.m_tab_content_end() }}
|
||||
|
||||
</div><!--tab-content-->
|
||||
</div> <!--전체-->
|
||||
|
||||
<!-- 링크 모달 -->
|
||||
{{ macros.m_modal_start('link_edit_modal', '링크', 'modal-lg') }}
|
||||
<form id="link_form">
|
||||
<input type="hidden" id="link_edit_index" name="link_edit_index"/>
|
||||
{{ macros.setting_input_text('link_edit_title', '제목') }}
|
||||
{{ macros.setting_input_text('link_edit_url', 'URL') }}
|
||||
{{ macros.setting_button([['link_edit_confirm_btn', '확인'], ['link_edit_cancel_btn', '취소']]) }}
|
||||
</form>
|
||||
{{ macros.m_modal_end() }}
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = 'system';
|
||||
var current_data;
|
||||
var link_data;
|
||||
|
||||
$(document).ready(function(){
|
||||
$(function() {
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/get_link_list',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: {},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
link_data = data
|
||||
make_link_data();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function setting_save_func(formData, noti) {
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/setting_save_system',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
success: function (ret) {
|
||||
if (ret) {
|
||||
if (noti) {
|
||||
$.notify('<strong>설정을 저장하였습니다.</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
window.location.href = "/"
|
||||
}
|
||||
} else {
|
||||
$.notify('<strong>설정 저장에 실패하였습니다.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//설정 저장
|
||||
$("#setting_save").click(function(e) {
|
||||
e.preventDefault();
|
||||
var formData = get_formdata('#setting');
|
||||
setting_save_func(formData, true)
|
||||
});
|
||||
|
||||
//설정 저장
|
||||
$("#setting_save2").click(function(e) {
|
||||
e.preventDefault();
|
||||
var formData = get_formdata('#setting2');
|
||||
setting_save_func(formData, false)
|
||||
});
|
||||
|
||||
$("#setting_save4").click(function(e) {
|
||||
e.preventDefault();
|
||||
var formData = get_formdata('#setting4');
|
||||
setting_save_func(formData, true)
|
||||
});
|
||||
|
||||
$("#setting_save3").click(function(e) {
|
||||
e.preventDefault();
|
||||
var formData = get_formdata('#setting3');
|
||||
setting_save_func(formData, true)
|
||||
$.notify('<strong>재시작해야 적용됩니다.</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("body").on('click', '#ddns_test_btn', function(e){
|
||||
e.preventDefault();
|
||||
ddns = document.getElementById('ddns').value;
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/ddns_test',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ddns:ddns},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
console.log(data)
|
||||
if (data == 'fail') {
|
||||
$.notify('<strong>접속에 실패하였습니다.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
} else {
|
||||
$.notify('<strong>Version:'+ data+'</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$("body").on('click', '#menu_toggle_btn', function(e){
|
||||
e.preventDefault();
|
||||
category = $(this).data('category')
|
||||
var div_name = '#menu_'+category+'_div'
|
||||
$(div_name).collapse('toggle')
|
||||
});
|
||||
|
||||
$("body").on('click', '#command_run_btn', function(e){
|
||||
e.preventDefault();
|
||||
command_text = document.getElementById('command_text').value;
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/command_run',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{command_text:command_text},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (data.ret == 'success') {
|
||||
$.notify('<strong>성공 : '+ data.log +'</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
$.notify('<strong>실패 : ' + data.log+'</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// 링크
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 화면 상단 버튼 START
|
||||
$("body").on('click', '#link_add_btn', function(e){
|
||||
e.preventDefault();
|
||||
document.getElementById("link_edit_index").value = -1;
|
||||
document.getElementById('link_edit_title').value = '';
|
||||
document.getElementById('link_edit_url').value = '';
|
||||
$('#link_edit_modal').modal();
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_add_divider_btn', function(e){
|
||||
e.preventDefault();
|
||||
tmp = {}
|
||||
tmp['type'] = 'divider'
|
||||
link_data.splice(link_data.length, 0, tmp);
|
||||
make_link_data()
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_save_btn', function(e){
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/link_save',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{link_data:JSON.stringify(link_data)},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (data) {
|
||||
$.notify('<strong>저장 후 적용하였습니다.</strong>', {
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
$.notify('<strong>실패하였습니다.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_reset_btn', function(e){
|
||||
e.preventDefault();
|
||||
link_data = []
|
||||
make_link_data()
|
||||
});
|
||||
// 화면 상단 버튼 END
|
||||
|
||||
// 리스트 각 항목 별 버튼 START
|
||||
$("body").on('click', '#link_item_up_btn', function(e){
|
||||
e.preventDefault();
|
||||
target_id = $(this).data('index')
|
||||
target = link_data[target_id]
|
||||
if (target_id != 0) {
|
||||
link_data.splice(target_id, 1);
|
||||
link_data.splice(target_id-1, 0, target);
|
||||
}
|
||||
make_link_data()
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_item_down_btn', function(e){
|
||||
e.preventDefault();
|
||||
target_id = $(this).data('index')
|
||||
target = link_data[target_id]
|
||||
if (link_data.length -1 != target_id) {
|
||||
link_data.splice(target_id, 1);
|
||||
link_data.splice(target_id+1, 0, target);
|
||||
}
|
||||
make_link_data()
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_item_delete_btn', function(e){
|
||||
e.preventDefault();
|
||||
target_id = $(this).data('index')
|
||||
target = link_data[target_id]
|
||||
link_data.splice(target_id, 1);
|
||||
make_link_data()
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_item_edit_btn', function(e){
|
||||
e.preventDefault();
|
||||
target_id = $(this).data('index')
|
||||
target = link_data[target_id]
|
||||
document.getElementById('link_edit_index').value = target_id
|
||||
document.getElementById('link_edit_title').value = target.title
|
||||
document.getElementById('link_edit_url').value = target.url
|
||||
$('#link_edit_modal').modal();
|
||||
});
|
||||
// 리스트 각 항목 별 버튼 END
|
||||
|
||||
// START 모달 버튼
|
||||
$("body").on('click', '#link_edit_confirm_btn', function(e){
|
||||
e.preventDefault();
|
||||
edit_index = parseInt(document.getElementById('link_edit_index').value)
|
||||
tmp = {}
|
||||
tmp['type'] = 'link'
|
||||
tmp['title'] = document.getElementById('link_edit_title').value
|
||||
tmp['url'] = document.getElementById('link_edit_url').value
|
||||
if (edit_index == -1) {
|
||||
link_data.splice(link_data.length, 0, tmp);
|
||||
} else {
|
||||
link_data.splice(target_id, 1);
|
||||
link_data.splice(target_id, 0, tmp);
|
||||
}
|
||||
make_link_data()
|
||||
$('#link_edit_modal').modal('hide');
|
||||
});
|
||||
|
||||
$("body").on('click', '#link_edit_cancel_btn', function(e){
|
||||
e.preventDefault();
|
||||
$('#link_edit_modal').modal('hide');
|
||||
});
|
||||
// END 모달 버튼
|
||||
|
||||
|
||||
|
||||
function make_link_data() {
|
||||
str = ''
|
||||
for (i in link_data) {
|
||||
//console.log(link_data[i])
|
||||
str += m_row_start_hover();
|
||||
str += m_col(1, parseInt(i)+1);
|
||||
|
||||
if (link_data[i].type == 'link') {
|
||||
str += m_col(4, link_data[i].title)
|
||||
str += m_col(4, link_data[i].url)
|
||||
} else {
|
||||
str += m_col(8, '---Divider Line---')
|
||||
}
|
||||
tmp = ''
|
||||
tmp += m_button('link_item_up_btn', 'UP', [{'key':'index', 'value':i}]);
|
||||
tmp += m_button('link_item_down_btn', 'DOWN', [{'key':'index', 'value':i}]);
|
||||
tmp += m_button('link_item_delete_btn', '삭제', [{'key':'index', 'value':i}]);
|
||||
if (link_data[i].type == 'link') {
|
||||
tmp += m_button('link_item_edit_btn', '편집', [{'key':'index', 'value':i}]);
|
||||
tmp += m_button('global_link_btn', 'Go', [{'key':'url', 'value':link_data[i].url}]);
|
||||
}
|
||||
tmp = m_button_group(tmp)
|
||||
str += m_col(3, tmp)
|
||||
str += m_row_end();
|
||||
if (i != link_data.length -1) str += m_hr(0);
|
||||
}
|
||||
document.getElementById("link_list_div").innerHTML = str;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
$("body").on('click', '#go_filebrowser_btn', function(e){
|
||||
e.preventDefault();
|
||||
url = document.getElementById('url_filebrowser').value
|
||||
window.open(url, "_blank");
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
11
lib/tool_base/__init__.py
Normal file
11
lib/tool_base/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from framework import logger
|
||||
from .subprocess import ToolSubprocess
|
||||
|
||||
|
||||
def d(data):
|
||||
if type(data) in [type({}), type([])]:
|
||||
import json
|
||||
|
||||
return "\n" + json.dumps(data, indent=4, ensure_ascii=False)
|
||||
else:
|
||||
return str(data)
|
||||
BIN
lib/tool_base/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
lib/tool_base/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib/tool_base/__pycache__/subprocess.cpython-310.pyc
Normal file
BIN
lib/tool_base/__pycache__/subprocess.cpython-310.pyc
Normal file
Binary file not shown.
75
lib/tool_base/subprocess.py
Normal file
75
lib/tool_base/subprocess.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
import os, sys, traceback, subprocess, json, platform
|
||||
from framework import app, logger
|
||||
|
||||
|
||||
class ToolSubprocess(object):
|
||||
@classmethod
|
||||
def execute_command_return(
|
||||
cls, command, format=None, force_log=False, shell=False, env=None
|
||||
):
|
||||
try:
|
||||
# pass
|
||||
# logger.debug('execute_command_return : %s', ' '.join(command))
|
||||
if app.config["config"]["running_type"] == "windows":
|
||||
tmp = []
|
||||
if type(command) == type([]):
|
||||
for x in command:
|
||||
if x.find(" ") == -1:
|
||||
tmp.append(x)
|
||||
else:
|
||||
tmp.append(f'"{x}"')
|
||||
command = " ".join(tmp)
|
||||
|
||||
iter_arg = b"" if app.config["config"]["is_py2"] else ""
|
||||
if app.config["config"]["is_py2"]:
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True,
|
||||
bufsize=1,
|
||||
shell=shell,
|
||||
env=env,
|
||||
)
|
||||
else:
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True,
|
||||
shell=shell,
|
||||
env=env,
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
# process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8')
|
||||
ret = []
|
||||
with process.stdout:
|
||||
for line in iter(process.stdout.readline, iter_arg):
|
||||
ret.append(line.strip())
|
||||
if force_log:
|
||||
logger.debug(ret[-1])
|
||||
process.wait() # wait for the subprocess to exit
|
||||
|
||||
if format is None:
|
||||
ret2 = "\n".join(ret)
|
||||
elif format == "json":
|
||||
try:
|
||||
index = 0
|
||||
for idx, tmp in enumerate(ret):
|
||||
# logger.debug(tmp)
|
||||
if tmp.startswith("{") or tmp.startswith("["):
|
||||
index = idx
|
||||
break
|
||||
ret2 = json.loads("".join(ret[index:]))
|
||||
except Exception:
|
||||
ret2 = None
|
||||
|
||||
return ret2
|
||||
except Exception as exception:
|
||||
logger.error("Exception:%s", exception)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error("command : %s", command)
|
||||
Reference in New Issue
Block a user