Merge pull request #587 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: improve the native build
This commit is contained in:
commit
28ec496430
6 changed files with 155 additions and 98 deletions
33
.github/workflows/native-build.yml
vendored
33
.github/workflows/native-build.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
contents: write
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: 3.11
|
||||
PYTHON_VERSION: "3.13"
|
||||
BUN_VERSION: latest
|
||||
NODE_VERSION: 20
|
||||
TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }}
|
||||
|
|
@ -63,6 +63,35 @@ jobs:
|
|||
shell: bash
|
||||
run: cp temp-spec/app.spec ./app.spec
|
||||
|
||||
- name: Update app/library/version.py
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${TAG_NAME}"
|
||||
SHA=$(git rev-parse HEAD)
|
||||
DATE=$(date -u +"%Y%m%d")
|
||||
BRANCH=$(git branch --show-current)
|
||||
|
||||
if [ -z "${BRANCH}" ]; then
|
||||
BRANCH="${TAG_NAME}"
|
||||
fi
|
||||
|
||||
BRANCH=$(echo "${BRANCH}" | sed 's#/#-#g')
|
||||
|
||||
echo "APP_VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||
echo "APP_SHA=${SHA}" >> "$GITHUB_ENV"
|
||||
echo "APP_DATE=${DATE}" >> "$GITHUB_ENV"
|
||||
echo "APP_BRANCH=${BRANCH}" >> "$GITHUB_ENV"
|
||||
|
||||
sed -i \
|
||||
-e "s/^APP_VERSION = \".*\"/APP_VERSION = \"${VERSION}\"/" \
|
||||
-e "s/^APP_COMMIT_SHA = \".*\"/APP_COMMIT_SHA = \"${SHA}\"/" \
|
||||
-e "s/^APP_BUILD_DATE = \".*\"/APP_BUILD_DATE = \"${DATE}\"/" \
|
||||
-e "s/^APP_BRANCH = \".*\"/APP_BRANCH = \"${BRANCH}\"/" \
|
||||
app/library/version.py
|
||||
|
||||
echo "Updated version info:"
|
||||
cat app/library/version.py
|
||||
|
||||
- name: Cache Python venv
|
||||
id: cache-python
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -161,7 +190,7 @@ jobs:
|
|||
rm -rf "dist/${PKG_DIR}"
|
||||
mv "dist/YTPTube" "dist/${PKG_DIR}"
|
||||
|
||||
zip -r "release/${ZIP_NAME}" "dist/${PKG_DIR}"
|
||||
zip -yr "release/${ZIP_NAME}" "dist/${PKG_DIR}"
|
||||
|
||||
- name: Package artifact (Windows)
|
||||
if: startsWith(env.TAG_NAME, 'v') && runner.os == 'Windows'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import asyncio
|
||||
import functools
|
||||
import logging
|
||||
import multiprocessing
|
||||
import pickle
|
||||
import sys
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -16,6 +18,14 @@ from app.library.Singleton import Singleton
|
|||
LOG: logging.Logger = logging.getLogger("downloads.extractor")
|
||||
|
||||
|
||||
def _get_process_pool_kwargs() -> dict[str, Any]:
|
||||
"""Use a fork-based pool for frozen Linux builds."""
|
||||
if sys.platform == "linux" and getattr(sys, "frozen", False):
|
||||
return {"mp_context": multiprocessing.get_context("fork")}
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
class ExtractorConfig:
|
||||
"""Configuration for the extractor."""
|
||||
|
||||
|
|
@ -83,7 +93,7 @@ class ExtractorPool(metaclass=Singleton):
|
|||
self._semaphore = asyncio.Semaphore(config.concurrency)
|
||||
|
||||
if self._pool is None:
|
||||
self._pool = ProcessPoolExecutor(max_workers=config.concurrency)
|
||||
self._pool = ProcessPoolExecutor(max_workers=config.concurrency, **_get_process_pool_kwargs())
|
||||
LOG.info("Initialized extractor process pool with %s workers", config.concurrency)
|
||||
|
||||
def get_pool(self, config: ExtractorConfig) -> ProcessPoolExecutor:
|
||||
|
|
@ -366,6 +376,7 @@ async def fetch_info(
|
|||
)
|
||||
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
LOG.warning("extract_info process pool failed, falling back to thread pool url=%s error=%s", url, exc)
|
||||
return await asyncio.wait_for(
|
||||
fut=loop.run_in_executor(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,48 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from app.features.ytdlp.extractor import extract_info_sync
|
||||
from app.features.ytdlp.extractor import ExtractorConfig, ExtractorPool, _get_process_pool_kwargs, extract_info_sync
|
||||
|
||||
|
||||
class TestProcessPoolConfiguration:
|
||||
def setup_method(self):
|
||||
ExtractorPool._reset_singleton()
|
||||
|
||||
def test_uses_fork_context_for_frozen_linux(self, monkeypatch):
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor.sys.platform", "linux")
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor.sys.frozen", True, raising=False)
|
||||
|
||||
context = object()
|
||||
get_context = MagicMock(return_value=context)
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor.multiprocessing.get_context", get_context)
|
||||
|
||||
kwargs = _get_process_pool_kwargs()
|
||||
|
||||
assert kwargs == {"mp_context": context}
|
||||
get_context.assert_called_once_with("fork")
|
||||
|
||||
def test_uses_default_context_when_not_frozen_linux(self, monkeypatch):
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor.sys.platform", "linux")
|
||||
monkeypatch.delattr("app.features.ytdlp.extractor.sys.frozen", raising=False)
|
||||
|
||||
get_context = MagicMock()
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor.multiprocessing.get_context", get_context)
|
||||
|
||||
assert _get_process_pool_kwargs() == {}
|
||||
get_context.assert_not_called()
|
||||
|
||||
def test_initializes_process_pool_with_context_kwargs(self, monkeypatch):
|
||||
context = object()
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor._get_process_pool_kwargs", lambda: {"mp_context": context})
|
||||
|
||||
executor = MagicMock()
|
||||
executor_cls = MagicMock(return_value=executor)
|
||||
monkeypatch.setattr("app.features.ytdlp.extractor.ProcessPoolExecutor", executor_cls)
|
||||
|
||||
pool = ExtractorPool.get_instance()
|
||||
pool._ensure_initialized(ExtractorConfig(concurrency=3))
|
||||
|
||||
executor_cls.assert_called_once_with(max_workers=3, mp_context=context)
|
||||
assert pool.get_pool(ExtractorConfig(concurrency=3)) is executor
|
||||
|
||||
|
||||
class TestExtractInfo:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ import time
|
|||
import urllib.request
|
||||
import webbrowser
|
||||
|
||||
if __name__ == "__main__":
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
freeze_support()
|
||||
|
||||
import dotenv
|
||||
|
||||
os.environ["PYTHONUTF8"] = "1"
|
||||
|
|
@ -135,7 +140,4 @@ def main():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
freeze_support()
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if __name__ == "__main__":
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
freeze_support()
|
||||
|
||||
APP_ROOT = str((Path(__file__).parent / "..").resolve())
|
||||
if APP_ROOT not in sys.path:
|
||||
sys.path.insert(0, APP_ROOT)
|
||||
|
|
@ -179,7 +184,4 @@ class Main:
|
|||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
freeze_support()
|
||||
Main().start()
|
||||
|
|
|
|||
|
|
@ -1,93 +1,64 @@
|
|||
<template>
|
||||
<div id="main_container" class="container">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<section
|
||||
class="hero has-text-centered is-flex is-align-items-center is-justify-content-center"
|
||||
>
|
||||
<div class="goodbye-box">
|
||||
<div class="goodbye-title">Goodbye!</div>
|
||||
<p class="goodbye-subtitle">YTPTube has shut down.</p>
|
||||
<p class="goodbye-hint">You may now close this window.</p>
|
||||
<div class="goodbye-spinner" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
class="relative flex min-h-screen flex-1 items-center justify-center overflow-hidden px-4 py-6 sm:px-6"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
||||
<div
|
||||
class="absolute top-1/2 left-1/2 size-72 -translate-x-[68%] -translate-y-[70%] rounded-full bg-primary/12 blur-3xl"
|
||||
/>
|
||||
<div
|
||||
class="absolute top-1/2 left-1/2 size-64 translate-x-[8%] translate-y-[4%] rounded-full bg-secondary/12 blur-3xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UPageCard
|
||||
variant="outline"
|
||||
:ui="pageCardUi"
|
||||
class="relative w-full max-w-xl overflow-hidden bg-default/95"
|
||||
>
|
||||
<template #body>
|
||||
<div class="space-y-6 px-5 py-6 sm:px-7 sm:py-8" role="status" aria-live="polite">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full border border-default bg-elevated/60 px-3 py-1.5 text-xs font-semibold tracking-[0.22em] text-toned uppercase"
|
||||
>
|
||||
<UIcon
|
||||
name="i-lucide-loader-circle"
|
||||
class="size-4 animate-spin text-info"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Shutdown in progress</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h1 class="text-3xl font-semibold tracking-tight text-highlighted sm:text-4xl">
|
||||
Goodbye!
|
||||
</h1>
|
||||
|
||||
<p class="text-base leading-7 text-default sm:text-lg">YTPTube is shutting down.</p>
|
||||
|
||||
<p class="max-w-lg text-sm leading-6 text-toned sm:text-base">
|
||||
You may now close this window. Thanks for using YTPTube.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
color="info"
|
||||
variant="soft"
|
||||
icon="i-lucide-power"
|
||||
title="Wrapping things up"
|
||||
description="The native app is closing background services and should exit shortly."
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UPageCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.goodbye-box {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
background: radial-gradient(circle at top left, #6f42c1, #f66d9b);
|
||||
color: white;
|
||||
padding: 3rem 4rem;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 0 50px rgba(255, 255, 255, 0.15);
|
||||
max-width: 480px;
|
||||
margin: 2rem auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.goodbye-title {
|
||||
font-size: 3rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
animation: floatTitle 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.goodbye-subtitle {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.goodbye-hint {
|
||||
opacity: 0.75;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.goodbye-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 3px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 1rem auto 0;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
transform: translateY(40px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes floatTitle {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
const pageCardUi = {
|
||||
root: 'w-full bg-transparent shadow-2xl shadow-primary/5',
|
||||
container: 'w-full p-0',
|
||||
wrapper: 'w-full items-stretch',
|
||||
body: 'w-full p-0',
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue