fix: system_all_log.html ReferenceError and other updates
This commit is contained in:
17
=24.0.0
17
=24.0.0
@@ -1,17 +0,0 @@
|
|||||||
Collecting greenlet
|
|
||||||
Downloading greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl.metadata (4.1 kB)
|
|
||||||
Collecting gevent
|
|
||||||
Downloading gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl.metadata (14 kB)
|
|
||||||
Collecting zope.event (from gevent)
|
|
||||||
Downloading zope_event-6.1-py3-none-any.whl.metadata (5.1 kB)
|
|
||||||
Collecting zope.interface (from gevent)
|
|
||||||
Downloading zope_interface-8.1.1-cp314-cp314-macosx_11_0_arm64.whl.metadata (45 kB)
|
|
||||||
Downloading greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl (275 kB)
|
|
||||||
Downloading gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl (3.0 MB)
|
|
||||||
━━━━━━━━━ 3.0/3.0 11.3 0:00:00
|
|
||||||
MB MB/s
|
|
||||||
Downloading zope_event-6.1-py3-none-any.whl (6.4 kB)
|
|
||||||
Downloading zope_interface-8.1.1-cp314-cp314-macosx_11_0_arm64.whl (209 kB)
|
|
||||||
Installing collected packages: zope.interface, zope.event, greenlet, gevent
|
|
||||||
|
|
||||||
Successfully installed gevent-25.9.1 greenlet-3.3.0 zope.event-6.1 zope.interface-8.1.1
|
|
||||||
17
=3.2.2
17
=3.2.2
@@ -1,17 +0,0 @@
|
|||||||
Collecting greenlet
|
|
||||||
Downloading greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl.metadata (4.1 kB)
|
|
||||||
Collecting gevent
|
|
||||||
Downloading gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl.metadata (14 kB)
|
|
||||||
Collecting zope.event (from gevent)
|
|
||||||
Downloading zope_event-6.1-py3-none-any.whl.metadata (5.1 kB)
|
|
||||||
Collecting zope.interface (from gevent)
|
|
||||||
Downloading zope_interface-8.1.1-cp314-cp314-macosx_11_0_arm64.whl.metadata (45 kB)
|
|
||||||
Downloading greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl (275 kB)
|
|
||||||
Downloading gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl (3.0 MB)
|
|
||||||
━━━━━━━━━ 3.0/3.0 11.3 0:00:00
|
|
||||||
MB MB/s
|
|
||||||
Downloading zope_event-6.1-py3-none-any.whl (6.4 kB)
|
|
||||||
Downloading zope_interface-8.1.1-cp314-cp314-macosx_11_0_arm64.whl (209 kB)
|
|
||||||
Installing collected packages: zope.interface, zope.event, greenlet, gevent
|
|
||||||
|
|
||||||
Successfully installed gevent-25.9.1 greenlet-3.3.0 zope.event-6.1 zope.interface-8.1.1
|
|
||||||
79
Dockerfile
79
Dockerfile
@@ -1,44 +1,89 @@
|
|||||||
# FlaskFarm Docker Image
|
# FlaskFarm Docker Image v3.16
|
||||||
# Ubuntu 22.04 + Python 3.10 for sc module support on ARM64/x86_64 Linux
|
# Ubuntu/Debian + Python 3.14 for maximum performance
|
||||||
|
# Python 3.14.2 stable release
|
||||||
|
|
||||||
FROM python:3.10-slim-bullseye
|
FROM python:3.14-slim
|
||||||
|
|
||||||
LABEL maintainer="yommi"
|
LABEL maintainer="yommi"
|
||||||
LABEL description="FlaskFarm with sc module support"
|
LABEL description="FlaskFarm with sc module support"
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
|
# Install system dependencies and Korean locales
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
gcc \
|
gcc \
|
||||||
python3-dev \
|
python3-dev \
|
||||||
|
wget \
|
||||||
|
gnupg \
|
||||||
|
libxml2-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libnss3 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libxcomposite1 \
|
||||||
|
libxdamage1 \
|
||||||
|
libxrandr2 \
|
||||||
|
libgbm1 \
|
||||||
|
libasound2 \
|
||||||
|
libasound2-dev \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libgtk-3-0 \
|
||||||
|
pkg-config \
|
||||||
|
libbz2-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libssl-dev \
|
||||||
|
build-essential \
|
||||||
|
locales \
|
||||||
|
&& sed -i -e 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen \
|
||||||
|
&& locale-gen \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Set working directory
|
ENV LC_ALL=ko_KR.UTF-8 \
|
||||||
WORKDIR /app
|
LANG=ko_KR.UTF-8 \
|
||||||
|
LANGUAGE=ko_KR.UTF-8
|
||||||
|
|
||||||
|
# Install Google Chrome Stable (amd64) or Chromium (arm64)
|
||||||
|
ARG TARGETARCH
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg && \
|
||||||
|
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && \
|
||||||
|
apt-get update && apt-get install -y google-chrome-stable; \
|
||||||
|
else \
|
||||||
|
apt-get update && apt-get install -y chromium chromium-driver; \
|
||||||
|
fi && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory to /root
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
# Copy requirements first for layer caching
|
# Copy requirements first for layer caching
|
||||||
COPY ff_3_10_requirements.txt .
|
COPY ff_3_14_requirements.txt .
|
||||||
|
|
||||||
# Install Python dependencies (skip FlaskFarm package - running from source)
|
# Install Python dependencies (including camoufox/zendriver)
|
||||||
RUN grep -v "FlaskFarm" ff_3_10_requirements.txt > requirements_docker.txt \
|
RUN grep -v "FlaskFarm" ff_3_14_requirements.txt > requirements_docker.txt \
|
||||||
&& pip install --no-cache-dir -r requirements_docker.txt \
|
&& pip install --no-cache-dir -r requirements_docker.txt
|
||||||
&& pip install --no-cache-dir curl_cffi yt-dlp loguru
|
|
||||||
|
|
||||||
# Copy FlaskFarm application
|
# Copy FlaskFarm application
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN mkdir -p /data/plugins /data/db
|
||||||
# Expose port
|
COPY gommi.sh /root/gommi.sh
|
||||||
EXPOSE 9099
|
COPY config.yaml /data/config.yaml
|
||||||
|
RUN chmod +x /root/gommi.sh
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
ENV TZ=Asia/Seoul
|
ENV TZ=Asia/Seoul
|
||||||
|
|
||||||
# Health check
|
# Health check (Matching EXPOSE port 9999)
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD curl -f http://localhost:9099/ || exit 1
|
CMD curl -f http://localhost:9999/ || exit 1
|
||||||
|
|
||||||
# Run FlaskFarm
|
# Expose port
|
||||||
CMD ["python", "main.py"]
|
EXPOSE 9999/tcp
|
||||||
|
|
||||||
|
# Run FlaskFarm via gommi.sh in /root
|
||||||
|
ENTRYPOINT ["/root/gommi.sh"]
|
||||||
|
|||||||
44
Dockerfile.3.10.bak
Normal file
44
Dockerfile.3.10.bak
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# FlaskFarm Docker Image
|
||||||
|
# Ubuntu 22.04 + Python 3.10 for sc module support on ARM64/x86_64 Linux
|
||||||
|
|
||||||
|
FROM python:3.10-slim-bullseye
|
||||||
|
|
||||||
|
LABEL maintainer="yommi"
|
||||||
|
LABEL description="FlaskFarm with sc module support"
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
gcc \
|
||||||
|
python3-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements first for layer caching
|
||||||
|
COPY ff_3_10_requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies (skip FlaskFarm package - running from source)
|
||||||
|
RUN grep -v "FlaskFarm" ff_3_10_requirements.txt > requirements_docker.txt \
|
||||||
|
&& pip install --no-cache-dir -r requirements_docker.txt \
|
||||||
|
&& pip install --no-cache-dir curl_cffi yt-dlp loguru
|
||||||
|
|
||||||
|
# Copy FlaskFarm application
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 9099
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV TZ=Asia/Seoul
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:9099/ || exit 1
|
||||||
|
|
||||||
|
# Run FlaskFarm
|
||||||
|
CMD ["python", "main.py"]
|
||||||
24
check_status.py
Normal file
24
check_status.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# FF 경로 설정
|
||||||
|
sys.path.append('/Volumes/WD/Users/Work/python/flaskfarm')
|
||||||
|
from framework import F
|
||||||
|
from gds_dviewer.logic import LogicExplorer
|
||||||
|
|
||||||
|
def check():
|
||||||
|
try:
|
||||||
|
logic = LogicExplorer(None) # 인스턴스 생성 (싱글톤 패턴일 경우 기존 인스턴스 접근 필요할 수도)
|
||||||
|
# 실제로는 LogicExplorer.instance 같은 게 있는지 확인 필요
|
||||||
|
# 하지만 gds_dviewer는 보통 P.logic에 저장됨.
|
||||||
|
|
||||||
|
# P instance 가져오기
|
||||||
|
from gds_dviewer.plugin import P
|
||||||
|
indexer = P.logic.explorer.indexer
|
||||||
|
print(f"Is Running: {indexer.is_running}")
|
||||||
|
print(f"Progress: {indexer.progress}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
check()
|
||||||
52
cleanup_duplicates.py
Normal file
52
cleanup_duplicates.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import unicodedata
|
||||||
|
sys.path.append('/Volumes/WD/Users/Work/python/flaskfarm')
|
||||||
|
from framework import app, db
|
||||||
|
from system.logic import SystemLogic
|
||||||
|
|
||||||
|
# 플러그인 모듈 로드
|
||||||
|
from data.plugins.gds_dviewer.model_file_index import FileIndex
|
||||||
|
|
||||||
|
def cleanup_duplicates(parent_path):
|
||||||
|
with app.app_context():
|
||||||
|
# 해당 폴더의 모든 항목 조회
|
||||||
|
items = FileIndex.query.filter_by(parent_path=parent_path).all()
|
||||||
|
print(f"Total items in {parent_path}: {len(items)}")
|
||||||
|
|
||||||
|
# NFC 이름 기준으로 그룹화
|
||||||
|
groups = {}
|
||||||
|
for item in items:
|
||||||
|
nfc_name = unicodedata.normalize('NFC', item.name)
|
||||||
|
if nfc_name not in groups:
|
||||||
|
groups[nfc_name] = []
|
||||||
|
groups[nfc_name].append(item)
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
for name, group in groups.items():
|
||||||
|
if len(group) > 1:
|
||||||
|
print(f"Found duplicate: {name} (Count: {len(group)})")
|
||||||
|
|
||||||
|
# 우선순위: 메타데이터 있는 것 > ID가 작은 것(오래된 것)
|
||||||
|
# 정렬: 메타데이터 있나? (내림차순 True=1, False=0), ID (오름차순)
|
||||||
|
group.sort(key=lambda x: (1 if x.meta_id else 0, -x.id), reverse=True)
|
||||||
|
|
||||||
|
# 첫 번째(가장 좋은 것)를 남기고 나머지 삭제
|
||||||
|
keep = group[0]
|
||||||
|
remove_list = group[1:]
|
||||||
|
|
||||||
|
print(f" Keep: ID={keep.id}, Meta={keep.meta_id}, Name={keep.name}")
|
||||||
|
for rm in remove_list:
|
||||||
|
print(f" REMOVE: ID={rm.id}, Meta={rm.meta_id}, Name={rm.name}")
|
||||||
|
db.session.delete(rm)
|
||||||
|
deleted_count += 1
|
||||||
|
|
||||||
|
if deleted_count > 0:
|
||||||
|
db.session.commit()
|
||||||
|
print(f"Deleted {deleted_count} duplicate items.")
|
||||||
|
else:
|
||||||
|
print("No duplicates found to delete.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cleanup_duplicates('VIDEO/방송중/라프텔 애니메이션')
|
||||||
12
config.yaml
12
config.yaml
@@ -1,4 +1,4 @@
|
|||||||
path_data: '.'
|
path_data: '/data'
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# 데이터 폴더 루트 경로
|
# 데이터 폴더 루트 경로
|
||||||
# 윈도우의 경우 폴더 구분 기호 \ 를 두개 사용
|
# 윈도우의 경우 폴더 구분 기호 \ 를 두개 사용
|
||||||
@@ -7,7 +7,7 @@ path_data: '.'
|
|||||||
#path_data: "."
|
#path_data: "."
|
||||||
|
|
||||||
# 개발용 플러그인 경로
|
# 개발용 플러그인 경로
|
||||||
path_dev: '/Volumes/WD/Users/Work/python/ff_dev_plugins'
|
path_dev: '/data/plugins'
|
||||||
|
|
||||||
# gevent 사용여부
|
# gevent 사용여부
|
||||||
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
|
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
|
||||||
@@ -24,12 +24,12 @@ use_celery: true
|
|||||||
|
|
||||||
# 포트
|
# 포트
|
||||||
# 생략시 DB 값을 사용.
|
# 생략시 DB 값을 사용.
|
||||||
port: 9099
|
port: 9999
|
||||||
|
|
||||||
# 소스 수정시 재로딩
|
# 소스 수정시 재로딩
|
||||||
# 두번 로딩되는 것을 감안하여 코딩해야 함.
|
# 두번 로딩되는 것을 감안하여 코딩해야 함.
|
||||||
debug: true
|
# debug: true
|
||||||
# debug: false
|
debug: false
|
||||||
|
|
||||||
# 플러그인 업데이트 여부
|
# 플러그인 업데이트 여부
|
||||||
# - true인 경우 로딩시 플러그인을 업데이트 함.
|
# - true인 경우 로딩시 플러그인을 업데이트 함.
|
||||||
@@ -39,7 +39,7 @@ plugin_update: false
|
|||||||
|
|
||||||
# running_type
|
# running_type
|
||||||
# termux, entware 인 경우 입력 함.
|
# termux, entware 인 경우 입력 함.
|
||||||
running_type: 'native'
|
running_type: 'docker'
|
||||||
# 개발용 폴더만 로딩할 경우 사용
|
# 개발용 폴더만 로딩할 경우 사용
|
||||||
# plugin_loading_only_devpath: true
|
# plugin_loading_only_devpath: true
|
||||||
# 로딩할 플러그인 package 명
|
# 로딩할 플러그인 package 명
|
||||||
|
|||||||
51
config_mac.yaml
Normal file
51
config_mac.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
path_data: 'data'
|
||||||
|
##########################################################################
|
||||||
|
# 데이터 폴더 루트 경로
|
||||||
|
# 윈도우의 경우 폴더 구분 기호 \ 를 두개 사용
|
||||||
|
# 예) data_folder: "C:\\work\\data"
|
||||||
|
# 현재 폴더인 경우 .
|
||||||
|
#path_data: "."
|
||||||
|
|
||||||
|
# 개발용 플러그인 경로
|
||||||
|
path_dev: '/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader'
|
||||||
|
|
||||||
|
# gevent 사용여부
|
||||||
|
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
|
||||||
|
# 실행환경에 gevent 관련 패키지가 설치되어 있지 않는다면 값과 상관 없이 false로 동작.
|
||||||
|
use_gevent: true
|
||||||
|
|
||||||
|
# celery 사용 여부
|
||||||
|
use_celery: true
|
||||||
|
|
||||||
|
# redis port
|
||||||
|
# celery를 사용하는 경우 사용하는 redis 포트
|
||||||
|
# 환경변수 REDIS_PORT 값이 있는 경우 무시됨.
|
||||||
|
#redis_port: 6379
|
||||||
|
|
||||||
|
# 포트
|
||||||
|
# 생략시 DB 값을 사용.
|
||||||
|
port: 9099
|
||||||
|
|
||||||
|
# 소스 수정시 재로딩
|
||||||
|
# 두번 로딩되는 것을 감안하여 코딩해야 함.
|
||||||
|
# debug: true
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
# 플러그인 업데이트 여부
|
||||||
|
# - true인 경우 로딩시 플러그인을 업데이트 함.
|
||||||
|
# /data/plugins 폴더에 있는 플러그인 만을 대상으로 함.
|
||||||
|
# - debug 값이 true인 경우에는 항상 false
|
||||||
|
plugin_update: false
|
||||||
|
|
||||||
|
# running_type
|
||||||
|
# termux, entware 인 경우 입력 함.
|
||||||
|
running_type: 'native'
|
||||||
|
# 개발용 폴더만 로딩할 경우 사용
|
||||||
|
# plugin_loading_only_devpath: true
|
||||||
|
# 로딩할 플러그인 package 명
|
||||||
|
# 타 플러그인과 연동되는 플러그인 개발시 사용.
|
||||||
|
# import 로 런타임에 로딩할 수 있지만 타 패키지 메뉴 등은 표시되지 않음.
|
||||||
|
#plugin_loading_list: ['command', 'flaskcode']
|
||||||
|
|
||||||
|
# 로딩 제외할 플러그인 package 명
|
||||||
|
plugin_except_list: ['.idea', '.git', '.vscode', '.nova', '.mypy_cache']
|
||||||
@@ -13,9 +13,9 @@ services:
|
|||||||
- "9099:9099"
|
- "9099:9099"
|
||||||
volumes:
|
volumes:
|
||||||
# FlaskFarm data 폴더 (DB, 설정, 다운로드 등)
|
# FlaskFarm data 폴더 (DB, 설정, 다운로드 등)
|
||||||
- ./data:/app/data
|
- ./data:/data
|
||||||
# 플러그인 폴더 (외부 마운트)
|
# 플러그인 폴더 (외부 마운트)
|
||||||
- ../ff_dev_plugins:/app/plugins
|
- ../ff_dev_plugins:/data/plugins
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Seoul
|
- TZ=Asia/Seoul
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
|
|||||||
97
ff_3_14_requirements.txt
Normal file
97
ff_3_14_requirements.txt
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
aiohttp>=3.9.0
|
||||||
|
aiosignal==1.2.0
|
||||||
|
amqp>=5.1.1
|
||||||
|
appdirs==1.4.4
|
||||||
|
APScheduler==3.9.1
|
||||||
|
async-generator==1.10
|
||||||
|
async-timeout==4.0.2
|
||||||
|
attrs==22.1.0
|
||||||
|
beautifulsoup4==4.11.1
|
||||||
|
bidict==0.22.0
|
||||||
|
billiard>=3.6.4.0
|
||||||
|
cattrs==22.2.0
|
||||||
|
celery>=5.4.0
|
||||||
|
certifi>=2022.9.24
|
||||||
|
charset-normalizer==2.1.1
|
||||||
|
click>=8.1.7
|
||||||
|
click-didyoumean==0.3.0
|
||||||
|
click-plugins==1.1.1
|
||||||
|
click-repl==0.2.0
|
||||||
|
cloudscraper==1.2.64
|
||||||
|
Deprecated>=1.2.14
|
||||||
|
discord-webhook==0.17.0
|
||||||
|
EditorConfig==0.12.3
|
||||||
|
exceptiongroup==1.0.0rc9
|
||||||
|
Flask>=3.0.0
|
||||||
|
Flask-Cors>=3.0.10
|
||||||
|
Flask-Dropzone>=1.6.0
|
||||||
|
Flask-Login>=0.6.3
|
||||||
|
Flask-Markdown==0.3
|
||||||
|
Flask-SocketIO>=5.3.6
|
||||||
|
Flask-SQLAlchemy>=3.0.2
|
||||||
|
FlaskFarm==4.0.47
|
||||||
|
frozenlist>=1.4.1
|
||||||
|
gevent>=24.2.1
|
||||||
|
gevent-websocket==0.10.1
|
||||||
|
greenlet>=3.2.2
|
||||||
|
h11==0.14.0
|
||||||
|
idna==3.4
|
||||||
|
importlib-metadata==5.0.0
|
||||||
|
itsdangerous>=2.1.2
|
||||||
|
Jinja2>=3.1.2
|
||||||
|
jsbeautifier==1.14.7
|
||||||
|
kombu>=5.3.0
|
||||||
|
lxml>=4.9.4
|
||||||
|
Markdown==3.4.1
|
||||||
|
MarkupSafe>=2.1.4
|
||||||
|
multidict>=6.0.5
|
||||||
|
outcome==1.2.0
|
||||||
|
packaging==21.3
|
||||||
|
Pillow>=10.0.0
|
||||||
|
prompt-toolkit==3.0.31
|
||||||
|
psutil==5.9.3
|
||||||
|
pycryptodome==3.15.0
|
||||||
|
pyparsing==3.0.9
|
||||||
|
PySocks==1.7.1
|
||||||
|
python-dotenv==0.21.0
|
||||||
|
python-engineio>=4.8.0
|
||||||
|
python-socketio>=5.10.0
|
||||||
|
pytz==2022.5
|
||||||
|
pytz-deprecation-shim==0.1.0.post0
|
||||||
|
PyYAML>=6.0.2
|
||||||
|
redis>=4.3.4
|
||||||
|
requests==2.28.1
|
||||||
|
requests-cache==0.9.6
|
||||||
|
requests-toolbelt==0.10.1
|
||||||
|
selenium>=4.20.0
|
||||||
|
selenium-stealth==1.0.6
|
||||||
|
six==1.16.0
|
||||||
|
sniffio==1.3.0
|
||||||
|
sortedcontainers==2.4.0
|
||||||
|
soupsieve==2.3.2.post1
|
||||||
|
SQLAlchemy==1.4.42
|
||||||
|
telepot-mod==0.0.1
|
||||||
|
tqdm==4.64.1
|
||||||
|
trio==0.22.0
|
||||||
|
trio-websocket==0.9.2
|
||||||
|
tzdata>=2022.5
|
||||||
|
tzlocal==4.2
|
||||||
|
url-normalize==1.4.3
|
||||||
|
urllib3==1.26.12
|
||||||
|
vine>=5.1.0
|
||||||
|
wcwidth==0.2.5
|
||||||
|
webdriver-manager>=4.0.0
|
||||||
|
Werkzeug>=3.0.0
|
||||||
|
wrapt==1.14.1
|
||||||
|
wsproto==1.2.0
|
||||||
|
yarl>=1.9.4
|
||||||
|
zipp==3.10.0
|
||||||
|
zope.event>=5.0
|
||||||
|
zope.interface>=7.0
|
||||||
|
zendriver
|
||||||
|
camoufox
|
||||||
|
curl_cffi
|
||||||
|
yt-dlp
|
||||||
|
loguru
|
||||||
|
shazamio
|
||||||
|
shazamio-core
|
||||||
34
gommi.sh
34
gommi.sh
@@ -4,12 +4,21 @@
|
|||||||
export GEVENT_NOWAITPID=1
|
export GEVENT_NOWAITPID=1
|
||||||
export PYTHONWARNINGS="ignore::DeprecationWarning"
|
export PYTHONWARNINGS="ignore::DeprecationWarning"
|
||||||
|
|
||||||
CONFIGFILE="./config.yaml"
|
CONFIGFILE="/data/config.yaml"
|
||||||
COUNT=0
|
COUNT=0
|
||||||
|
|
||||||
# 🔧 서버 시작 전에 플러그인 업데이트
|
# 🌐 Camoufox 브라우저 캐시 경로 설정 (마운트된 data 폴더 사용으로 이미지 용량 절감)
|
||||||
|
export CAMOUFOX_CACHE_DIR="/data/.camoufox"
|
||||||
|
|
||||||
|
# 🔧 서버 시작 전에 플러그인 업데이트 및 브라우저 확인
|
||||||
update_plugins() {
|
update_plugins() {
|
||||||
PLUGINS_DIR="./data/plugins"
|
# Camoufox 브라우저 체크 및 다운로드 (컨테이너 최초 실행 시 1회)
|
||||||
|
if [ ! -d "$CAMOUFOX_CACHE_DIR" ]; then
|
||||||
|
echo "Fetching Camoufox binaries to $CAMOUFOX_CACHE_DIR..."
|
||||||
|
camoufox fetch
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLUGINS_DIR="/data/plugins"
|
||||||
if [ -d "$PLUGINS_DIR" ]; then
|
if [ -d "$PLUGINS_DIR" ]; then
|
||||||
for dir in "$PLUGINS_DIR"/*/; do
|
for dir in "$PLUGINS_DIR"/*/; do
|
||||||
if [ -d "$dir/.git" ]; then
|
if [ -d "$dir/.git" ]; then
|
||||||
@@ -28,14 +37,23 @@ fi
|
|||||||
|
|
||||||
while true;
|
while true;
|
||||||
do
|
do
|
||||||
python -m flaskfarm.main --repeat ${COUNT} --config ${CONFIGFILE}
|
echo "------------------------------------------------"
|
||||||
|
echo "Starting FlaskFarm Python Process (COUNT: ${COUNT})"
|
||||||
|
echo "Config: ${CONFIGFILE}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
python main.py --repeat ${COUNT} --config ${CONFIGFILE}
|
||||||
RESULT=$?
|
RESULT=$?
|
||||||
echo "PYTHON EXIT CODE : ${RESULT}.............."
|
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "PYTHON EXIT CODE : ${RESULT}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
if [ "$RESULT" = "1" ]; then
|
if [ "$RESULT" = "1" ]; then
|
||||||
echo 'REPEAT....'
|
echo 'Restarting... (RESULT=1)'
|
||||||
update_plugins # 재시작 시에도 업데이트
|
update_plugins
|
||||||
else
|
else
|
||||||
echo 'FINISH....'
|
echo "Exiting... (RESULT=${RESULT})"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
COUNT=`expr $COUNT + 1`
|
COUNT=`expr $COUNT + 1`
|
||||||
|
|||||||
101
gommi_mac.sh
Executable file
101
gommi_mac.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# pyenv 초기화 (Warp/iTerm 등 비-인터랙티브 셸에서도 작동하도록)
|
||||||
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||||
|
if command -v pyenv &> /dev/null; then
|
||||||
|
eval "$(pyenv init -)"
|
||||||
|
eval "$(pyenv virtualenv-init -)" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Python 3.14 + gevent fork 경고 억제
|
||||||
|
export GEVENT_NOWAITPID=1
|
||||||
|
export PYTHONWARNINGS="ignore::DeprecationWarning"
|
||||||
|
|
||||||
|
# Ctrl+C (SIGINT) 한 번에 종료되도록 설정
|
||||||
|
cleanup() {
|
||||||
|
echo ""
|
||||||
|
echo "Stopping FlaskFarm..."
|
||||||
|
# Python 프로세스 종료
|
||||||
|
if [ -n "$PYTHON_PID" ]; then
|
||||||
|
kill -TERM $PYTHON_PID 2>/dev/null
|
||||||
|
sleep 0.5
|
||||||
|
kill -9 $PYTHON_PID 2>/dev/null
|
||||||
|
fi
|
||||||
|
# 모든 자식 프로세스 종료
|
||||||
|
pkill -P $$ 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
trap cleanup SIGINT SIGTERM
|
||||||
|
|
||||||
|
CONFIGFILE="data/config_mac.yaml"
|
||||||
|
COUNT=0
|
||||||
|
|
||||||
|
# 🌐 Camoufox 브라우저 캐시 경로 설정 및 유지
|
||||||
|
# macOS에서는 /Users/yommi/Library/Caches/camoufox가 기본값이나, 프로젝트 data 폴더로 강제 리다이렉션
|
||||||
|
CAMOUFOX_DEFAULT_DIR="$HOME/Library/Caches/camoufox"
|
||||||
|
CAMOUFOX_PERSISTENT_DIR="$(pwd)/data/.camoufox"
|
||||||
|
|
||||||
|
# 심볼릭 링크를 통해 data 폴더와 동기화
|
||||||
|
if [ ! -L "$CAMOUFOX_DEFAULT_DIR" ]; then
|
||||||
|
echo "Configuring Camoufox persistence link..."
|
||||||
|
mkdir -p "$CAMOUFOX_PERSISTENT_DIR"
|
||||||
|
if [ -d "$CAMOUFOX_DEFAULT_DIR" ] && [ ! -L "$CAMOUFOX_DEFAULT_DIR" ]; then
|
||||||
|
cp -R "$CAMOUFOX_DEFAULT_DIR/" "$CAMOUFOX_PERSISTENT_DIR/" 2>/dev/null
|
||||||
|
rm -rf "$CAMOUFOX_DEFAULT_DIR"
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "$CAMOUFOX_DEFAULT_DIR")"
|
||||||
|
ln -s "$CAMOUFOX_PERSISTENT_DIR" "$CAMOUFOX_DEFAULT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 🔧 서버 시작 전에 플러그인 업데이트 및 브라우저 확인
|
||||||
|
update_plugins() {
|
||||||
|
# Camoufox 브라우저 체크 (실제 설치된 폴더 확인)
|
||||||
|
if [ ! -d "$CAMOUFOX_DEFAULT_DIR/Camoufox.app" ]; then
|
||||||
|
echo "Fetching Camoufox binaries to $CAMOUFOX_PERSISTENT_DIR..."
|
||||||
|
camoufox fetch
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLUGINS_DIR="data/plugins"
|
||||||
|
if [ -d "$PLUGINS_DIR" ]; then
|
||||||
|
for dir in "$PLUGINS_DIR"/*/; do
|
||||||
|
if [ -d "$dir/.git" ]; then
|
||||||
|
echo "Updating plugin: $dir"
|
||||||
|
git -C "$dir" reset --hard HEAD 2>/dev/null
|
||||||
|
git -C "$dir" pull 2>/dev/null & # 병렬 실행
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
wait # 모든 git pull 완료 대기
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 첫 실행 시 또는 --update 옵션일 때만
|
||||||
|
if [ "$COUNT" = "0" ]; then
|
||||||
|
update_plugins
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true;
|
||||||
|
do
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "Starting FlaskFarm Python Process (COUNT: ${COUNT})"
|
||||||
|
echo "Config: ${CONFIGFILE}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
python main.py --repeat ${COUNT} --config ${CONFIGFILE} &
|
||||||
|
PYTHON_PID=$!
|
||||||
|
wait $PYTHON_PID
|
||||||
|
RESULT=$?
|
||||||
|
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "PYTHON EXIT CODE : ${RESULT}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
if [ "$RESULT" = "1" ]; then
|
||||||
|
echo 'Restarting... (RESULT=1)'
|
||||||
|
update_plugins
|
||||||
|
else
|
||||||
|
echo "Exiting... (RESULT=${RESULT})"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
COUNT=$(expr $COUNT + 1)
|
||||||
|
done
|
||||||
@@ -3,6 +3,7 @@ import platform
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
@@ -110,7 +111,8 @@ class PluginManager:
|
|||||||
|
|
||||||
F.logger.debug(plugins)
|
F.logger.debug(plugins)
|
||||||
for plugin_name in plugins:
|
for plugin_name in plugins:
|
||||||
F.logger.debug(f'[+] PLUGIN LOADING Start.. [{plugin_name}]')
|
F.logger.info(f'[+] PLUGIN IMPORT Start.. [{plugin_name}]')
|
||||||
|
import_start_time = time.time()
|
||||||
entity = cls.all_package_list[plugin_name]
|
entity = cls.all_package_list[plugin_name]
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@@ -129,6 +131,9 @@ class PluginManager:
|
|||||||
F.app.register_blueprint(mod_blue_print)
|
F.app.register_blueprint(mod_blue_print)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
F.logger.warning(f'[!] BLUEPRINT not exist : [{plugin_name}]')
|
F.logger.warning(f'[!] BLUEPRINT not exist : [{plugin_name}]')
|
||||||
|
|
||||||
|
import_elapsed_time = time.time() - import_start_time
|
||||||
|
F.logger.info(f'[+] PLUGIN IMPORT End.. [{plugin_name}] ({import_elapsed_time:.3f}s)')
|
||||||
cls.plugin_list[plugin_name] = entity
|
cls.plugin_list[plugin_name] = entity
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
F.logger.error(f"Exception:{str(e)}")
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
@@ -155,9 +160,11 @@ class PluginManager:
|
|||||||
if mod_plugin_load:
|
if mod_plugin_load:
|
||||||
def func(mod_plugin_load, key):
|
def func(mod_plugin_load, key):
|
||||||
try:
|
try:
|
||||||
#F.logger.debug(f'[!] plugin_load_celery threading start : [{key}]')
|
load_start_time = time.time()
|
||||||
|
F.logger.info(f'[!] plugin_load_celery threading start : [{key}]')
|
||||||
mod_plugin_load()
|
mod_plugin_load()
|
||||||
#F.logger.debug(f'[!] plugin_load_celery threading end : [{key}]')
|
load_elapsed_time = time.time() - load_start_time
|
||||||
|
F.logger.info(f'[!] plugin_load_celery threading end : [{key}] ({load_elapsed_time:.3f}s)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
F.logger.error(f"Exception:{str(e)}")
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
@@ -177,9 +184,11 @@ class PluginManager:
|
|||||||
if mod_plugin_load:
|
if mod_plugin_load:
|
||||||
def func(mod_plugin_load, key):
|
def func(mod_plugin_load, key):
|
||||||
try:
|
try:
|
||||||
|
load_start_time = time.time()
|
||||||
F.logger.info(f'[!] plugin_load threading start : [{key}]')
|
F.logger.info(f'[!] plugin_load threading start : [{key}]')
|
||||||
mod_plugin_load()
|
mod_plugin_load()
|
||||||
F.logger.debug(f'[!] plugin_load threading end : [{key}]')
|
load_elapsed_time = time.time() - load_start_time
|
||||||
|
F.logger.info(f'[!] plugin_load threading end : [{key}] ({load_elapsed_time:.3f}s)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
F.logger.error('### plugin_load exception : %s', key)
|
F.logger.error('### plugin_load exception : %s', key)
|
||||||
F.logger.error(f"Exception:{str(e)}")
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
|
|||||||
@@ -107,4 +107,29 @@ background-color: #ffff0080 !important;
|
|||||||
margin:-2px;
|
margin:-2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal { overflow: scroll !important; }
|
.modal { overflow: scroll !important; }
|
||||||
|
|
||||||
|
/* Mobile Navigation Tightening */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#menu_module_div {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
#menu_module_div .nav-pills {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
#menu_page_div .nav-pills {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: 0 .125rem .25rem rgba(0,0,0,.075) !important;
|
||||||
|
}
|
||||||
|
#main_container {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
#main_container > .d-inline-block {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,82 +1,208 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<style>
|
||||||
<nav>
|
/* Unified Log Page Design (matches gds_dviewer) */
|
||||||
{{ macros.m_tab_head_start() }}
|
.log-wrapper {
|
||||||
{{ macros.m_tab_head('old', '이전', true) }}
|
max-width: 1400px;
|
||||||
{{ macros.m_tab_head('new', '실시간', false) }}
|
margin: 0 auto;
|
||||||
{{ macros.m_tab_head_end() }}
|
padding: 20px;
|
||||||
</nav>
|
}
|
||||||
<div class="tab-content" id="nav-tabContent">
|
|
||||||
{{ macros.m_tab_content_start('old', true) }}
|
|
||||||
<div>
|
|
||||||
<textarea id="log" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:hidden"></textarea>
|
|
||||||
</div>
|
|
||||||
{{ macros.m_tab_content_end() }}
|
|
||||||
|
|
||||||
{{ macros.m_tab_content_start('new', false) }}
|
.log-card {
|
||||||
<div>
|
background: linear-gradient(145deg, rgba(20, 30, 48, 0.95), rgba(36, 59, 85, 0.9));
|
||||||
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
|
border: 1px solid rgba(100, 150, 180, 0.25);
|
||||||
</div>
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
<div class="form-inline">
|
}
|
||||||
<label class="form-check-label" for="auto_scroll">자동 스크롤</label>
|
|
||||||
<input id="auto_scroll" name="auto_scroll" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
|
|
||||||
<span class='text-left' style="padding-left:25px; padding-top:0px">
|
|
||||||
<button id="clear" class="btn btn-sm btn-outline-success">리셋</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{{ macros.m_tab_content_end() }}
|
|
||||||
|
|
||||||
</div>
|
.log-tabs {
|
||||||
|
border-bottom: 1px solid rgba(100, 150, 180, 0.2);
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 10px 10px 0 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-tab {
|
||||||
|
color: #94a3b8;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-tab:hover {
|
||||||
|
color: #e2e8f0;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-tab.active {
|
||||||
|
color: #7dd3fc;
|
||||||
|
background: rgba(20, 30, 48, 0.95);
|
||||||
|
border-bottom: 2px solid #7dd3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
min-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line-error { color: #f87171; }
|
||||||
|
.log-line-warning { color: #fbbf24; }
|
||||||
|
.log-line-info { color: #5eead4; }
|
||||||
|
.log-line-debug { color: #94a3b8; }
|
||||||
|
|
||||||
|
.controls-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-top: 1px solid rgba(100, 150, 180, 0.2);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-log {
|
||||||
|
background: linear-gradient(180deg, rgba(45, 55, 72, 0.95), rgba(35, 45, 60, 0.98));
|
||||||
|
border: 1px solid rgba(100, 150, 180, 0.25);
|
||||||
|
color: #7dd3fc;
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-log:hover {
|
||||||
|
background: linear-gradient(180deg, rgba(55, 65, 82, 0.95), rgba(45, 55, 70, 0.98));
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-switch label {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.log-wrapper {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.log-container {
|
||||||
|
height: calc(100vh - 180px);
|
||||||
|
min-height: 300px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="log-wrapper">
|
||||||
|
<div class="log-card">
|
||||||
|
<div class="log-tabs">
|
||||||
|
<button class="log-tab active" data-tab="old">이전</button>
|
||||||
|
<button class="log-tab" data-tab="new">실시간</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log-content active" id="tab-old">
|
||||||
|
<div class="log-container" id="log-history"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log-content" id="tab-new">
|
||||||
|
<div class="log-container" id="log-realtime"></div>
|
||||||
|
<div class="controls-bar">
|
||||||
|
<div class="log-switch">
|
||||||
|
<label for="auto_scroll">자동 스크롤</label>
|
||||||
|
<input id="auto_scroll" name="auto_scroll" type="checkbox" checked>
|
||||||
|
</div>
|
||||||
|
<button id="clear" class="btn-log">리셋</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
function escapeHtml(text) {
|
||||||
setWide();
|
var div = document.createElement('div');
|
||||||
$('#loading').show();
|
div.appendChild(document.createTextNode(text));
|
||||||
ResizeTextAreaLog()
|
return div.innerHTML;
|
||||||
})
|
|
||||||
|
|
||||||
function ResizeTextAreaLog() {
|
|
||||||
ClientHeight = window.innerHeight
|
|
||||||
$("#log").height(ClientHeight-240);
|
|
||||||
$("#add").height(ClientHeight-260);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(window).resize(function() {
|
function formatLogLine(line) {
|
||||||
ResizeTextAreaLog();
|
var className = '';
|
||||||
|
if (line.indexOf('ERROR') !== -1) className = 'log-line-error';
|
||||||
|
else if (line.indexOf('WARNING') !== -1) className = 'log-line-warning';
|
||||||
|
else if (line.indexOf('INFO') !== -1) className = 'log-line-info';
|
||||||
|
else if (line.indexOf('DEBUG') !== -1) className = 'log-line-debug';
|
||||||
|
return '<div class="' + className + '">' + escapeHtml(line) + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab switching
|
||||||
|
document.querySelectorAll('.log-tab').forEach(function(tab) {
|
||||||
|
tab.addEventListener('click', function() {
|
||||||
|
document.querySelectorAll('.log-tab').forEach(function(t) { t.classList.remove('active'); });
|
||||||
|
document.querySelectorAll('.log-content').forEach(function(c) { c.classList.remove('active'); });
|
||||||
|
this.classList.add('active');
|
||||||
|
document.getElementById('tab-' + this.dataset.tab).classList.add('active');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
setWide();
|
||||||
|
$('#loading').show();
|
||||||
|
});
|
||||||
|
|
||||||
var protocol = window.location.protocol;
|
var protocol = window.location.protocol;
|
||||||
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
|
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
|
||||||
|
|
||||||
socket.emit("start", {'package':'{{package}}'} );
|
socket.emit("start", {'package':'{{package}}'});
|
||||||
socket.on('on_start', function(data){
|
|
||||||
document.getElementById("log").innerHTML += data.data;
|
socket.on('on_start', function(data) {
|
||||||
document.getElementById("log").scrollTop = document.getElementById("log").scrollHeight;
|
var container = document.getElementById("log-history");
|
||||||
document.getElementById("log").style.visibility = 'visible';
|
var lines = data.data.split('\n');
|
||||||
$('#loading').hide();
|
var html = '';
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
html += formatLogLine(lines[i]);
|
||||||
|
}
|
||||||
|
container.innerHTML = html || '<div style="text-align:center;color:#64748b;">로그가 비어 있습니다.</div>';
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
$('#loading').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('add', function(data){
|
socket.on('add', function(data) {
|
||||||
if (data.package == "{{package}}") {
|
if (data.package == "{{package}}") {
|
||||||
var chk = $('#auto_scroll').is(":checked");
|
var chk = $('#auto_scroll').is(":checked");
|
||||||
document.getElementById("add").innerHTML += data.data;
|
var container = document.getElementById("log-realtime");
|
||||||
if (chk) document.getElementById("add").scrollTop = document.getElementById("add").scrollHeight;
|
container.innerHTML += formatLogLine(data.data);
|
||||||
}
|
if (chk) container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#clear").click(function(e) {
|
$("#clear").click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById("add").innerHTML = '';
|
document.getElementById("log-realtime").innerHTML = '';
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
$("#auto_scroll").click(function(){
|
|
||||||
var chk = $(this).is(":checked");//.attr('checked');
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VERSION="4.1.40"
|
VERSION="4.1.40"
|
||||||
|
|||||||
@@ -132,14 +132,15 @@ def default_route(P):
|
|||||||
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
|
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def second_ajax(module_name, cmd):
|
def second_ajax(module_name, cmd):
|
||||||
|
# P.logger.debug(f"[CORE-DEBUG] second_ajax: package={P.package_name}, module={module_name}, cmd={cmd}")
|
||||||
try:
|
try:
|
||||||
for module in P.module_list:
|
for module in P.module_list:
|
||||||
if cmd == 'scheduler':
|
if cmd == 'scheduler':
|
||||||
go = request.form['scheduler']
|
go = request.form['scheduler']
|
||||||
if go == 'true':
|
if go == 'true':
|
||||||
P.logic.scheduler_start(module_name)
|
P.logic.scheduler_start(module_name)
|
||||||
else:
|
else:
|
||||||
P.logic.scheduler_stop(module_name)
|
P.logic.scheduler_stop(module_name)
|
||||||
return jsonify(go)
|
return jsonify(go)
|
||||||
elif cmd == 'db_delete':
|
elif cmd == 'db_delete':
|
||||||
day = request.form['day']
|
day = request.form['day']
|
||||||
|
|||||||
@@ -53,11 +53,26 @@ $(window).resize(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.appendChild(document.createTextNode(text));
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLogLine(line) {
|
||||||
|
var className = '';
|
||||||
|
if (line.indexOf('ERROR') !== -1) className = 'text-danger';
|
||||||
|
else if (line.indexOf('WARNING') !== -1) className = 'text-warning';
|
||||||
|
else if (line.indexOf('INFO') !== -1) className = 'text-info';
|
||||||
|
else if (line.indexOf('DEBUG') !== -1) className = 'text-muted';
|
||||||
|
return '<div class="' + className + '" style="white-space: pre-wrap; font-family: monospace; font-size: 13px;">' + escapeHtml(line) + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
socket.on('on_start', function(data){
|
socket.on('on_start', function(data){
|
||||||
lines = splitLines(data.data);
|
var lines = data.data.split('\n');
|
||||||
var html = '';
|
var html = '';
|
||||||
for (i in lines) {
|
for (var i in lines) {
|
||||||
html += logline(lines[i]);
|
html += formatLogLine(lines[i]);
|
||||||
}
|
}
|
||||||
$('#log_div').html(html)
|
$('#log_div').html(html)
|
||||||
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
|
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
|
||||||
@@ -66,7 +81,7 @@ socket.on('on_start', function(data){
|
|||||||
|
|
||||||
socket.on('add', function(data){
|
socket.on('add', function(data){
|
||||||
if (data.filename == current_filename) {
|
if (data.filename == current_filename) {
|
||||||
$('#log_div').append(logline(data.data.trim()));
|
$('#log_div').append(formatLogLine(data.data.trim()));
|
||||||
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
|
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
13
main.py
13
main.py
@@ -3,12 +3,21 @@
|
|||||||
|
|
||||||
monkey.patch_all()
|
monkey.patch_all()
|
||||||
print("[MAIN] gevent mokey patch!!")
|
print("[MAIN] gevent mokey patch!!")
|
||||||
except Exception:
|
except Exception as e:
|
||||||
print("[MAIN] gevent not installed!!")
|
print(f"[MAIN] gevent not installed or failed to load: {e}")
|
||||||
|
# import traceback; traceback.print_exc()
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import markupsafe
|
||||||
|
flask.Markup = markupsafe.Markup
|
||||||
|
sys.modules['flask.Markup'] = markupsafe.Markup # Backup for some import styles
|
||||||
|
# Injecting into flask module members for 'from flask import Markup'
|
||||||
|
if not hasattr(flask, 'Markup'):
|
||||||
|
setattr(flask, 'Markup', markupsafe.Markup)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
|||||||
43
manual_migration.py
Normal file
43
manual_migration.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
db_path = '/Volumes/WD/Users/Work/python/flaskfarm/data/db/gds_dviewer.db'
|
||||||
|
table_name = 'gds_dviewer_file_index'
|
||||||
|
|
||||||
|
print(f"Checking DB: {db_path}")
|
||||||
|
if not os.path.exists(db_path):
|
||||||
|
print("Error: DB file not found")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Check columns
|
||||||
|
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||||
|
columns = [row[1] for row in cursor.fetchall()]
|
||||||
|
print(f"Current columns: {columns}")
|
||||||
|
|
||||||
|
# Add columns if missing
|
||||||
|
changed = False
|
||||||
|
if 'meta_id' not in columns:
|
||||||
|
print(f"Adding meta_id to {table_name}")
|
||||||
|
cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN meta_id VARCHAR(64)")
|
||||||
|
try:
|
||||||
|
cursor.execute(f"CREATE INDEX ix_{table_name}_meta_id ON {table_name} (meta_id)")
|
||||||
|
print("Created index for meta_id")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Index creation hint/error: {e}")
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if 'meta_poster' not in columns:
|
||||||
|
print(f"Adding meta_poster to {table_name}")
|
||||||
|
cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN meta_poster VARCHAR(1024)")
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
conn.commit()
|
||||||
|
print("Migration successful")
|
||||||
|
else:
|
||||||
|
print("No changes needed")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
16
restart_mac.sh
Executable file
16
restart_mac.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# FlaskFarm 재시작 스크립트
|
||||||
|
# 사용법: ./restart.sh
|
||||||
|
|
||||||
|
cd /Volumes/WD/Users/Work/python/flaskfarm
|
||||||
|
|
||||||
|
# 기존 FlaskFarm 프로세스 종료
|
||||||
|
pkill -9 -f "python main.py" 2>/dev/null
|
||||||
|
lsof -ti :9099 | xargs kill -9 2>/dev/null
|
||||||
|
|
||||||
|
# 잠시 대기
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# 백그라운드 없이 재시작 (터미널에서 직접 실행용)
|
||||||
|
echo "FlaskFarm 시작 중..."
|
||||||
|
./gommi_mac.sh
|
||||||
37
test_metadata_search.py
Normal file
37
test_metadata_search.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
# Add dev plugins path to sys.path
|
||||||
|
dev_path = '/Volumes/WD/Users/Work/python/ff_dev_plugins'
|
||||||
|
if dev_path not in sys.path:
|
||||||
|
sys.path.insert(0, dev_path)
|
||||||
|
|
||||||
|
# Mocking Logger and F for direct import test
|
||||||
|
class MockLogger:
|
||||||
|
def debug(self, msg, *args): print(f"DEBUG: {msg % args if args else msg}")
|
||||||
|
def info(self, msg, *args): print(f"INFO: {msg % args if args else msg}")
|
||||||
|
def error(self, msg, *args): print(f"ERROR: {msg % args if args else msg}")
|
||||||
|
def warning(self, msg, *args): print(f"WARNING: {msg % args if args else msg}")
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
# We need to set up the environment so metadata can be imported
|
||||||
|
os.environ['PYTHONPATH'] = dev_path
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try importing directly from the plugin
|
||||||
|
# metadata/mod_ftv.py contains ModuleFtv
|
||||||
|
from metadata.mod_ftv import ModuleFtv
|
||||||
|
from support_site import SiteTmdbFtv
|
||||||
|
|
||||||
|
print("Searching for '비밀의 아이프리' with year 2024...")
|
||||||
|
# SiteTmdbFtv.search is a classmethod
|
||||||
|
result = SiteTmdbFtv.search('비밀의 아이프리', year=2024)
|
||||||
|
print(f"Result: {result}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
Reference in New Issue
Block a user