first commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
.git/
|
||||
BIN
__pycache__/gommi.cpython-310.pyc
Normal file
BIN
__pycache__/gommi.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/gommi_server.cpython-39.pyc
Normal file
BIN
__pycache__/gommi_server.cpython-39.pyc
Normal file
Binary file not shown.
BIN
data/.DS_Store
vendored
Normal file
BIN
data/.DS_Store
vendored
Normal file
Binary file not shown.
1
data/custom/linkkf-yommi
Submodule
1
data/custom/linkkf-yommi
Submodule
Submodule data/custom/linkkf-yommi added at 82657bf602
BIN
data/db/.DS_Store
vendored
Normal file
BIN
data/db/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
data/db/gommi.db
Normal file
BIN
data/db/gommi.db
Normal file
Binary file not shown.
2819
data/log/framework.log
Normal file
2819
data/log/framework.log
Normal file
File diff suppressed because it is too large
Load Diff
1515
data/log/system.log
Normal file
1515
data/log/system.log
Normal file
File diff suppressed because it is too large
Load Diff
135
gommi.py
Normal file
135
gommi.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import os, sys, platform, traceback # noqa F401
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib")
|
||||
)
|
||||
sys.path.insert(
|
||||
1, os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib2")
|
||||
)
|
||||
|
||||
print(platform.system())
|
||||
if platform.system() == "Linux":
|
||||
if (
|
||||
(
|
||||
platform.platform().find("86") == -1
|
||||
and platform.platform().find("64") == -1
|
||||
)
|
||||
or platform.platform().find("arch") != -1
|
||||
or platform.platform().find("arm") != -1
|
||||
):
|
||||
sys.path.insert(
|
||||
2,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"lib",
|
||||
"sc",
|
||||
"LinuxArm",
|
||||
),
|
||||
)
|
||||
else:
|
||||
sys.path.insert(
|
||||
2,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "lib", "sc", "Linux"
|
||||
),
|
||||
)
|
||||
if platform.system() == "Windows":
|
||||
sys.path.insert(
|
||||
2,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "lib", "sc", "Windows"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
print("[GOMMI] sys.path : %s" % sys.path)
|
||||
print("[GOMMI] sys.argv : %s" % sys.argv)
|
||||
|
||||
|
||||
# prepare starting
|
||||
def prepare_starting():
|
||||
try:
|
||||
from gevent import monkey
|
||||
|
||||
monkey.patch_all()
|
||||
print("[GOMMI] gevent monkey patch!!")
|
||||
sys.getfilesystemencoding = lambda: "UTF-8"
|
||||
except Exception:
|
||||
print("[GOMMI] gevent not installed!!")
|
||||
|
||||
|
||||
def start_app():
|
||||
import framework
|
||||
|
||||
app = framework.app
|
||||
app.jinja_env.auto_reload = True
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
|
||||
for i in range(10):
|
||||
try:
|
||||
framework.socketio.run(
|
||||
app, host="0.0.0.0", port=app.config["config"]["port"]
|
||||
)
|
||||
print("EXIT CODE : %s" % framework.exit_code)
|
||||
# 2021-05-18
|
||||
if app.config["config"]["running_type"] in ["termux", "entware"]:
|
||||
os._exit(framework.exit_code)
|
||||
else:
|
||||
if framework.exit_code != -1:
|
||||
sys.exit(framework.exit_code)
|
||||
else:
|
||||
print("framework.exit_code is -1")
|
||||
break
|
||||
except Exception as exception:
|
||||
print(str(exception))
|
||||
import time
|
||||
|
||||
time.sleep(10 * i)
|
||||
continue
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt !!")
|
||||
print("start_app() end")
|
||||
|
||||
|
||||
def process_args():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
help="If this value is set, ignore the DB value and use it",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use_gevent",
|
||||
choices=["true", "false"],
|
||||
default="true",
|
||||
help="If true, use gevent. (WCGI, scheduler, socketio)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use_celery",
|
||||
choices=["true", "false"],
|
||||
default="true",
|
||||
help="If true, use celery.\nThis value is set to False by force on Windows10",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repeat",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Do not set. This value is set by automatic",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
args.use_gevent = args.use_gevent == "true"
|
||||
args.use_celery = args.use_celery == "true"
|
||||
print("[GOMMI] args : %s" % args)
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
process_args()
|
||||
prepare_starting()
|
||||
start_app()
|
||||
except Exception as exception:
|
||||
print(str(exception))
|
||||
print(traceback.format_exc())
|
||||
7
gommi_server.py
Normal file
7
gommi_server.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return {"message": "Hello World"}
|
||||
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)
|
||||
3
lib2/support/__init__.py
Normal file
3
lib2/support/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .logger import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
BIN
lib2/support/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
lib2/support/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
lib2/support/__pycache__/logger.cpython-310.pyc
Normal file
BIN
lib2/support/__pycache__/logger.cpython-310.pyc
Normal file
Binary file not shown.
86
lib2/support/logger.py
Normal file
86
lib2/support/logger.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import os, sys, logging, logging.handlers # noqa F401
|
||||
from datetime import datetime
|
||||
from pytz import timezone, utc
|
||||
|
||||
"""
|
||||
ConsoleColor.Black => "\x1B[30m",
|
||||
ConsoleColor.DarkRed => "\x1B[31m",
|
||||
ConsoleColor.DarkGreen => "\x1B[32m",
|
||||
ConsoleColor.DarkYellow => "\x1B[33m",
|
||||
ConsoleColor.DarkBlue => "\x1B[34m",
|
||||
ConsoleColor.DarkMagenta => "\x1B[35m",
|
||||
ConsoleColor.DarkCyan => "\x1B[36m",
|
||||
ConsoleColor.Gray => "\x1B[37m",
|
||||
ConsoleColor.Red => "\x1B[1m\x1B[31m",
|
||||
ConsoleColor.Green => "\x1B[1m\x1B[32m",
|
||||
ConsoleColor.Yellow => "\x1B[1m\x1B[33m",
|
||||
ConsoleColor.Blue => "\x1B[1m\x1B[34m",
|
||||
ConsoleColor.Magenta => "\x1B[1m\x1B[35m",
|
||||
ConsoleColor.Cyan => "\x1B[1m\x1B[36m",
|
||||
ConsoleColor.White => "\x1B[1m\x1B[37m",
|
||||
"""
|
||||
|
||||
|
||||
class CustomFormatter(logging.Formatter):
|
||||
"""Logging Formatter to add colors and count warning / errors"""
|
||||
|
||||
grey = "\x1b[38;21m"
|
||||
yellow = "\x1b[33;21m"
|
||||
red = "\x1b[31;21m"
|
||||
bold_red = "\x1b[31;1m"
|
||||
reset = "\x1b[0m"
|
||||
green = "\x1B[32m"
|
||||
# format = "[%(asctime)s|%(name)s|%(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
|
||||
format = "[%(asctime)s|%(name)s %(pathname)s:%(lineno)s] %(message)s"
|
||||
|
||||
FORMATS = {
|
||||
logging.DEBUG: grey + format + reset,
|
||||
logging.INFO: green + format + reset,
|
||||
logging.WARNING: yellow + format + reset,
|
||||
logging.ERROR: red + format + reset,
|
||||
logging.CRITICAL: bold_red + format + reset,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
formatter = logging.Formatter(log_fmt)
|
||||
return formatter.format(record)
|
||||
|
||||
|
||||
def get_logger(name=None, log_path=None):
|
||||
if name is None:
|
||||
name = sys.argv[0].rsplit(".", 1)[0]
|
||||
logger = logging.getLogger(name)
|
||||
if not logger.handlers:
|
||||
level = logging.DEBUG
|
||||
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
|
||||
if log_path is None:
|
||||
log_path = os.path.join(os.getcwd(), "tmp")
|
||||
os.makedirs(log_path, exist_ok=True)
|
||||
fileHandler = logging.handlers.RotatingFileHandler(
|
||||
filename=os.path.join(log_path, f"{name}.log"),
|
||||
maxBytes=file_max_bytes,
|
||||
backupCount=5,
|
||||
encoding="utf8",
|
||||
delay=True,
|
||||
)
|
||||
streamHandler = logging.StreamHandler()
|
||||
|
||||
fileHandler.setFormatter(formatter)
|
||||
streamHandler.setFormatter(CustomFormatter())
|
||||
|
||||
logger.addHandler(fileHandler)
|
||||
logger.addHandler(streamHandler)
|
||||
return logger
|
||||
3
plugin/ffmpeg/__init__.py
Executable file
3
plugin/ffmpeg/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
||||
from .plugin import blueprint, menu, plugin_load, plugin_unload, streaming_kill, get_video_info
|
||||
from .logic import Status, Logic
|
||||
from .interface_program_ffmpeg import Ffmpeg
|
||||
446
plugin/ffmpeg/interface_program_ffmpeg.py
Executable file
446
plugin/ffmpeg/interface_program_ffmpeg.py
Executable file
@@ -0,0 +1,446 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import threading
|
||||
import subprocess
|
||||
import platform
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pytz import timezone
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
|
||||
|
||||
#from flask import Blueprint, request, Response, send_file, render_template, redirect, jsonify
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework.util import Util
|
||||
from framework import app
|
||||
|
||||
# 패키지
|
||||
from ffmpeg.logic import Logic, Status
|
||||
from ffmpeg.model import ModelSetting
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
|
||||
class Ffmpeg(object):
|
||||
instance_list = []
|
||||
idx = 1
|
||||
# retry : 재시도 횟수
|
||||
# max_error_packet_count : 이 숫자 초과시 중단
|
||||
# where : 호출 모듈
|
||||
def __init__(self, url, filename, plugin_id=None, listener=None, max_pf_count=None, call_plugin=None, temp_path=None, save_path=None, proxy=None, headers=None):
|
||||
self.thread = None
|
||||
self.url = url
|
||||
self.filename = filename
|
||||
self.plugin_id = plugin_id
|
||||
self.listener = listener
|
||||
self.max_pf_count = int(ModelSetting.query.filter_by(key='max_pf_count').first().value if max_pf_count is None else max_pf_count)
|
||||
self.call_plugin = call_plugin
|
||||
self.process = None
|
||||
self.temp_path = ModelSetting.query.filter_by(key='temp_path').first().value if temp_path is None else temp_path
|
||||
self.save_path = ModelSetting.query.filter_by(key='save_path').first().value if save_path is None else save_path
|
||||
self.proxy = proxy
|
||||
self.temp_fullpath = os.path.join(self.temp_path, filename)
|
||||
self.save_fullpath = os.path.join(self.save_path, filename)
|
||||
self.log_thread = None
|
||||
self.status = Status.READY
|
||||
self.duration = 0
|
||||
self.duration_str = ''
|
||||
self.current_duration = 0
|
||||
self.percent = 0
|
||||
#self.log = []
|
||||
self.current_pf_count = 0
|
||||
self.idx = str(Ffmpeg.idx)
|
||||
Ffmpeg.idx += 1
|
||||
self.current_bitrate = ''
|
||||
self.current_speed = ''
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.download_time = None
|
||||
self.start_event = threading.Event()
|
||||
self.exist = False
|
||||
self.filesize = 0
|
||||
self.filesize_str = ''
|
||||
self.download_speed = ''
|
||||
self.headers = headers
|
||||
|
||||
Ffmpeg.instance_list.append(self)
|
||||
#logger.debug('Ffmpeg.instance_list LEN:%s', len(Ffmpeg.instance_list))
|
||||
if len(Ffmpeg.instance_list) > 30:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.thread is None and f.status != Status.READY:
|
||||
Ffmpeg.instance_list.remove(f)
|
||||
break
|
||||
else:
|
||||
logger.debug('remove fail %s %s', f.thread, self.status)
|
||||
|
||||
def start(self):
|
||||
self.thread = threading.Thread(target=self.thread_fuction, args=())
|
||||
self.thread.start()
|
||||
#self.start_event.wait(timeout=10)
|
||||
self.start_time = datetime.now()
|
||||
return self.get_data()
|
||||
|
||||
def start_and_wait(self):
|
||||
self.start()
|
||||
self.thread.join(timeout=60*70)
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
self.status = Status.USER_STOP
|
||||
self.kill()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def kill(self):
|
||||
try:
|
||||
if self.process is not None and self.process.poll() is None:
|
||||
import psutil
|
||||
process = psutil.Process(self.process.pid)
|
||||
for proc in process.children(recursive=True):
|
||||
proc.kill()
|
||||
process.kill()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def thread_fuction(self):
|
||||
try:
|
||||
import system
|
||||
user = str(system.ModelSetting.get('sjva_me_user_id'))
|
||||
try:
|
||||
from framework.common.util import AESCipher
|
||||
user = AESCipher.encrypt(user)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
if self.proxy is None:
|
||||
if self.headers is None:
|
||||
command = [ModelSetting.get('ffmpeg_path'), '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-metadata', 'network=%s' % user]
|
||||
else:
|
||||
headers_command = []
|
||||
for key, value in self.headers.items():
|
||||
if key.lower() == 'user-agent':
|
||||
headers_command.append('-user_agent')
|
||||
headers_command.append(value)
|
||||
pass
|
||||
else:
|
||||
headers_command.append('-headers')
|
||||
#headers_command.append('\'%s: "%s"\''%(key,value))
|
||||
if platform.system() == 'Windows':
|
||||
headers_command.append('\'%s:%s\''%(key,value))
|
||||
else:
|
||||
headers_command.append(f'{key}:{value}')
|
||||
command = [ModelSetting.get('ffmpeg_path'), '-y'] + headers_command + ['-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-metadata', 'network=%s' % user]
|
||||
else:
|
||||
command = [ModelSetting.get('ffmpeg_path'), '-y', '-http_proxy', self.proxy, '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-metadata', 'network=%s' % user]
|
||||
#command = [ModelSetting.get('ffmpeg_path'), '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
||||
if platform.system() == 'Windows':
|
||||
#if self.call_plugin.startswith('pooq'):
|
||||
now = str(datetime.now()).replace(':', '').replace('-', '').replace(' ', '-')
|
||||
#filename = ('%s' % now).split('.')[0] + '.mp4'
|
||||
#동시에 요청시 구분이 안됨. 2019-10-11
|
||||
filename = ('%s' % now) + '.mp4'
|
||||
self.temp_fullpath = os.path.join(self.temp_path, filename)
|
||||
command.append(self.temp_fullpath)
|
||||
#else:
|
||||
# command.append(self.temp_fullpath.encode('cp949'))
|
||||
else:
|
||||
command.append(self.temp_fullpath)
|
||||
|
||||
try:
|
||||
logger.debug(' '.join(command))
|
||||
#logger.debug(command)
|
||||
if os.path.exists(self.temp_fullpath):
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.idx != self.idx and f.temp_fullpath == self.temp_fullpath and f.status in [Status.DOWNLOADING, Status.READY]:
|
||||
self.status = Status.ALREADY_DOWNLOADING
|
||||
#logger.debug('temp_fullpath ALREADY_DOWNLOADING')
|
||||
return
|
||||
except:
|
||||
pass
|
||||
#self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1)
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
|
||||
self.status = Status.READY
|
||||
self.log_thread = threading.Thread(target=self.log_thread_fuction, args=())
|
||||
self.log_thread.start()
|
||||
self.start_event.wait(timeout=60)
|
||||
#logger.debug('start_event awake.. ')
|
||||
if self.log_thread is None:
|
||||
#logger.debug('log_thread is none')
|
||||
#logger.debug(self.log)
|
||||
if self.status == Status.READY:
|
||||
self.status = Status.ERROR
|
||||
self.kill()
|
||||
elif self.status == Status.READY:
|
||||
#logger.debug('still status.ready kill')
|
||||
#logger.debug(self.log)
|
||||
self.status = Status.ERROR
|
||||
self.kill()
|
||||
else:
|
||||
#logger.debug('normally process wait()')
|
||||
#process_ret = self.process.wait(timeout=60*60)
|
||||
process_ret = self.process.wait(timeout=60*ModelSetting.get_int('timeout_minute'))
|
||||
#logger.debug('process_ret :%s' % process_ret)
|
||||
if process_ret is None: # timeout
|
||||
if self.status != Status.COMPLETED and self.status != Status.USER_STOP and self.status != Status.PF_STOP:
|
||||
self.status = Status.TIME_OVER
|
||||
self.kill()
|
||||
else:
|
||||
#logger.debug('process end')
|
||||
if self.status == Status.DOWNLOADING:
|
||||
self.status = Status.FORCE_STOP
|
||||
self.end_time = datetime.now()
|
||||
self.download_time = self.end_time - self.start_time
|
||||
try:
|
||||
if self.status == Status.COMPLETED:
|
||||
if self.save_fullpath != self.temp_fullpath:
|
||||
if os.path.exists(self.save_fullpath):
|
||||
os.remove(self.save_fullpath)
|
||||
#if app.config['config']['is_py2']:
|
||||
# os.chmod(self.temp_fullpath, 0777)
|
||||
#else:
|
||||
# os.chmod(self.temp_fullpath, 777)
|
||||
os.system('chmod 777 "%s"' % self.temp_fullpath)
|
||||
# 2020-03-19 씹힌다 ㅠ
|
||||
#import framework.common.celery as celery_task
|
||||
#celery_task.move(self.temp_fullpath, self.save_fullpath)
|
||||
import shutil
|
||||
shutil.move(self.temp_fullpath, self.save_fullpath)
|
||||
|
||||
# 2019-05-16
|
||||
#os.chmod(self.save_fullpath, 0777)
|
||||
self.filesize = os.stat(self.save_fullpath).st_size
|
||||
else:
|
||||
if os.path.exists(self.temp_fullpath):
|
||||
os.remove(self.temp_fullpath)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
self.process = None
|
||||
self.thread = None
|
||||
|
||||
#파일처리
|
||||
|
||||
#logger.debug('ffmpeg thread end')
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
self.status = Status.EXCEPTION
|
||||
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
except:
|
||||
pass
|
||||
#self.start_event.set()
|
||||
|
||||
|
||||
def log_thread_fuction(self):
|
||||
with self.process.stdout:
|
||||
iter_arg = b'' if app.config['config']['is_py2'] else ''
|
||||
for line in iter(self.process.stdout.readline, iter_arg):
|
||||
try:
|
||||
#logger.debug(line)
|
||||
#logger.debug('XXXXXXXXXXXXXX %s', self.status)
|
||||
#self.log.append(line.strip())
|
||||
#arg = {'type':'log', 'status':self.status, 'data' : {'idx':self.idx, 'line':line}}
|
||||
#self.send_to_listener(**arg)
|
||||
if self.status == Status.READY:
|
||||
if line.find('Server returned 404 Not Found') != -1 or line.find('Unknown error') != -1:
|
||||
self.status = Status.WRONG_URL
|
||||
self.start_event.set()
|
||||
#logger.debug('start_event set by 404 not found')
|
||||
elif line.find('No such file or directory') != -1:
|
||||
self.status = Status.WRONG_DIRECTORY
|
||||
self.start_event.set()
|
||||
#logger.debug('start_event set by WRONG_DIR')
|
||||
else:
|
||||
match = re.compile(r'Duration\:\s(\d{2})\:(\d{2})\:(\d{2})\.(\d{2})\,\sstart').search(line)
|
||||
if match:
|
||||
self.duration_str = '%s:%s:%s' % ( match.group(1), match.group(2), match.group(3))
|
||||
self.duration = int(match.group(4))
|
||||
self.duration += int(match.group(3)) * 100
|
||||
self.duration += int(match.group(2)) * 100 * 60
|
||||
self.duration += int(match.group(1)) * 100 * 60 * 60
|
||||
#logger.debug('Duration : %s', self.duration)
|
||||
if match:
|
||||
self.status = Status.READY
|
||||
arg = {'type':'status_change', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
continue
|
||||
#if self.listener is not None:
|
||||
# #arg = {'status':self.status, 'duration' : self.duration}
|
||||
# arg = {'status':self.status, 'data' : self.get_data()}
|
||||
# self.listener(**arg)
|
||||
#
|
||||
#self.status = 'DOWNLOADING'
|
||||
match = re.compile(r'time\=(\d{2})\:(\d{2})\:(\d{2})\.(\d{2})\sbitrate\=\s*(?P<bitrate>\d+).*?[$|\s](\s?speed\=\s*(?P<speed>.*?)x)?').search(line)
|
||||
if match:
|
||||
self.status = Status.DOWNLOADING
|
||||
arg = {'type':'status_change', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
self.start_event.set()
|
||||
#logger.debug('start_event set by DOWNLOADING')
|
||||
elif self.status == Status.DOWNLOADING:
|
||||
if line.find('PES packet size mismatch') != -1:
|
||||
self.current_pf_count += 1
|
||||
if self.current_pf_count > self.max_pf_count:
|
||||
#logger.debug('%s : PF_STOP!', self.idx)
|
||||
self.status = Status.PF_STOP
|
||||
self.kill()
|
||||
continue
|
||||
if line.find('HTTP error 403 Forbidden') != -1:
|
||||
#logger.debug('HTTP error 403 Forbidden')
|
||||
self.status = Status.HTTP_FORBIDDEN
|
||||
self.kill()
|
||||
continue
|
||||
match = re.compile(r'time\=(\d{2})\:(\d{2})\:(\d{2})\.(\d{2})\sbitrate\=\s*(?P<bitrate>\d+).*?[$|\s](\s?speed\=\s*(?P<speed>.*?)x)?').search(line)
|
||||
if match:
|
||||
self.current_duration = int(match.group(4))
|
||||
self.current_duration += int(match.group(3)) * 100
|
||||
self.current_duration += int(match.group(2)) * 100 * 60
|
||||
self.current_duration += int(match.group(1)) * 100 * 60 * 60
|
||||
try:
|
||||
self.percent = int(self.current_duration * 100 / self.duration)
|
||||
except: pass
|
||||
#logger.debug('Current Duration : %s %s %s%%', self.duration, self.current_duration, self.percent)
|
||||
self.current_bitrate = match.group('bitrate')
|
||||
self.current_speed = match.group('speed')
|
||||
self.download_time = datetime.now() - self.start_time
|
||||
arg = {'type':'normal', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
continue
|
||||
match = re.compile(r'video\:\d*kB\saudio\:\d*kB').search(line)
|
||||
if match:
|
||||
self.status = Status.COMPLETED
|
||||
self.end_time = datetime.now()
|
||||
self.download_time = self.end_time - self.start_time
|
||||
self.percent = 100
|
||||
arg = {'type':'status_change', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
continue
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
#logger.debug('ffmpeg log thread end')
|
||||
self.start_event.set()
|
||||
self.log_thread = None
|
||||
|
||||
def get_data(self):
|
||||
data = {
|
||||
'url' : self.url,
|
||||
'filename' : self.filename,
|
||||
'max_pf_count' : self.max_pf_count,
|
||||
'call_plugin' : self.call_plugin,
|
||||
'temp_path' : self.temp_path,
|
||||
'save_path' : self.save_path,
|
||||
'temp_fullpath' : self.temp_fullpath,
|
||||
'save_fullpath' : self.save_fullpath,
|
||||
'status' : int(self.status),
|
||||
'status_str' : self.status.name,
|
||||
'status_kor' : str(self.status),
|
||||
'duration' : self.duration,
|
||||
'duration_str' : self.duration_str,
|
||||
'current_duration' : self.current_duration,
|
||||
'percent' : self.percent,
|
||||
'current_pf_count' : self.current_pf_count,
|
||||
'idx' : self.idx,
|
||||
#'log' : self.log,
|
||||
'current_bitrate' : self.current_bitrate,
|
||||
'current_speed' : self.current_speed,
|
||||
'start_time' : '' if self.start_time is None else str(self.start_time).split('.')[0][5:],
|
||||
'end_time' : '' if self.end_time is None else str(self.end_time).split('.')[0][5:],
|
||||
'download_time' : '' if self.download_time is None else '%02d:%02d' % (self.download_time.seconds/60, self.download_time.seconds%60),
|
||||
'exist' : os.path.exists(self.save_fullpath),
|
||||
}
|
||||
if self.status == Status.COMPLETED:
|
||||
data['filesize'] = self.filesize
|
||||
data['filesize_str'] = Util.sizeof_fmt(self.filesize)
|
||||
data['download_speed'] = Util.sizeof_fmt(self.filesize/self.download_time.seconds, suffix='Bytes/Second')
|
||||
return data
|
||||
|
||||
def send_to_listener(self, **arg):
|
||||
Logic.ffmpeg_listener(**arg)
|
||||
if self.listener is not None:
|
||||
arg['plugin_id'] = self.plugin_id
|
||||
self.listener(**arg)
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
try:
|
||||
command = u'%s -version' % (ModelSetting.get('ffmpeg_path'))
|
||||
command = command.split(' ')
|
||||
#logger.debug(command)
|
||||
if app.config['config']['is_py2']:
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1)
|
||||
else:
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
iter_arg = b'' if app.config['config']['is_py2'] else ''
|
||||
ret = []
|
||||
with process.stdout:
|
||||
for line in iter(process.stdout.readline, iter_arg):
|
||||
ret.append(line)
|
||||
process.wait() # wait for the subprocess to exit
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def stop_by_idx(idx):
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.idx == idx:
|
||||
f.stop()
|
||||
break
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def ffmpeg_by_idx(idx):
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.idx == idx:
|
||||
return f
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_ffmpeg_by_caller(caller, caller_id):
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.plugin_id == caller_id and f.call_plugin == caller:
|
||||
return f
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
f.stop()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
178
plugin/ffmpeg/logic.py
Executable file
178
plugin/ffmpeg/logic.py
Executable file
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
import re
|
||||
import threading
|
||||
import json
|
||||
import enum
|
||||
import platform
|
||||
|
||||
# third-party
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import or_, and_, func, not_
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import app, db, scheduler, path_app_root, socketio, path_data
|
||||
from framework.job import Job
|
||||
from framework.util import Util
|
||||
from system.logic import SystemLogic
|
||||
|
||||
# 패키지
|
||||
from .model import ModelSetting
|
||||
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
class Status(enum.Enum):
|
||||
READY = 0
|
||||
WRONG_URL = 1
|
||||
WRONG_DIRECTORY = 2
|
||||
EXCEPTION = 3
|
||||
ERROR = 4
|
||||
HTTP_FORBIDDEN = 11
|
||||
|
||||
DOWNLOADING = 5
|
||||
|
||||
USER_STOP = 6
|
||||
COMPLETED = 7
|
||||
TIME_OVER = 8
|
||||
PF_STOP = 9
|
||||
FORCE_STOP = 10 #강제중단
|
||||
ALREADY_DOWNLOADING = 12 #이미 목록에 있고 다운로드중
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
def __str__(self):
|
||||
kor = [r'준비', r'URL에러', r'폴더에러', r'실패(Exception)', r'실패(에러)', r'다운로드중', r'사용자중지', r'완료', r'시간초과', r'PF중지', r'강제중지',
|
||||
r'403에러', r'임시파일이 이미 있음']
|
||||
return kor[int(self)]
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def get_instance(value):
|
||||
tmp = [ Status.READY, Status.WRONG_URL, Status.WRONG_DIRECTORY, Status.EXCEPTION, Status.ERROR,
|
||||
Status.DOWNLOADING, Status.USER_STOP, Status.COMPLETED, Status.TIME_OVER, Status.PF_STOP,
|
||||
Status.FORCE_STOP, Status.HTTP_FORBIDDEN, Status.ALREADY_DOWNLOADING ]
|
||||
return tmp[value]
|
||||
|
||||
class Logic(object):
|
||||
db_default = {
|
||||
'temp_path' : os.path.join(path_data, 'tmp'),
|
||||
'save_path' : os.path.join(path_data, 'download'),
|
||||
'max_pf_count' : '0',
|
||||
'if_fail_remove_tmp_file' : 'True',
|
||||
'timeout_minute' : '60',
|
||||
'ffmpeg_path' : 'ffmpeg' if platform.system() != 'Windows' else os.path.join(path_data, 'bin', 'ffmpeg.exe'),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def db_init():
|
||||
try:
|
||||
for key, value in Logic.db_default.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 plugin_load():
|
||||
try:
|
||||
Logic.db_init()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
from ffmpeg.interface_program_ffmpeg import Ffmpeg
|
||||
Ffmpeg.plugin_unload()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def ffmpeg_listener(**args):
|
||||
#logger.debug('ffmpeg_listener : %s', args)
|
||||
#logger.debug('ffmpeg_listener : type:%s status:%s', args['type'], args['status'])
|
||||
# status_change ready, downloading, completed
|
||||
refresh_type = None
|
||||
if args['type'] == 'status_change':
|
||||
if args['status'] == Status.DOWNLOADING:
|
||||
refresh_type = 'status_change'
|
||||
elif args['status'] == Status.COMPLETED:
|
||||
refresh_type = 'status_change'
|
||||
elif args['status'] == Status.READY:
|
||||
data = {'type':'info', 'msg' : u'다운로드중 Duration(%s)' % args['data']['duration_str'] + '<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
# 1번에서 리스트화면, 2번에서 추가시 1번도 추가되도록
|
||||
refresh_type = 'add'
|
||||
elif args['type'] == 'last':
|
||||
|
||||
if args['status'] == Status.WRONG_URL:
|
||||
data = {'type':'warning', 'msg' : u'잘못된 URL입니다'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'add'
|
||||
elif args['status'] == Status.WRONG_DIRECTORY:
|
||||
data = {'type':'warning', 'msg' : u'잘못된 디렉토리입니다.<br>' + args['data']['save_fullpath']}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'add'
|
||||
elif args['status'] == Status.ERROR or args['status'] == Status.EXCEPTION:
|
||||
data = {'type':'warning', 'msg' : u'다운로드 시작 실패.<br>' + args['data']['save_fullpath']}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'add'
|
||||
elif args['status'] == Status.USER_STOP:
|
||||
data = {'type':'warning', 'msg' : u'다운로드가 중지 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.COMPLETED:
|
||||
data = {'type':'success', 'msg' : u'다운로드가 완료 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.TIME_OVER:
|
||||
data = {'type':'warning', 'msg' : u'시간초과로 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.PF_STOP:
|
||||
data = {'type':'warning', 'msg' : u'PF초과로 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.FORCE_STOP:
|
||||
data = {'type':'warning', 'msg' : u'강제 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.HTTP_FORBIDDEN:
|
||||
data = {'type':'warning', 'msg' : u'403에러로 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.ALREADY_DOWNLOADING:
|
||||
data = {'type':'warning', 'msg' : u'임시파일폴더에 파일이 있습니다.<br>' + args['data']['temp_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
|
||||
|
||||
|
||||
#elif args['type'] == 'log':
|
||||
# socketio.emit("log", args['data'], namespace='/%s' % package_name, broadcast=True)
|
||||
|
||||
elif args['type'] == 'normal':
|
||||
if args['status'] == Status.DOWNLOADING:
|
||||
refresh_type = 'status'
|
||||
|
||||
if refresh_type is not None:
|
||||
socketio.emit(refresh_type, args['data'], namespace='/%s' % package_name, broadcast=True)
|
||||
23
plugin/ffmpeg/model.py
Executable file
23
plugin/ffmpeg/model.py
Executable file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
# third-party
|
||||
from sqlalchemy import or_, and_, func, not_
|
||||
from sqlalchemy.orm import backref
|
||||
|
||||
# sjva 공용
|
||||
from framework import db, app, path_data
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
from .plugin import logger, package_name
|
||||
#########################################################
|
||||
app.config['SQLALCHEMY_BINDS'][package_name] = 'sqlite:///%s' % (os.path.join(path_data, 'db', '%s.db' % package_name))
|
||||
from plugin import get_model_setting
|
||||
ModelSetting = get_model_setting(package_name, logger)
|
||||
|
||||
323
plugin/ffmpeg/plugin.py
Executable file
323
plugin/ffmpeg/plugin.py
Executable file
@@ -0,0 +1,323 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pytz import timezone
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
from flask import Blueprint, request, Response, send_file, render_template, redirect, jsonify, session, send_from_directory, stream_with_context
|
||||
from flask_socketio import SocketIO, emit, send
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
# sjva 공용
|
||||
from framework import app, db, scheduler, path_app_root, socketio, path_data
|
||||
from framework.logger import get_logger
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
from ffmpeg.logic import Logic
|
||||
from ffmpeg.model import ModelSetting
|
||||
from ffmpeg.interface_program_ffmpeg import Ffmpeg
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
|
||||
#########################################################
|
||||
|
||||
|
||||
#########################################################
|
||||
# 플러그인 공용
|
||||
#########################################################
|
||||
blueprint = Blueprint(package_name, package_name, url_prefix='/%s' % package_name, template_folder='templates')
|
||||
menu = {
|
||||
'main' : [package_name, u'FFMPEG'],
|
||||
'sub' : [
|
||||
['setting', u'설정'], ['download', u'다운로드'], ['list', u'목록'], ['log', u'로그'],
|
||||
]
|
||||
}
|
||||
|
||||
def plugin_load():
|
||||
Logic.plugin_load()
|
||||
|
||||
|
||||
def plugin_unload():
|
||||
Logic.plugin_unload()
|
||||
streaming_kill()
|
||||
|
||||
def streaming_kill():
|
||||
logger.debug('streaming_kill...')
|
||||
global process_list
|
||||
try:
|
||||
for p in process_list:
|
||||
if p is not None and p.poll() is None:
|
||||
import psutil
|
||||
process = psutil.Process(p.pid)
|
||||
for proc in process.children(recursive=True):
|
||||
proc.kill()
|
||||
process.kill()
|
||||
except Exception as e:
|
||||
logger.error('Exception:%s', e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
#########################################################
|
||||
# WEB Menu
|
||||
#########################################################
|
||||
@blueprint.route('/')
|
||||
def home():
|
||||
return redirect('/%s/list' % package_name)
|
||||
|
||||
@blueprint.route('/<sub>')
|
||||
@login_required
|
||||
def detail(sub):
|
||||
arg = ModelSetting.to_dict()
|
||||
if sub == 'setting':
|
||||
return render_template('{package_name}_{sub}.html'.format(package_name=package_name, sub=sub), arg=arg)
|
||||
elif sub == 'download':
|
||||
now = str(datetime.now(timezone('Asia/Seoul'))).replace(':', '').replace('-', '').replace(' ', '-')
|
||||
arg['temp_filename'] = ('%s' % now).split('.')[0] + '.mp4'
|
||||
return render_template('{package_name}_{sub}.html'.format(package_name=package_name, sub=sub), arg=arg)
|
||||
elif sub == 'list':
|
||||
return render_template('{package_name}_{sub}.html'.format(package_name=package_name, sub=sub), arg=arg)
|
||||
elif sub == 'log':
|
||||
return render_template('log.html', package=package_name)
|
||||
return render_template('sample.html', title='%s - %s' % (package_name, sub))
|
||||
|
||||
#########################################################
|
||||
# For UI
|
||||
#########################################################
|
||||
@blueprint.route('/ajax/<sub>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def ajax(sub):
|
||||
try:
|
||||
if sub == 'setting_save':
|
||||
ret = ModelSetting.setting_save(request)
|
||||
return jsonify(ret)
|
||||
elif sub == 'ffmpeg_version':
|
||||
ret = Ffmpeg.get_version()
|
||||
return jsonify(ret)
|
||||
elif sub == 'download':
|
||||
url = request.form['url']
|
||||
filename = request.form['filename']
|
||||
ffmpeg = Ffmpeg(url, filename, call_plugin=package_name)
|
||||
data = ffmpeg.start()
|
||||
return jsonify([])
|
||||
elif sub == 'stop':
|
||||
idx = request.form['idx']
|
||||
Ffmpeg.stop_by_idx(idx)
|
||||
return jsonify([])
|
||||
elif sub == 'play':
|
||||
idx = request.form['idx']
|
||||
ffmpeg = Ffmpeg.ffmpeg_by_idx(idx)
|
||||
tmp = ffmpeg.save_fullpath.replace(path_app_root, '')
|
||||
tmp = tmp.replace('\\', '/')
|
||||
logger.debug('play : %s', tmp)
|
||||
#return redirect('/open_file%s', tmp)
|
||||
#return send_from_directory('', tmp[1:])
|
||||
return jsonify(tmp)
|
||||
elif sub == 'list':
|
||||
ret = []
|
||||
for ffmpeg in Ffmpeg.instance_list:
|
||||
ret.append(ffmpeg.get_data())
|
||||
return jsonify(ret)
|
||||
elif sub == 'streaming_kill':
|
||||
streaming_kill()
|
||||
return jsonify('')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
#http://192.168.0.11:9999/ffmpeg/api/download?url=https%3a%2f%2fani24zo.com%2fani%2fdownload.php%3fid%3d38912&filename=test.mp4&id=0&caller=ani24&save_path=D:\
|
||||
#http://192.168.0.11:9999/ffmpeg/api/status?id=0&caller=ani24
|
||||
#http://192.168.0.11:9999/ffmpeg/api/stop?id=0&caller=ani24
|
||||
|
||||
|
||||
@blueprint.route('/api/<sub>', methods=['GET', 'POST'])
|
||||
def api(sub):
|
||||
sjva_token = request.args.get('token')
|
||||
if sjva_token != SystemModelSetting.get('unique'):
|
||||
ret = {}
|
||||
ret['ret'] = 'wrong_token'
|
||||
return jsonify(ret)
|
||||
if sub == 'download':
|
||||
ret = {}
|
||||
try:
|
||||
|
||||
max_pf_count = ModelSetting.get('max_pf_count')
|
||||
url = request.args.get('url')
|
||||
filename = request.args.get('filename')
|
||||
caller_id = request.args.get('id')
|
||||
package_name = request.args.get('caller')
|
||||
save_path = request.args.get('save_path')
|
||||
if save_path is None:
|
||||
save_path = ModelSetting.get('save_path')
|
||||
else:
|
||||
if not os.path.exists(save_path):
|
||||
os.makedirs(save_path)
|
||||
|
||||
logger.debug('url : %s', url)
|
||||
logger.debug('filename : %s', filename)
|
||||
logger.debug('caller_id : %s', caller_id)
|
||||
logger.debug('caller : %s', package_name)
|
||||
logger.debug('save_path : %s', save_path)
|
||||
|
||||
f = Ffmpeg(url, filename, plugin_id=caller_id, listener=None, max_pf_count=max_pf_count, call_plugin=package_name, save_path=save_path)
|
||||
f.start()
|
||||
ret['ret'] = 'success'
|
||||
ret['data'] = f.get_data()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret['ret'] = 'exception'
|
||||
ret['log'] = traceback.format_exc()
|
||||
return jsonify(ret)
|
||||
elif sub == 'stop':
|
||||
ret = {}
|
||||
try:
|
||||
caller_id = request.args.get('id')
|
||||
package_name = request.args.get('caller')
|
||||
f = Ffmpeg.get_ffmpeg_by_caller(package_name, caller_id)
|
||||
Ffmpeg.stop_by_idx(f.idx)
|
||||
ret['ret'] = 'success'
|
||||
ret['data'] = f.get_data()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret['ret'] = 'exception'
|
||||
ret['log'] = traceback.format_exc()
|
||||
return jsonify(ret)
|
||||
elif sub == 'status':
|
||||
ret = {}
|
||||
try:
|
||||
caller_id = request.args.get('id')
|
||||
package_name = request.args.get('caller')
|
||||
f = Ffmpeg.get_ffmpeg_by_caller(package_name, caller_id)
|
||||
ret['ret'] = 'success'
|
||||
ret['data'] = f.get_data()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret['ret'] = 'exception'
|
||||
ret['log'] = traceback.format_exc()
|
||||
return jsonify(ret)
|
||||
|
||||
|
||||
|
||||
|
||||
@socketio.on('connect', namespace='/%s' % package_name)
|
||||
def connect():
|
||||
logger.debug('ffmpeg socketio connect')
|
||||
|
||||
@socketio.on('disconnect', namespace='/%s' % package_name)
|
||||
def disconnect():
|
||||
logger.debug('ffmpeg socketio disconnect')
|
||||
|
||||
|
||||
process_list = []
|
||||
@blueprint.route('/streaming', methods=['GET'])
|
||||
def streaming():
|
||||
mode = request.args.get('mode')
|
||||
if mode == 'file':
|
||||
try:
|
||||
import subprocess
|
||||
filename = request.args.get('value')
|
||||
if filename.endswith('mp4'):
|
||||
"""
|
||||
output = os.path.join(path_data, 'tmp', 'index.m3u8')
|
||||
#ffmpeg_command = ['ffmpeg', "-loglevel", "quiet", "-i", filename, '-ss', '00:00:03', '-t', '00:03:00', "-vcodec", 'libx264', '-vf', 'scale=160:-1', '-qscale:v', '1', '-acodec', 'aac', '-qscale:a', '1', '-f', 'mp4', output, '-y']
|
||||
ffmpeg_command = ['ffmpeg', "-loglevel", "quiet", "-i", filename, '-ss', '00:00:03', '-t', '00:03:00', "-start_number", '0', '-vf', 'scale=320:-1', '-hls_list_size', '0', '-hls_time', '10', '-f', 'hls', output, '-y']
|
||||
|
||||
logger.warning(' '.join(ffmpeg_command))
|
||||
#subprocess.check_output(ffmpeg_command)
|
||||
subprocess.Popen(ffmpeg_command)
|
||||
time.sleep(5)
|
||||
|
||||
filename = output
|
||||
"""
|
||||
|
||||
url = '/open_file%s' % filename
|
||||
logger.debug(url)
|
||||
return redirect(url)
|
||||
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
def generate():
|
||||
startTime = time.time()
|
||||
buffer = []
|
||||
sentBurst = False
|
||||
|
||||
path_ffmpeg = 'ffmpeg'
|
||||
#filename = '/home/coder/project/SJ/mnt/soju6janm/AV/censored/library2/vr/C/CBIKMV/CBIKMV-093/cbikmv-093cd1.mp4'
|
||||
#filename = '/home/coder/project/SJ/mnt/soju6janw/1.mp4'
|
||||
#ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, "-c:v", "copy", "-c:a", "aac", "-b:a", "128k", "-f", "mpegts", "-tune", "zerolatency", "pipe:stdout"]
|
||||
|
||||
ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, '-ss', '00:00:03', '-t', '00:03:00', "-vcodec", "libvpx", '-vf', 'scale=320:-1', "-qmin", "0", "-qmax", "50", "-crf", "50", "-b:v", "0.1M", '-acodec', 'libvorbis', '-f', 'webm', "pipe:stdout"]
|
||||
|
||||
#ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, "-vcodec", "libtheora", '-vf', 'scale=320:-1', "-qscale:v", '1', '-acodec', 'libvorbis', '-qscale:a', '1', '-f', 'ogv', "pipe:stdout"]
|
||||
|
||||
#ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, "-vcodec", 'libx264', '-acodec', 'aac ', '-f', 'mp4', "pipe:stdout"]
|
||||
|
||||
logger.debug(' '.join(ffmpeg_command))
|
||||
#logger.debug('command : %s', ffmpeg_command)
|
||||
#process = subprocess.Popen(ffmpeg_command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, bufsize = -1)
|
||||
process = subprocess.Popen(ffmpeg_command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, encoding='utf8')
|
||||
global process_list
|
||||
process_list.append(process)
|
||||
while True:
|
||||
#if time.time() - startTime > 120:
|
||||
# break
|
||||
line = process.stdout.read(1024)
|
||||
buffer.append(line)
|
||||
if sentBurst is False and time.time() > startTime + 1 and len(buffer) > 0:
|
||||
sentBurst = True
|
||||
for i in range(0, len(buffer) - 2):
|
||||
yield buffer.pop(0)
|
||||
elif time.time() > startTime + 1 and len(buffer) > 0:
|
||||
yield buffer.pop(0)
|
||||
process.poll()
|
||||
if isinstance(process.returncode, int):
|
||||
if process.returncode > 0:
|
||||
logger.debug('FFmpeg Error :%s', process.returncode)
|
||||
break
|
||||
|
||||
if process is not None and process.poll() is None:
|
||||
process.kill()
|
||||
return Response(stream_with_context(generate()), mimetype = "video/MP2T")
|
||||
|
||||
|
||||
|
||||
def get_video_info(filepath):
|
||||
try:
|
||||
from system.logic_command import SystemLogicCommand
|
||||
command = ['ffprobe', '-v', 'error', '-print_format', 'json', '-show_format', '-show_streams', "%s" % filepath]
|
||||
ret = SystemLogicCommand.execute_command_return(command, format='json')
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
"""
|
||||
ffmpeg version 3.4.8-0ubuntu0.2 Copyright (c) 2000-2020 the FFmpeg developers
|
||||
built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
|
||||
configuration: --prefix=/usr --extra-version=0ubuntu0.2 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared
|
||||
libavutil 55. 78.100 / 55. 78.100
|
||||
libavcodec 57.107.100 / 57.107.100
|
||||
libavformat 57. 83.100 / 57. 83.100
|
||||
libavdevice 57. 10.100 / 57. 10.100
|
||||
libavfilter 6.107.100 / 6.107.100
|
||||
libavresample 3. 7. 0 / 3. 7. 0
|
||||
libswscale 4. 8.100 / 4. 8.100
|
||||
libswresample 2. 9.100 / 2. 9.100
|
||||
libpostproc 54. 7.100 / 54. 7.100
|
||||
Hyper fast Audio and Video encoder
|
||||
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
|
||||
"""
|
||||
39
plugin/ffmpeg/templates/ffmpeg_download.html
Executable file
39
plugin/ffmpeg/templates/ffmpeg_download.html
Executable file
@@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div>
|
||||
{{ macros.setting_input_text('url', 'URL', placeholder='http:// 주소', desc='비디오 파일 주소 or m3u8 주소') }}
|
||||
{{ macros.setting_input_text('filename', '파일명', value=arg['temp_filename']) }}
|
||||
{{ macros.setting_button([['download_start', '다운로드']]) }}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = "{{arg['package_name'] }}";
|
||||
|
||||
$(document).ready(function(){
|
||||
});
|
||||
|
||||
//다운로드시작
|
||||
$("#download_start").click(function(e) {
|
||||
e.preventDefault();
|
||||
if ($("#url").val().startsWith('http') == false) {
|
||||
$.notify('<strong>URL을 입력하세요.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/download',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: {url: $("#url").val(), filename: $("#filename").val()},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
$.notify('<strong>분석중..</strong>', {
|
||||
type: 'info'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
237
plugin/ffmpeg/templates/ffmpeg_list.html
Executable file
237
plugin/ffmpeg/templates/ffmpeg_list.html
Executable file
@@ -0,0 +1,237 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<!--<div class="d-inline-block"></div>-->
|
||||
<!--<div id="accordion">-->
|
||||
<style>
|
||||
.row>div { padding-top: 3px; padding-bottom:3px; }
|
||||
.row { align-items: center; word-break:break-all;}
|
||||
.row>div:nth-child(odd) { align-items: right; text-align: right; }
|
||||
.row>div:nth-child(even) { align-items: left; text-align: left; }
|
||||
</style>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.6/socket.io.js"></script>
|
||||
<table id="result_table" class="table table-sm tableRowHover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:5%">IDX</th>
|
||||
<th style="width:8%">Plugin</th>
|
||||
<th style="width:10%">시작시간</th>
|
||||
<th style="width:20%">파일명</th>
|
||||
<th style="width:8%">상태</th>
|
||||
<th style="width:15%">진행률</th>
|
||||
<th style="width:5%">길이</th>
|
||||
<th style="width:5%">PF</th>
|
||||
<th style="width:8%">배속</th>
|
||||
<th style="width:8%">진행시간</th>
|
||||
<th style="width:8%">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="list" >
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
var package_name = 'ffmpeg';
|
||||
var protocol = window.location.protocol;
|
||||
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/" + package_name)
|
||||
//var socket = io.connect("http://" + document.domain + ":" + location.port + "/ffmpeg", {transports:['websocket']}, {'force new connection': true});
|
||||
|
||||
//socket.emit("start");
|
||||
|
||||
|
||||
socket.on('add', function(data){
|
||||
str = make_item(data)
|
||||
document.getElementById("list").innerHTML += str;
|
||||
//document.getElementById("log_"+data.idx).scrollTop = document.getElementById("log_"+data.idx).scrollHeight ;
|
||||
});
|
||||
|
||||
// 화면을 켜놓고, 스케쥴러에 의해 시작될때 add전에 로그가온다.
|
||||
/*
|
||||
socket.on('log', function(data){
|
||||
if (document.getElementById("log_"+data.idx) != null) {
|
||||
document.getElementById("log_"+data.idx).innerHTML += data.line;
|
||||
document.getElementById("log_"+data.idx).scrollTop = document.getElementById("log_"+data.idx).scrollHeight;
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
|
||||
socket.on('status_change', function(data) {
|
||||
button_html(data);
|
||||
});
|
||||
|
||||
socket.on('status', function(data){
|
||||
status_html(data);
|
||||
});
|
||||
|
||||
socket.on('last', function(data){
|
||||
status_html(data);
|
||||
button_html(data);
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/list',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
document.getElementById("list").innerHTML = '';
|
||||
str = ''
|
||||
for(i in data) {
|
||||
str += make_item(data[i])
|
||||
}
|
||||
document.getElementById("list").innerHTML = str;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$("body").on('click', '#stop', function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var idx = $(this).data('idx')
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/stop',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ idx : idx},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#play', function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var idx = $(this).data('idx')
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/play',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ idx : idx},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
console.log(data);
|
||||
if (data.startsWith('/')) {
|
||||
var url = '/open_file' + data;
|
||||
} else {
|
||||
var url = '/open_file/' + data;
|
||||
}
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function make_item(data) {
|
||||
//if (document.getElementById("log_"+data.idx) != null) return;
|
||||
str = '<tr style="cursor: pointer;" data-toggle="collapse" data-target="#collapse_'+ data.idx + '" aria-expanded="true" >';
|
||||
str += '<td scope="col" style="width:5%">'+ data.idx + '</td>';
|
||||
str += '<td scope="col" style="width:8%">'+ data.call_plugin + '</td>';
|
||||
str += '<td scope="col" style="width:10%">'+ data.start_time + '</td>';
|
||||
str += '<td scope="col" style="width:20%">'+ data.filename + '</td>';
|
||||
str += '<td id="status_'+data.idx+'" scope="col" style="width:8%">'+ data.status_kor + '</td>';
|
||||
var visi = 'hidden';
|
||||
if (parseInt(data.percent) > 0) {
|
||||
visi = 'visible';
|
||||
}
|
||||
str += '<td scope="col" style="width:20%"><div class="progress"><div id="progress_'+data.idx+'" class="progress-bar" style="visibility: '+visi+'; width:'+data.percent+'%">'+data.percent +'%</div></div></td>';
|
||||
str += '<td scope="col" style="width:5%">'+ data.duration_str + '</td>';
|
||||
str += '<td id="current_pf_count_'+data.idx+'" scope="col" style="width:5%">'+ data.current_pf_count + '</td>';
|
||||
str += '<td id="current_speed_'+data.idx+'" scope="col" style="width:8%">'+ data.current_speed + '</td>';
|
||||
str += '<td id="download_time_'+data.idx+'" scope="col" style="width:8%">'+ data.download_time + '</td>';
|
||||
str += '<td id="button_'+data.idx+'" scope="col" style="width:8%" class="tableRowHoverOff">';
|
||||
if (data.status_str == 'DOWNLOADING') {
|
||||
str += '<button id="stop" class="align-middle btn btn-outline-danger btn-sm" data-idx="'+data.idx+'">중지</button>';
|
||||
} else if (data.status_str == 'COMPLETED' && data.exist) {
|
||||
str += '<button id="play" class="align-middle btn btn-outline-info btn-sm" data-idx="'+data.idx+'">재생</button>';
|
||||
}
|
||||
str += '</td>'
|
||||
str += '</tr>'
|
||||
str += '<tr class="collapse tableRowHoverOff" style="cursor: pointer;" id="collapse_' + data.idx + '">';
|
||||
str += '<td colspan="11">';
|
||||
str += '<div id="detail_'+data.idx+'">';
|
||||
str += get_detail(data);
|
||||
str += '</div>';
|
||||
/*
|
||||
str += '<div><textarea class="form-control" id="log_'+data.idx+'" rows="20">';
|
||||
for (var i = 0; i < data.log.length; i++) {
|
||||
str += data.log[i] + '\n';
|
||||
}
|
||||
str += '</textarea></div>';
|
||||
*/
|
||||
str += '</td>';
|
||||
str += '</tr>'
|
||||
return str
|
||||
}
|
||||
|
||||
function info_html($left, $right) {
|
||||
var str = '<div class="row">' +
|
||||
'<div class="col-sm-2">' +
|
||||
'<strong>'+ $left +'</strong>' +
|
||||
'</div>' +
|
||||
'<div class="col-sm-10">' +
|
||||
'<div class="input-group col-sm-9">' +
|
||||
'<span class="text-left" style="padding-left:10px; padding-top:3px">';
|
||||
if ($left == 'URL') {
|
||||
str += '<a href="/hls?url=' + $right + '" target="_blank">';
|
||||
}
|
||||
str += $right;
|
||||
if ($left == 'URL') {
|
||||
str += '</a>';
|
||||
}
|
||||
str += '</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
return str;
|
||||
}
|
||||
|
||||
function get_detail(data) {
|
||||
var str = info_html('URL', data.url);
|
||||
str += info_html('임시경로', data.temp_fullpath);
|
||||
str += info_html('저장경로', data.save_fullpath);
|
||||
str += info_html('진행률(current/total)', data.percent+ '% (' + data.current_duration + ' / ' + data.duration + ')');
|
||||
str += info_html('현재 비트레이트', data.current_bitrate);
|
||||
str += info_html('종료시간', data.end_time);
|
||||
str += info_html('허용 Packet Fail 수', data.max_pf_count);
|
||||
str += info_html('파일 Exist', data.exist);
|
||||
if (data.status_str == 'COMPLETED') {
|
||||
str += info_html('파일 크기', data.filesize_str);
|
||||
str += info_html('다운 속도', data.download_speed);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function button_html(data) {
|
||||
if (data.status_str == 'DOWNLOADING') {
|
||||
document.getElementById("button_" + data.idx).innerHTML =
|
||||
'<button id="stop" class="align-middle btn btn-outline-danger btn-sm" data-idx="'+data.idx+'">중지</button>';
|
||||
} else if (data.status_str == 'COMPLETED' && data.exist) {
|
||||
document.getElementById("button_" + data.idx).innerHTML = '<button id="play" class="align-middle btn btn-outline-info btn-sm" data-idx="'+data.idx+'">재생</button>';
|
||||
} else {
|
||||
document.getElementById("button_" + data.idx).innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function status_html(data) {
|
||||
var progress = document.getElementById("progress_" + data.idx);
|
||||
progress.style.width = data.percent+ '%';
|
||||
progress.innerHTML = data.percent+ '%';
|
||||
progress.style.visibility = 'visible';
|
||||
document.getElementById("status_" + data.idx).innerHTML = data.status_kor;
|
||||
document.getElementById("current_pf_count_" + data.idx).innerHTML = data.current_pf_count;
|
||||
document.getElementById("current_speed_" + data.idx).innerHTML = data.current_speed;
|
||||
document.getElementById("download_time_" + data.idx).innerHTML = data.download_time;
|
||||
//document.getElementById("log").innerHTML += str + '\r\n';
|
||||
document.getElementById("detail_" + data.idx).innerHTML = get_detail(data);
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
79
plugin/ffmpeg/templates/ffmpeg_setting.html
Executable file
79
plugin/ffmpeg/templates/ffmpeg_setting.html
Executable file
@@ -0,0 +1,79 @@
|
||||
{% 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_head_end() }}
|
||||
</nav >
|
||||
<form id='setting' name='setting'>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
{{ macros.m_tab_content_start('normal', true) }}
|
||||
{{ macros.setting_input_text_and_buttons('ffmpeg_path', 'FFMPEG 경로', [['ffmpeg_version', '버전확인'], ['select_binary_path_btn', '파일 선택']], value=arg['ffmpeg_path']) }}
|
||||
{{ macros.setting_input_text_and_buttons('temp_path', '임시 폴더', [['select_temp_path_btn', '경로 선택']], value=arg['temp_path'], desc=['다운로드 파일이 임시로 저장될 폴더 입니다.']) }}
|
||||
|
||||
{{ macros.setting_input_text_and_buttons('save_path', '저장 폴더', [['select_save_path_btn', '경로 선택']], value=arg['save_path'], placeholder='저장 폴더 경로', desc='정상적으로 완료된 파일이 이동할 폴더 입니다.') }}
|
||||
{{ macros.setting_input_int('max_pf_count', '허용 Packet Fail 수', value=arg['max_pf_count'], min='0', placeholder='0', desc=['이 값보다 Packet Fail 횟수가 더 많으면 실패처리 합니다.', '0일 경우 Packet Fail이 발생하면 바로 실패처리.']) }}
|
||||
|
||||
{{ macros.setting_checkbox('if_fail_remove_tmp_file', '임시 파일', arg['if_fail_remove_tmp_file'], desc='On : 실패시 임시 파일 자동 삭제') }}
|
||||
{{ macros.setting_input_int('timeout_minute', '타임아웃 시간', value=arg['timeout_minute'], desc=['이 시간 안에 완료가 되지 않으면 시간초과 에러를 발생합니다.', '속도가 느린 경우 값을 올려 설정하세요. 분 단위']) }}
|
||||
{{ macros.m_tab_content_end() }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = "{{arg['package_name'] }}";
|
||||
|
||||
$(document).ready(function(){
|
||||
});
|
||||
|
||||
//버전
|
||||
$("#ffmpeg_version").click(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/ffmpeg_version',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ },
|
||||
dataType: "json",
|
||||
success: function (list) {
|
||||
var str = '';
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
str += "<div>" + list[i] + "</div>";
|
||||
}
|
||||
document.getElementById("modal_title").innerHTML = "ffmpeg -version";
|
||||
document.getElementById("modal_body").innerHTML = str;
|
||||
$("#large_modal").modal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("body").on('click', '#select_temp_path_btn', function(e){
|
||||
e.preventDefault();
|
||||
m_select_local_file_modal("임시 저장 경로 선택", $('#temp_path').val().trim(), true, function(result){
|
||||
document.getElementById("temp_path").value = result;
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#select_save_path_btn', function(e){
|
||||
e.preventDefault();
|
||||
m_select_local_file_modal("저장 경로 선택", $('#save_path').val().trim(), true, function(result){
|
||||
document.getElementById("save_path").value = result;
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#select_binary_path_btn', function(e){
|
||||
e.preventDefault();
|
||||
m_select_local_file_modal("실행 파일 선택", '/', false, function(result){
|
||||
document.getElementById("ffmpeg_path").value = result;
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
1346
poetry.lock
generated
Normal file
1346
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
pyproject.toml
Normal file
32
pyproject.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[tool.poetry]
|
||||
name = "gommi-server"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["projectdx <com114@gmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
fastapi = "^0.75.2"
|
||||
uvicorn = "^0.17.6"
|
||||
black = "^22.3.0"
|
||||
flake8 = "^4.0.1"
|
||||
gevent = "^21.12.0"
|
||||
pytz = "^2022.1"
|
||||
Flask = "^2.1.1"
|
||||
Flask-Cors = "^3.0.10"
|
||||
Flask-SocketIO = "^5.1.1"
|
||||
Flask-Markdown = "^0.3"
|
||||
requests = "^2.27.1"
|
||||
Flask-SQLAlchemy = "^2.5.1"
|
||||
APScheduler = "^3.9.1"
|
||||
Flask-Login = "^0.6.0"
|
||||
gevent-websocket = "^0.10.1"
|
||||
pyqueue = "^2.0.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^4.0.1"
|
||||
black = "^22.3.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
11
static/css/animate.min.css
vendored
Executable file
11
static/css/animate.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user