From 2681f5a09684572809f0f00f607628b88747b7ca Mon Sep 17 00:00:00 2001 From: projectdx Date: Sat, 17 Jan 2026 14:06:27 +0900 Subject: [PATCH] fix: system_all_log.html ReferenceError and other updates --- .DS_Store | Bin 10244 -> 10244 bytes =24.0.0 | 17 -- =3.2.2 | 17 -- Dockerfile | 79 ++++++-- Dockerfile.3.10.bak | 44 ++++ check_status.py | 24 +++ cleanup_duplicates.py | 52 +++++ config.yaml | 12 +- config_mac.yaml | 51 +++++ db.sqlite | 0 docker-compose.yml | 4 +- ff_3_14_requirements.txt | 97 +++++++++ gommi.sh | 34 +++- gommi_mac.sh | 101 ++++++++++ lib/framework/init_plugin.py | 17 +- lib/framework/static/css/custom.css | 27 ++- lib/framework/templates/log.html | 246 +++++++++++++++++------ lib/framework/version.py | 2 +- lib/plugin/route.py | 5 +- lib/system/templates/system_all_log.html | 23 ++- main.py | 13 +- manual_migration.py | 43 ++++ restart_mac.sh | 16 ++ test_metadata_search.py | 37 ++++ 24 files changed, 820 insertions(+), 141 deletions(-) delete mode 100644 =24.0.0 delete mode 100644 =3.2.2 create mode 100644 Dockerfile.3.10.bak create mode 100644 check_status.py create mode 100644 cleanup_duplicates.py create mode 100644 config_mac.yaml create mode 100644 db.sqlite create mode 100644 ff_3_14_requirements.txt create mode 100755 gommi_mac.sh create mode 100644 manual_migration.py create mode 100755 restart_mac.sh create mode 100644 test_metadata_search.py diff --git a/.DS_Store b/.DS_Store index 029c867ada404ab6b470f71461689677ced6ab2b..167bf60986442ce0c9ee22feff9497b17842f053 100644 GIT binary patch delta 1487 zcmeH`TTB#J7{||l06pV`?imZL9WINm#RwbR4M@GTfb5dWMVGK3s1;nCF-*G5t~(0` zn`T=|`%o`0le9Li@kwKXcPpj0#6D=E51J4(#?)vu;lUUaG~N@_vpXxVebG1P;hgjT zzBAvM@BIEV+BVvDazCTKYM-aHs>WxvJL{}lOa`%LnA5kaFBZ|wgx7lmLRNOpoViq- zCrP$*M4kpi+@}vUmH@Px7pUI8|A1F%qV4uEYP)`gqH>LKL5~WmRXX$4uuemO&>f3cq zO=px6Wih3jQ*nP>gpB9;N zB>A@}HPQ;oTEwm)EpF;j!#-oR^a+*W5xciV2L?1FVcOw(rGc{FX;13#*07lf#x#SU zpK2peq9_|v@inTdshKX6CZ&b4NxI4G5689OPD6{^g_@KgrM*e!NGDRI8dW_Joy~@L zmNDHJ(i8k@+&z@#>shdnOr0Pl4&wS9+Jo833-WZk&i;wx9=F5yo~z5knj%hB1O~@GZW>KKzLN_!+<85RT&nPT@5E!6i)LDz4!cZa3f# z?&1NS;u)R`l8`6l3kAYbpss5? z+|s)4e=G^nx>wkrRV=xzkMhfs+w-xra++#L88Y#Im{N>|AXfmqf;zjP?h3rIdf@EXX8zGbCSwyhtX=b#j~B z=OAAp3lvV2q6{96trqq0BY;MZt_|%7aeToo=;ieKuniFyoZt@5@C$s2B)-De_#S(( zH_h>SXECZ3&ibS+*@$EYfYuMO@qkOW7^hI?E0+vX^#e z+6om{6GBYHC>bO~^id5;h>>U{(Zu-R6Pid+Q4(G-#`uCHCPw3f-*i?J5))r|bROo+ z|NmXioO6E1lgE?qo#m~A(QvS5U|`@_IJh_%jf8{!ee3NDdY{?yrC~7W-OyvFemhqDPm|iLQ9W)vECEtShXfHuyOO)_MN+~H8d{gO0&{x zuWR$$x0}3-dwu@4`&89_-s3N)<}$|YoM9c|A;#Rj?dnRIf57%Ei9$%4)S+<{BF}#jm?p^eAg(Zp!0_jY8#UROx3tEAPo^89nFTwm}(W zW&Vh~cit_*b%&IPSQEl>>Oe7{*9(^E+A(#M@yl$=NgmfsD>0)NM1H51E$ynBcKld2 zt7kK z>#`m5wwBwdgZgQRl9Z+_Ezn7NmCn$cbdf%!kLh#zimuYv^bOshALwWLmHtEx>cAkQ z87=U@ix9%-K`%C96l2(mZHQwJCXhlJQ+N`G@DvVX7Dus&V|W%P@G@S3jnjAoZ%Wr4DJ06p_}sB}i~1O;%zhNF(B- zT9l%ZMOx0BpULEk86s)+-#`sCY~7|jd>5(ddqe7X^$wd&%`L5-6`|EV(e)1w+nt^S zbNN&P_M&H1*%9qQCBSP0KJ1z!JG+Fe738?`{_a44OM+Ne_O1;myh`vWFKzDY77`V) z;Q>YA)q+fSZD^B_)Pz_?$410W4rYvnDjd|&e}ZtH-lvb~Q@SERxJkF@C;AP9TGWfd zuRu@?zY|?z_z^_Whkk6rX2dWeP>ADEB(V#-u@8@98c#qIAmoro0Sh=HV0cF0Z~`yj zMV!P-QJle9yoPgl-Qfc7;(|NTe+Iqd?m^>|OO3wY*{SCaUaAZJQEhMW^#0%6d)nOn F_zU+7QIY@v diff --git a/=24.0.0 b/=24.0.0 deleted file mode 100644 index feb1880..0000000 --- a/=24.0.0 +++ /dev/null @@ -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 diff --git a/=3.2.2 b/=3.2.2 deleted file mode 100644 index feb1880..0000000 --- a/=3.2.2 +++ /dev/null @@ -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 diff --git a/Dockerfile b/Dockerfile index a994a4a..06e3690 100644 --- a/Dockerfile +++ b/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"] diff --git a/Dockerfile.3.10.bak b/Dockerfile.3.10.bak new file mode 100644 index 0000000..a994a4a --- /dev/null +++ b/Dockerfile.3.10.bak @@ -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"] diff --git a/check_status.py b/check_status.py new file mode 100644 index 0000000..c58ef9b --- /dev/null +++ b/check_status.py @@ -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() diff --git a/cleanup_duplicates.py b/cleanup_duplicates.py new file mode 100644 index 0000000..9351c8a --- /dev/null +++ b/cleanup_duplicates.py @@ -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/방송중/라프텔 애니메이션') diff --git a/config.yaml b/config.yaml index 9686777..e991368 100644 --- a/config.yaml +++ b/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 명 diff --git a/config_mac.yaml b/config_mac.yaml new file mode 100644 index 0000000..e190a9e --- /dev/null +++ b/config_mac.yaml @@ -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'] diff --git a/db.sqlite b/db.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml index 7bb7969..99c65c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/ff_3_14_requirements.txt b/ff_3_14_requirements.txt new file mode 100644 index 0000000..79c2f4d --- /dev/null +++ b/ff_3_14_requirements.txt @@ -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 diff --git a/gommi.sh b/gommi.sh index 1322cab..a94dab9 100755 --- a/gommi.sh +++ b/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` diff --git a/gommi_mac.sh b/gommi_mac.sh new file mode 100755 index 0000000..f2ed24c --- /dev/null +++ b/gommi_mac.sh @@ -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 diff --git a/lib/framework/init_plugin.py b/lib/framework/init_plugin.py index 3630d63..b3e7933 100644 --- a/lib/framework/init_plugin.py +++ b/lib/framework/init_plugin.py @@ -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)}") diff --git a/lib/framework/static/css/custom.css b/lib/framework/static/css/custom.css index 5a11631..38851ec 100644 --- a/lib/framework/static/css/custom.css +++ b/lib/framework/static/css/custom.css @@ -107,4 +107,29 @@ background-color: #ffff0080 !important; margin:-2px; } -.modal { overflow: scroll !important; } \ No newline at end of file +.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; + } +} \ No newline at end of file diff --git a/lib/framework/templates/log.html b/lib/framework/templates/log.html index 6fa156e..3aa6613 100644 --- a/lib/framework/templates/log.html +++ b/lib/framework/templates/log.html @@ -1,82 +1,208 @@ {% extends "base.html" %} {% block content %} -
- -