first commit

This commit is contained in:
2022-04-21 19:23:01 +09:00
commit cfd562fd13
152 changed files with 54937 additions and 0 deletions

219
lib/framework/__init__.py Normal file
View 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 *

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View File

@@ -0,0 +1 @@
import os

View File

@@ -0,0 +1 @@
from .shutil_task import move, copytree, copy, rmtree, move_exist_remove

View 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

View File

@@ -0,0 +1 @@
from .env import *

View 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())

View 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

View 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
View 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"] = {}

View 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()

View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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)