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
|
||||
# Ubuntu 22.04 + Python 3.10 for sc module support on ARM64/x86_64 Linux
|
||||
# FlaskFarm Docker Image v3.16
|
||||
# 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 description="FlaskFarm with sc module support"
|
||||
|
||||
# Install system dependencies
|
||||
# Install system dependencies and Korean locales
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ffmpeg \
|
||||
git \
|
||||
curl \
|
||||
gcc \
|
||||
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/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
ENV LC_ALL=ko_KR.UTF-8 \
|
||||
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 ff_3_10_requirements.txt .
|
||||
COPY ff_3_14_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
|
||||
# Install Python dependencies (including camoufox/zendriver)
|
||||
RUN grep -v "FlaskFarm" ff_3_14_requirements.txt > requirements_docker.txt \
|
||||
&& pip install --no-cache-dir -r requirements_docker.txt
|
||||
|
||||
# Copy FlaskFarm application
|
||||
COPY . .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 9099
|
||||
RUN mkdir -p /data/plugins /data/db
|
||||
COPY gommi.sh /root/gommi.sh
|
||||
COPY config.yaml /data/config.yaml
|
||||
RUN chmod +x /root/gommi.sh
|
||||
|
||||
# Environment variables
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV TZ=Asia/Seoul
|
||||
|
||||
# Health check
|
||||
# Health check (Matching EXPOSE port 9999)
|
||||
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
|
||||
CMD ["python", "main.py"]
|
||||
# Expose port
|
||||
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_dev: '/Volumes/WD/Users/Work/python/ff_dev_plugins'
|
||||
path_dev: '/data/plugins'
|
||||
|
||||
# gevent 사용여부
|
||||
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
|
||||
@@ -24,12 +24,12 @@ use_celery: true
|
||||
|
||||
# 포트
|
||||
# 생략시 DB 값을 사용.
|
||||
port: 9099
|
||||
port: 9999
|
||||
|
||||
# 소스 수정시 재로딩
|
||||
# 두번 로딩되는 것을 감안하여 코딩해야 함.
|
||||
debug: true
|
||||
# debug: false
|
||||
# debug: true
|
||||
debug: false
|
||||
|
||||
# 플러그인 업데이트 여부
|
||||
# - true인 경우 로딩시 플러그인을 업데이트 함.
|
||||
@@ -39,7 +39,7 @@ plugin_update: false
|
||||
|
||||
# running_type
|
||||
# termux, entware 인 경우 입력 함.
|
||||
running_type: 'native'
|
||||
running_type: 'docker'
|
||||
# 개발용 폴더만 로딩할 경우 사용
|
||||
# plugin_loading_only_devpath: true
|
||||
# 로딩할 플러그인 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"
|
||||
volumes:
|
||||
# FlaskFarm data 폴더 (DB, 설정, 다운로드 등)
|
||||
- ./data:/app/data
|
||||
- ./data:/data
|
||||
# 플러그인 폴더 (외부 마운트)
|
||||
- ../ff_dev_plugins:/app/plugins
|
||||
- ../ff_dev_plugins:/data/plugins
|
||||
environment:
|
||||
- TZ=Asia/Seoul
|
||||
- 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 PYTHONWARNINGS="ignore::DeprecationWarning"
|
||||
|
||||
CONFIGFILE="./config.yaml"
|
||||
CONFIGFILE="/data/config.yaml"
|
||||
COUNT=0
|
||||
|
||||
# 🔧 서버 시작 전에 플러그인 업데이트
|
||||
# 🌐 Camoufox 브라우저 캐시 경로 설정 (마운트된 data 폴더 사용으로 이미지 용량 절감)
|
||||
export CAMOUFOX_CACHE_DIR="/data/.camoufox"
|
||||
|
||||
# 🔧 서버 시작 전에 플러그인 업데이트 및 브라우저 확인
|
||||
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
|
||||
for dir in "$PLUGINS_DIR"/*/; do
|
||||
if [ -d "$dir/.git" ]; then
|
||||
@@ -28,14 +37,23 @@ fi
|
||||
|
||||
while true;
|
||||
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=$?
|
||||
echo "PYTHON EXIT CODE : ${RESULT}.............."
|
||||
|
||||
echo "------------------------------------------------"
|
||||
echo "PYTHON EXIT CODE : ${RESULT}"
|
||||
echo "------------------------------------------------"
|
||||
|
||||
if [ "$RESULT" = "1" ]; then
|
||||
echo 'REPEAT....'
|
||||
update_plugins # 재시작 시에도 업데이트
|
||||
echo 'Restarting... (RESULT=1)'
|
||||
update_plugins
|
||||
else
|
||||
echo 'FINISH....'
|
||||
echo "Exiting... (RESULT=${RESULT})"
|
||||
break
|
||||
fi
|
||||
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 sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import zipfile
|
||||
|
||||
@@ -110,7 +111,8 @@ class PluginManager:
|
||||
|
||||
F.logger.debug(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]
|
||||
try:
|
||||
try:
|
||||
@@ -129,6 +131,9 @@ class PluginManager:
|
||||
F.app.register_blueprint(mod_blue_print)
|
||||
except Exception as exception:
|
||||
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
|
||||
except Exception as e:
|
||||
F.logger.error(f"Exception:{str(e)}")
|
||||
@@ -155,9 +160,11 @@ class PluginManager:
|
||||
if mod_plugin_load:
|
||||
def func(mod_plugin_load, key):
|
||||
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()
|
||||
#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:
|
||||
F.logger.error(f"Exception:{str(e)}")
|
||||
F.logger.error(traceback.format_exc())
|
||||
@@ -177,9 +184,11 @@ class PluginManager:
|
||||
if mod_plugin_load:
|
||||
def func(mod_plugin_load, key):
|
||||
try:
|
||||
load_start_time = time.time()
|
||||
F.logger.info(f'[!] plugin_load threading start : [{key}]')
|
||||
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:
|
||||
F.logger.error('### plugin_load exception : %s', key)
|
||||
F.logger.error(f"Exception:{str(e)}")
|
||||
|
||||
@@ -108,3 +108,28 @@ background-color: #ffff0080 !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" %}
|
||||
{% block content %}
|
||||
<div>
|
||||
<nav>
|
||||
{{ macros.m_tab_head_start() }}
|
||||
{{ macros.m_tab_head('old', '이전', true) }}
|
||||
{{ macros.m_tab_head('new', '실시간', false) }}
|
||||
{{ macros.m_tab_head_end() }}
|
||||
</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() }}
|
||||
<style>
|
||||
/* Unified Log Page Design (matches gds_dviewer) */
|
||||
.log-wrapper {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
{{ macros.m_tab_content_start('new', false) }}
|
||||
<div>
|
||||
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
|
||||
</div>
|
||||
.log-card {
|
||||
background: linear-gradient(145deg, rgba(20, 30, 48, 0.95), rgba(36, 59, 85, 0.9));
|
||||
border: 1px solid rgba(100, 150, 180, 0.25);
|
||||
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() }}
|
||||
.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;
|
||||
}
|
||||
|
||||
</div>
|
||||
.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>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
setWide();
|
||||
$('#loading').show();
|
||||
ResizeTextAreaLog()
|
||||
})
|
||||
|
||||
function ResizeTextAreaLog() {
|
||||
ClientHeight = window.innerHeight
|
||||
$("#log").height(ClientHeight-240);
|
||||
$("#add").height(ClientHeight-260);
|
||||
function escapeHtml(text) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(text));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
ResizeTextAreaLog();
|
||||
function formatLogLine(line) {
|
||||
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 socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
|
||||
|
||||
socket.emit("start", {'package':'{{package}}'} );
|
||||
socket.on('on_start', function(data){
|
||||
document.getElementById("log").innerHTML += data.data;
|
||||
document.getElementById("log").scrollTop = document.getElementById("log").scrollHeight;
|
||||
document.getElementById("log").style.visibility = 'visible';
|
||||
$('#loading').hide();
|
||||
socket.emit("start", {'package':'{{package}}'});
|
||||
|
||||
socket.on('on_start', function(data) {
|
||||
var container = document.getElementById("log-history");
|
||||
var lines = data.data.split('\n');
|
||||
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){
|
||||
if (data.package == "{{package}}") {
|
||||
var chk = $('#auto_scroll').is(":checked");
|
||||
document.getElementById("add").innerHTML += data.data;
|
||||
if (chk) document.getElementById("add").scrollTop = document.getElementById("add").scrollHeight;
|
||||
}
|
||||
socket.on('add', function(data) {
|
||||
if (data.package == "{{package}}") {
|
||||
var chk = $('#auto_scroll').is(":checked");
|
||||
var container = document.getElementById("log-realtime");
|
||||
container.innerHTML += formatLogLine(data.data);
|
||||
if (chk) container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
$("#clear").click(function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById("add").innerHTML = '';
|
||||
e.preventDefault();
|
||||
document.getElementById("log-realtime").innerHTML = '';
|
||||
});
|
||||
|
||||
$("#auto_scroll").click(function(){
|
||||
var chk = $(this).is(":checked");//.attr('checked');
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -132,14 +132,15 @@ def default_route(P):
|
||||
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def second_ajax(module_name, cmd):
|
||||
# P.logger.debug(f"[CORE-DEBUG] second_ajax: package={P.package_name}, module={module_name}, cmd={cmd}")
|
||||
try:
|
||||
for module in P.module_list:
|
||||
if cmd == 'scheduler':
|
||||
go = request.form['scheduler']
|
||||
if go == 'true':
|
||||
P.logic.scheduler_start(module_name)
|
||||
P.logic.scheduler_start(module_name)
|
||||
else:
|
||||
P.logic.scheduler_stop(module_name)
|
||||
P.logic.scheduler_stop(module_name)
|
||||
return jsonify(go)
|
||||
elif cmd == 'db_delete':
|
||||
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){
|
||||
lines = splitLines(data.data);
|
||||
var lines = data.data.split('\n');
|
||||
var html = '';
|
||||
for (i in lines) {
|
||||
html += logline(lines[i]);
|
||||
for (var i in lines) {
|
||||
html += formatLogLine(lines[i]);
|
||||
}
|
||||
$('#log_div').html(html)
|
||||
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){
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
13
main.py
13
main.py
@@ -3,12 +3,21 @@
|
||||
|
||||
monkey.patch_all()
|
||||
print("[MAIN] gevent mokey patch!!")
|
||||
except Exception:
|
||||
print("[MAIN] gevent not installed!!")
|
||||
except Exception as e:
|
||||
print(f"[MAIN] gevent not installed or failed to load: {e}")
|
||||
# import traceback; traceback.print_exc()
|
||||
import os
|
||||
import sys
|
||||
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:
|
||||
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