upd
This commit is contained in:
@@ -192,6 +192,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модалка редактирования метаданных -->
|
||||
<div id="edit-meta-modal" class="fixed inset-0 z-[60] hidden items-center justify-center" style="background:rgba(0,0,0,0.75)">
|
||||
<div class="card rounded-2xl w-full max-w-md mx-4 p-6 flex flex-col gap-4">
|
||||
<h3 class="font-semibold text-white text-base">✏️ Редактировать название</h3>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div>
|
||||
<label class="text-xs text-gray-400 mb-1 block">Название (ru)</label>
|
||||
<input id="edit-meta-title-ru" type="text"
|
||||
class="w-full px-3 py-2 rounded-lg text-sm text-white border border-gray-700 focus:border-indigo-500 outline-none"
|
||||
style="background:#0f1117" placeholder="Название на русском">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-400 mb-1 block">Полное название</label>
|
||||
<input id="edit-meta-title-full" type="text"
|
||||
class="w-full px-3 py-2 rounded-lg text-sm text-white border border-gray-700 focus:border-indigo-500 outline-none"
|
||||
style="background:#0f1117" placeholder="Полное название">
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">⚠️ Папка не переименуется. Метаданные файлов обновятся автоматически.</p>
|
||||
</div>
|
||||
<div class="flex gap-3 justify-end mt-2">
|
||||
<button onclick="closeEditMeta()"
|
||||
class="px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white"
|
||||
style="background:#1e293b">Отмена</button>
|
||||
<button onclick="saveEditMeta()" id="edit-meta-save-btn"
|
||||
class="px-4 py-2 rounded-lg text-sm font-semibold text-white"
|
||||
style="background:#4f46e5">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модалка переименования папки -->
|
||||
<div id="rename-folder-modal" class="fixed inset-0 z-[60] hidden items-center justify-center" style="background:rgba(0,0,0,0.75)">
|
||||
<div class="card rounded-2xl w-full max-w-md mx-4 p-6 flex flex-col gap-4">
|
||||
<h3 class="font-semibold text-white text-base">📁 Переименовать папку</h3>
|
||||
<div>
|
||||
<label class="text-xs text-gray-400 mb-1 block">Новое имя папки</label>
|
||||
<input id="rename-folder-input" type="text"
|
||||
class="w-full px-3 py-2 rounded-lg text-sm text-white border border-gray-700 focus:border-indigo-500 outline-none"
|
||||
style="background:#0f1117" placeholder="Название папки">
|
||||
<p class="text-xs text-gray-500 mt-2">Специальные символы будут удалены. Пробелы заменятся на «_».</p>
|
||||
</div>
|
||||
<div class="flex gap-3 justify-end mt-2">
|
||||
<button onclick="closeRenameFolder()"
|
||||
class="px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white"
|
||||
style="background:#1e293b">Отмена</button>
|
||||
<button onclick="saveRenameFolder()" id="rename-folder-save-btn"
|
||||
class="px-4 py-2 rounded-lg text-sm font-semibold text-white"
|
||||
style="background:#4f46e5">Переименовать</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ── State ────────────────────────────────────
|
||||
const state = {
|
||||
@@ -377,6 +429,34 @@ function handleEvent(msg) {
|
||||
// Ничего не делаем визуально — файлы обновлены на диске
|
||||
break;
|
||||
|
||||
case 'manga_meta_updated':
|
||||
if(state.mangas[msg.url]) {
|
||||
state.mangas[msg.url].title = msg.title;
|
||||
state.mangas[msg.url].title_ru = msg.title_ru;
|
||||
state.mangas[msg.url].title_full = msg.title_full;
|
||||
updateMangaRow(msg.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'manga_folder_renamed':
|
||||
if(state.mangas[msg.url]) {
|
||||
state.mangas[msg.url].folder_name = msg.folder_name;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'queue_positions': {
|
||||
// Обновляем queue_position для всех манг
|
||||
const pos = msg.positions || {};
|
||||
Object.values(state.mangas).forEach(m => {
|
||||
const newPos = pos[m.url] ?? null;
|
||||
if(m.queue_position !== newPos) {
|
||||
m.queue_position = newPos;
|
||||
updateMangaRow(m.url);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'manga_done':
|
||||
if(state.mangas[msg.url]) {
|
||||
state.mangas[msg.url].status = 'done';
|
||||
@@ -948,7 +1028,7 @@ function _rowButtons(m) {
|
||||
${m.status === 'stopped' || m.status === 'failed'
|
||||
? `<button onclick="resumeManga('${u}')" title="Возобновить" style="background:#14532d;color:#86efac;padding:4px 12px;border-radius:6px;font-size:0.75rem;cursor:pointer">▶</button>`
|
||||
: ''}
|
||||
${!isActive
|
||||
${m.status === 'queued'
|
||||
? `<button onclick="prioritizeManga('${u}')" title="Загрузить первой" style="background:#312e81;color:#a5b4fc;padding:4px 8px;border-radius:6px;font-size:0.75rem;cursor:pointer">🚀</button>`
|
||||
: ''}
|
||||
<button onclick="deleteManga('${u}')" class="btn-danger" title="Удалить">✕</button>
|
||||
@@ -1185,6 +1265,17 @@ function renderModalBody(data) {
|
||||
</details>` : '<div class="text-xs text-gray-500 mb-3">Файлов на диске нет</div>'}
|
||||
|
||||
<div class="flex flex-wrap gap-2 mt-4 pt-4 border-t border-gray-800">
|
||||
<button onclick="openEditMeta('${escHtml(data.url)}')"
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-colors"
|
||||
style="background:#1e293b;color:#94a3b8;border:1px solid #334155">
|
||||
✏️ Редактировать название
|
||||
</button>
|
||||
${data.status !== 'downloading' ? `
|
||||
<button onclick="openRenameFolder('${escHtml(data.url)}', '${escHtml(data.folder_name || '')}')"
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-colors"
|
||||
style="background:#1e293b;color:#94a3b8;border:1px solid #334155">
|
||||
📁 Переименовать папку
|
||||
</button>` : ''}
|
||||
${data.status === 'done' ? `
|
||||
<button id="modal-refresh-meta-btn" onclick="refreshMetaModal('${escHtml(data.url)}')"
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold transition-colors"
|
||||
@@ -1323,6 +1414,98 @@ function escHtml(s) {
|
||||
return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// ── Edit Meta ────────────────────────────────
|
||||
let _editMetaUrl = null;
|
||||
|
||||
function openEditMeta(url) {
|
||||
_editMetaUrl = url;
|
||||
const m = state.mangas[url] || {};
|
||||
document.getElementById('edit-meta-title-ru').value = m.title_ru || m.title || '';
|
||||
document.getElementById('edit-meta-title-full').value = m.title_full || '';
|
||||
const modal = document.getElementById('edit-meta-modal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
|
||||
function closeEditMeta() {
|
||||
const modal = document.getElementById('edit-meta-modal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
_editMetaUrl = null;
|
||||
}
|
||||
|
||||
async function saveEditMeta() {
|
||||
if(!_editMetaUrl) return;
|
||||
const btn = document.getElementById('edit-meta-save-btn');
|
||||
btn.disabled = true; btn.textContent = '⏳ Сохраняем...';
|
||||
const title_ru = document.getElementById('edit-meta-title-ru').value.trim();
|
||||
const title_full = document.getElementById('edit-meta-title-full').value.trim();
|
||||
try {
|
||||
const r = await fetch('/api/mangas/update_meta', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({url: _editMetaUrl, title_ru, title_full}),
|
||||
});
|
||||
if(!r.ok) throw new Error(await r.text());
|
||||
// Update local state
|
||||
if(state.mangas[_editMetaUrl]) {
|
||||
state.mangas[_editMetaUrl].title = title_ru;
|
||||
state.mangas[_editMetaUrl].title_ru = title_ru;
|
||||
state.mangas[_editMetaUrl].title_full = title_full;
|
||||
}
|
||||
renderList();
|
||||
closeEditMeta();
|
||||
} catch(e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
} finally {
|
||||
btn.disabled = false; btn.textContent = 'Сохранить';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Rename Folder ────────────────────────────
|
||||
let _renameFolderUrl = null;
|
||||
|
||||
function openRenameFolder(url, currentFolder) {
|
||||
_renameFolderUrl = url;
|
||||
const m = state.mangas[url] || {};
|
||||
const cur = currentFolder || m.folder_name || (m.title_ru || m.title || '').replace(/[^\w\s\-]/g,'').trim().replace(/ /g,'_');
|
||||
document.getElementById('rename-folder-input').value = cur;
|
||||
const modal = document.getElementById('rename-folder-modal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
|
||||
function closeRenameFolder() {
|
||||
const modal = document.getElementById('rename-folder-modal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
_renameFolderUrl = null;
|
||||
}
|
||||
|
||||
async function saveRenameFolder() {
|
||||
if(!_renameFolderUrl) return;
|
||||
const btn = document.getElementById('rename-folder-save-btn');
|
||||
btn.disabled = true; btn.textContent = '⏳ Переименовываем...';
|
||||
const folder_name = document.getElementById('rename-folder-input').value.trim();
|
||||
try {
|
||||
const r = await fetch('/api/mangas/rename_folder', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({url: _renameFolderUrl, folder_name}),
|
||||
});
|
||||
if(!r.ok) throw new Error((await r.json()).detail || await r.text());
|
||||
const data = await r.json();
|
||||
if(state.mangas[_renameFolderUrl]) {
|
||||
state.mangas[_renameFolderUrl].folder_name = data.folder_name;
|
||||
}
|
||||
closeRenameFolder();
|
||||
} catch(e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
} finally {
|
||||
btn.disabled = false; btn.textContent = 'Переименовать';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Init ─────────────────────────────────────
|
||||
async function init() {
|
||||
_initDeleteModal();
|
||||
@@ -1344,6 +1527,12 @@ document.addEventListener('DOMContentLoaded', init);
|
||||
document.getElementById('modal').addEventListener('click', function(e) {
|
||||
if(e.target === this) closeModal();
|
||||
});
|
||||
document.getElementById('edit-meta-modal').addEventListener('click', function(e) {
|
||||
if(e.target === this) closeEditMeta();
|
||||
});
|
||||
document.getElementById('rename-folder-modal').addEventListener('click', function(e) {
|
||||
if(e.target === this) closeRenameFolder();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
131
src/api.py
131
src/api.py
@@ -55,6 +55,13 @@ download_queue: asyncio.Queue = asyncio.Queue()
|
||||
active_tasks: dict[str, asyncio.Task] = {}
|
||||
|
||||
|
||||
async def _broadcast_queue_positions():
|
||||
"""Отправляет всем клиентам актуальные позиции в очереди."""
|
||||
queue_list = list(download_queue._queue) # type: ignore
|
||||
positions = {job["url"]: i + 1 for i, job in enumerate(queue_list)}
|
||||
await ws_manager.broadcast({"type": "queue_positions", "positions": positions})
|
||||
|
||||
|
||||
async def queue_worker():
|
||||
"""Последовательно обрабатывает очередь загрузок. Перезапускается при краше."""
|
||||
while True:
|
||||
@@ -84,9 +91,12 @@ async def _queue_worker_loop():
|
||||
|
||||
if skip:
|
||||
download_queue.task_done()
|
||||
await _broadcast_queue_positions()
|
||||
continue
|
||||
|
||||
logger.info("Воркер: начинаю скачивать {}", url)
|
||||
# Позиции изменились — уведомляем клиентов
|
||||
await _broadcast_queue_positions()
|
||||
dl_task = asyncio.create_task(download_manga(
|
||||
url=url,
|
||||
fmt=fmt,
|
||||
@@ -116,6 +126,7 @@ async def _queue_worker_loop():
|
||||
finally:
|
||||
active_tasks.pop(url, None)
|
||||
download_queue.task_done()
|
||||
await _broadcast_queue_positions()
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
@@ -186,6 +197,19 @@ async def _run_auto_updates():
|
||||
|
||||
# ── Вспомогательные функции ───────────────────
|
||||
|
||||
def _safe_name(s: str) -> str:
|
||||
return re.sub(r'[^\w\s\-]', '', s).strip().replace(" ", "_")[:80]
|
||||
|
||||
|
||||
def _manga_folder(m: dict) -> Path:
|
||||
"""Возвращает папку манги с учётом кастомного имени."""
|
||||
if m.get("folder_name"):
|
||||
return OUTPUT_DIR / m["folder_name"]
|
||||
title = m.get("title") or ""
|
||||
safe_title = _safe_name(title)
|
||||
return OUTPUT_DIR / safe_title
|
||||
|
||||
|
||||
def _dir_size(path: Path) -> int:
|
||||
"""Размер директории в байтах."""
|
||||
if not path.exists():
|
||||
@@ -203,9 +227,7 @@ def _format_size(bytes_val: int) -> str:
|
||||
|
||||
def _enrich_manga(m: dict, db: StateDB) -> dict:
|
||||
"""Обогащает строку манги реальными счётчиками из таблицы chapters."""
|
||||
title = m.get("title") or ""
|
||||
safe_title = re.sub(r'[^\w\s\-]', '', title).strip().replace(" ", "_")[:80]
|
||||
size_bytes = _dir_size(OUTPUT_DIR / safe_title)
|
||||
size_bytes = _dir_size(_manga_folder(m))
|
||||
ch_done_count = db.conn.execute(
|
||||
"SELECT COUNT(*) FROM chapters WHERE manga_url=? AND status='done'",
|
||||
(m["url"],)
|
||||
@@ -238,9 +260,7 @@ def _manga_detail(manga: dict, db: StateDB) -> dict:
|
||||
chapters = db.get_all_chapters(url)
|
||||
|
||||
# Определяем директорию манги
|
||||
title = manga.get("title") or ""
|
||||
safe_title = re.sub(r'[^\w\s\-]', '', title).strip().replace(" ", "_")[:80]
|
||||
manga_dir = OUTPUT_DIR / safe_title
|
||||
manga_dir = _manga_folder(manga)
|
||||
size_bytes = _dir_size(manga_dir)
|
||||
|
||||
# Файлы
|
||||
@@ -361,6 +381,7 @@ async def add_to_queue(body: AddMangaRequest):
|
||||
"url": url,
|
||||
"format": body.format,
|
||||
})
|
||||
await _broadcast_queue_positions()
|
||||
# Запускаем фоновую задачу предпросмотра (без Chromium — быстро)
|
||||
asyncio.create_task(_fetch_preview(url))
|
||||
else:
|
||||
@@ -525,6 +546,7 @@ async def prioritize_manga(url: str):
|
||||
"url": url,
|
||||
"preempted_url": current_url,
|
||||
})
|
||||
await _broadcast_queue_positions()
|
||||
return {"ok": True}
|
||||
finally:
|
||||
db.close()
|
||||
@@ -619,6 +641,96 @@ async def _do_refresh_meta(url: str):
|
||||
db.close()
|
||||
|
||||
|
||||
class UpdateMetaRequest(BaseModel):
|
||||
url: str
|
||||
title_ru: str
|
||||
title_full: str = ""
|
||||
|
||||
|
||||
@app.post("/api/mangas/update_meta")
|
||||
async def update_meta(body: UpdateMetaRequest):
|
||||
"""Обновить метаданные манги (название серии) и применить к файлам."""
|
||||
db = StateDB()
|
||||
try:
|
||||
manga = db.get_manga(body.url)
|
||||
if not manga:
|
||||
raise HTTPException(status_code=404, detail="Манга не найдена")
|
||||
db.update_manga_meta_fields(
|
||||
body.url,
|
||||
title_ru=body.title_ru or None,
|
||||
title_full=body.title_full or None,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
# Обновляем метаданные в файлах фоново
|
||||
asyncio.create_task(_do_refresh_meta(body.url))
|
||||
await ws_manager.broadcast({
|
||||
"type": "manga_meta_updated",
|
||||
"url": body.url,
|
||||
"title": body.title_ru,
|
||||
"title_ru": body.title_ru,
|
||||
"title_full": body.title_full,
|
||||
})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class RenameFolderRequest(BaseModel):
|
||||
url: str
|
||||
folder_name: str
|
||||
|
||||
|
||||
@app.post("/api/mangas/rename_folder")
|
||||
async def rename_folder(body: RenameFolderRequest):
|
||||
"""Переименовать папку манги и обновить пути в БД."""
|
||||
new_folder = _safe_name(body.folder_name)
|
||||
if not new_folder:
|
||||
raise HTTPException(status_code=400, detail="Некорректное имя папки")
|
||||
|
||||
db = StateDB()
|
||||
try:
|
||||
manga = db.get_manga(body.url)
|
||||
if not manga:
|
||||
raise HTTPException(status_code=404, detail="Манга не найдена")
|
||||
if manga["status"] == "downloading" and body.url in active_tasks:
|
||||
raise HTTPException(status_code=400, detail="Нельзя переименовать — манга загружается")
|
||||
|
||||
old_dir = _manga_folder(manga)
|
||||
new_dir = OUTPUT_DIR / new_folder
|
||||
|
||||
if old_dir != new_dir:
|
||||
if new_dir.exists():
|
||||
raise HTTPException(status_code=400, detail=f"Папка '{new_folder}' уже существует")
|
||||
if old_dir.exists():
|
||||
import shutil
|
||||
shutil.move(str(old_dir), str(new_dir))
|
||||
logger.info("Папка переименована: {} → {}", old_dir, new_dir)
|
||||
# Обновляем пути в таблице chapters
|
||||
chapters = db.get_all_chapters(body.url)
|
||||
for ch in chapters:
|
||||
updates = {}
|
||||
for col in ("output_cbz", "output_pdf", "output_epub"):
|
||||
p = ch.get(col)
|
||||
if p and str(old_dir) in p:
|
||||
updates[col] = p.replace(str(old_dir), str(new_dir))
|
||||
if updates:
|
||||
sets = ", ".join(f"{k}=?" for k in updates)
|
||||
db.conn.execute(
|
||||
f"UPDATE chapters SET {sets} WHERE chapter_url=?",
|
||||
[*updates.values(), ch["chapter_url"]]
|
||||
)
|
||||
db.conn.commit()
|
||||
|
||||
db.set_folder_name(body.url, new_folder)
|
||||
await ws_manager.broadcast({
|
||||
"type": "manga_folder_renamed",
|
||||
"url": body.url,
|
||||
"folder_name": new_folder,
|
||||
})
|
||||
return {"ok": True, "folder_name": new_folder}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.post("/api/mangas/force_redownload")
|
||||
async def force_redownload(url: str):
|
||||
"""Сбросить все главы на pending и поставить мангу заново в очередь."""
|
||||
@@ -641,6 +753,7 @@ async def force_redownload(url: str):
|
||||
db.update_manga_status(url, "queued")
|
||||
await download_queue.put({"url": url, "fmt": manga["format"], "resume": False})
|
||||
await ws_manager.broadcast({"type": "manga_queued", "url": url, "format": manga["format"]})
|
||||
await _broadcast_queue_positions()
|
||||
return {"ok": True}
|
||||
finally:
|
||||
db.close()
|
||||
@@ -664,6 +777,7 @@ async def stop_manga(url: str):
|
||||
# Манга в очереди (ещё не начата) — просто помечаем как stopped
|
||||
db.update_manga_status(url, "stopped")
|
||||
await ws_manager.broadcast({"type": "manga_stopped", "url": url})
|
||||
await _broadcast_queue_positions()
|
||||
|
||||
return {"ok": True}
|
||||
finally:
|
||||
@@ -684,6 +798,7 @@ async def resume_manga(url: str):
|
||||
db.update_manga_status(url, "queued")
|
||||
await download_queue.put({"url": url, "fmt": manga["format"]})
|
||||
await ws_manager.broadcast({"type": "manga_queued", "url": url, "format": manga["format"]})
|
||||
await _broadcast_queue_positions()
|
||||
return {"ok": True}
|
||||
finally:
|
||||
db.close()
|
||||
@@ -701,9 +816,7 @@ async def delete_manga(url: str, delete_files: bool = False):
|
||||
|
||||
deleted_size = 0
|
||||
if delete_files:
|
||||
title = manga.get("title") or ""
|
||||
safe_title = re.sub(r'[^\w\s\-]', '', title).strip().replace(" ", "_")[:80]
|
||||
manga_dir = OUTPUT_DIR / safe_title
|
||||
manga_dir = _manga_folder(manga)
|
||||
if manga_dir.exists() and manga_dir.is_dir():
|
||||
deleted_size = _dir_size(manga_dir)
|
||||
import shutil
|
||||
|
||||
27
src/state.py
27
src/state.py
@@ -79,6 +79,7 @@ class StateDB:
|
||||
("mangas", "last_checked_at", "TEXT"),
|
||||
("mangas", "started_at", "TEXT"),
|
||||
("mangas", "finished_at", "TEXT"),
|
||||
("mangas", "folder_name", "TEXT"),
|
||||
]
|
||||
for table, col, typedef in migrations:
|
||||
try:
|
||||
@@ -110,6 +111,32 @@ class StateDB:
|
||||
""", (title, title_ru, title_full, pub_status, chapters_total, _now(), url))
|
||||
self.conn.commit()
|
||||
|
||||
def set_folder_name(self, url: str, folder_name: str):
|
||||
"""Установить кастомное имя папки для манги."""
|
||||
self.conn.execute("""
|
||||
UPDATE mangas SET folder_name=?, updated_at=? WHERE url=?
|
||||
""", (folder_name, _now(), url))
|
||||
self.conn.commit()
|
||||
|
||||
def update_manga_meta_fields(self, url: str, title_ru: str = None,
|
||||
title_full: str = None):
|
||||
"""Обновить пользовательские метаданные манги (название серии)."""
|
||||
fields = []
|
||||
params = []
|
||||
if title_ru is not None:
|
||||
fields.extend(["title_ru=?", "title=?"])
|
||||
params.extend([title_ru, title_ru])
|
||||
if title_full is not None:
|
||||
fields.append("title_full=?")
|
||||
params.append(title_full)
|
||||
if not fields:
|
||||
return
|
||||
fields.append("updated_at=?")
|
||||
params.append(_now())
|
||||
params.append(url)
|
||||
self.conn.execute(f"UPDATE mangas SET {', '.join(fields)} WHERE url=?", params)
|
||||
self.conn.commit()
|
||||
|
||||
def set_auto_update(self, url: str, enabled: bool):
|
||||
self.conn.execute("""
|
||||
UPDATE mangas SET auto_update=?, updated_at=? WHERE url=?
|
||||
|
||||
@@ -92,7 +92,12 @@ async def download_manga(
|
||||
"chapters_total": len(manga.chapters),
|
||||
})
|
||||
|
||||
folder_name = _safe_name(manga.title_ru or manga.title)
|
||||
# Используем кастомное имя папки из БД, если задано
|
||||
_db_manga = await db_call(db.get_manga, url)
|
||||
folder_name = (
|
||||
(_db_manga.get("folder_name") if _db_manga else None)
|
||||
or _safe_name(manga.title_ru or manga.title)
|
||||
)
|
||||
manga_dir = output_dir / folder_name
|
||||
manga_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user