Удаление сценариев
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,4 +12,5 @@ wheels/
|
|||||||
TASK.md
|
TASK.md
|
||||||
.env
|
.env
|
||||||
data/
|
data/
|
||||||
data_partial/
|
data_partial/
|
||||||
|
bot.db
|
||||||
@@ -61,6 +61,8 @@ uv run main.py
|
|||||||
|
|
||||||
## Администрирование
|
## Администрирование
|
||||||
|
|
||||||
Бот управляется единственным администратором, чей Telegram логин указывается в переменной окружения `ADMIN_LOGIN`. Только у администратора доступна команда `/admin`, переводящая пользовательскую сессию в состояние **ADMIN**. Команда доступна из любого другого состояния. В **ADMIN** доступна кнопка "Вернуться в пользовательский режим", переводящая пользовательскую сессию в то состояние, из которого она была переведена в **ADMIN**. После перехода режим администратора выводится сообщение с информацией о текущем состоянии датасета: количество полностью озвученных сценариев, общее количество сценариев, количество полностью озвученных дорожек, общее количество дорожек, количество уникальных дикторов, количество озвученных реплик, общее количество реплик, количество уникальных пользователей бота и т. п.
|
Бот управляется единственным администратором, чей Telegram логин указывается в переменной окружения `ADMIN_LOGIN`. Только у администратора доступна команда `/admin`, переводящая пользовательскую сессию в состояние **ADMIN**. Команда доступна из любого другого состояния. В **ADMIN** доступна кнопка "Вернуться в пользовательский режим", переводящая пользовательскую сессию в то состояние, из которого она была переведена в **ADMIN**. Исключением является случай, когда админ озвучивал дорожку из сценария, который только что был удалён. После перехода режим администратора выводится сообщение с информацией о текущем состоянии датасета: количество полностью озвученных сценариев, общее количество сценариев, количество полностью озвученных дорожек, общее количество дорожек, количество уникальных дикторов, количество озвученных реплик, общее количество реплик, количество уникальных пользователей бота и т. п.
|
||||||
|
|
||||||
В **ADMIN** администратор может отправить боту `json`-файл с новым сценарием. Это переводит пользовательскую сессию в состояние **ADMIN_UPLOAD_CONFIRM**, если файл корректен, иначе выводится сообщение об ошибке в формате файла. В **ADMIN_UPLOAD_CONFIRM** выводится сообщение с предложением добавить сценарий, а также дополнительная информация о сценарии: количество дорожек, количество реплик. Две кнопки: "Да, добавить" и "Отмена". Обе кнопки возвращают администратора в состояние **ADMIN**.
|
В **ADMIN** администратор может отправить боту `json`-файл с новым сценарием. Это переводит пользовательскую сессию в состояние **ADMIN_UPLOAD_CONFIRM**, если файл корректен, иначе выводится сообщение об ошибке в формате файла. В **ADMIN_UPLOAD_CONFIRM** выводится сообщение с предложением добавить сценарий, а также дополнительная информация о сценарии: количество дорожек, количество реплик. Две кнопки: "Да, добавить" и "Отмена". Обе кнопки возвращают администратора в состояние **ADMIN**.
|
||||||
|
|
||||||
|
В **ADMIN** доступна кнопка "Удалить сценарий". По нажатию выводится список всех сценариев в виде inline-кнопок, а также кнопка "Отмена". Выбор сценария переводит в состояние **ADMIN_DELETE_CONFIRM**. В этом состоянии выводится информация о сценарии: количество дорожек, количество реплик, количество записей. Две кнопки: "Да, удалить" и "Отмена". При удалении удаляются записи из базы данных и файлы из `data/` и `data_partial/`, а все пользователи, которые озвучивали этот сценарий, переводятся в **FIRST_REPLICA** с выводом уведомления сообщения о сохранении дорожки, если ещё есть сценарии не озвученные этим диктором, иначе в **NO_MORE_SCENARIOS**. Обе кнопки возвращают в **ADMIN**.
|
||||||
24
main.py
24
main.py
@@ -14,16 +14,21 @@ from src.handlers import (
|
|||||||
handle_admin_document,
|
handle_admin_document,
|
||||||
handle_ask_replica_number,
|
handle_ask_replica_number,
|
||||||
handle_cancel_ask_number,
|
handle_cancel_ask_number,
|
||||||
|
handle_cancel_delete,
|
||||||
|
handle_cancel_delete_list,
|
||||||
handle_cancel_restart,
|
handle_cancel_restart,
|
||||||
handle_cancel_upload,
|
handle_cancel_upload,
|
||||||
|
handle_confirm_delete,
|
||||||
handle_confirm_restart,
|
handle_confirm_restart,
|
||||||
handle_confirm_upload,
|
handle_confirm_upload,
|
||||||
|
handle_delete_scenario_list,
|
||||||
handle_exit_admin,
|
handle_exit_admin,
|
||||||
handle_replica_number_input,
|
handle_replica_number_input,
|
||||||
handle_rerecord_last,
|
handle_rerecord_last,
|
||||||
handle_rerecord_previous,
|
handle_rerecord_previous,
|
||||||
handle_restart_track,
|
handle_restart_track,
|
||||||
handle_save_track,
|
handle_save_track,
|
||||||
|
handle_select_scenario_delete,
|
||||||
handle_unexpected_text,
|
handle_unexpected_text,
|
||||||
handle_voice_message,
|
handle_voice_message,
|
||||||
start_command,
|
start_command,
|
||||||
@@ -75,6 +80,25 @@ def main() -> None:
|
|||||||
CallbackQueryHandler(handle_cancel_upload, pattern="^cancel_upload$")
|
CallbackQueryHandler(handle_cancel_upload, pattern="^cancel_upload$")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Delete scenario handlers
|
||||||
|
app.add_handler(
|
||||||
|
CallbackQueryHandler(
|
||||||
|
handle_delete_scenario_list, pattern="^delete_scenario_list$"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
app.add_handler(
|
||||||
|
CallbackQueryHandler(handle_select_scenario_delete, pattern="^delete_scenario:")
|
||||||
|
)
|
||||||
|
app.add_handler(
|
||||||
|
CallbackQueryHandler(handle_confirm_delete, pattern="^confirm_delete$")
|
||||||
|
)
|
||||||
|
app.add_handler(
|
||||||
|
CallbackQueryHandler(handle_cancel_delete, pattern="^cancel_delete$")
|
||||||
|
)
|
||||||
|
app.add_handler(
|
||||||
|
CallbackQueryHandler(handle_cancel_delete_list, pattern="^cancel_delete_list$")
|
||||||
|
)
|
||||||
|
|
||||||
# Message handlers
|
# Message handlers
|
||||||
app.add_handler(MessageHandler(filters.VOICE, handle_voice_message))
|
app.add_handler(MessageHandler(filters.VOICE, handle_voice_message))
|
||||||
app.add_handler(MessageHandler(filters.Document.ALL, handle_admin_document))
|
app.add_handler(MessageHandler(filters.Document.ALL, handle_admin_document))
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class UserState(Enum):
|
|||||||
REPEAT_REPLICA = "repeat_replica"
|
REPEAT_REPLICA = "repeat_replica"
|
||||||
ADMIN = "admin"
|
ADMIN = "admin"
|
||||||
ADMIN_UPLOAD_CONFIRM = "admin_upload_confirm"
|
ADMIN_UPLOAD_CONFIRM = "admin_upload_confirm"
|
||||||
|
ADMIN_DELETE_CONFIRM = "admin_delete_confirm"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -484,20 +485,69 @@ def get_stats() -> dict:
|
|||||||
"SELECT COUNT(*) FROM recordings"
|
"SELECT COUNT(*) FROM recordings"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
|
|
||||||
# Количество полностью озвученных дорожек (в data/)
|
# Количество полностью озвученных дорожек
|
||||||
# Это вычисляется по файловой системе, здесь примерная оценка
|
|
||||||
stats["completed_tracks"] = conn.execute("""
|
stats["completed_tracks"] = conn.execute("""
|
||||||
SELECT COUNT(*) FROM (
|
SELECT COUNT(*) FROM (
|
||||||
SELECT user_id, scenario_id, speaker_id, COUNT(*) as cnt
|
SELECT r.user_id, r.scenario_id, rep.speaker_id, COUNT(*) as cnt
|
||||||
FROM recordings r
|
FROM recordings r
|
||||||
JOIN replicas rep ON r.scenario_id = rep.scenario_id
|
JOIN replicas rep ON r.scenario_id = rep.scenario_id
|
||||||
AND r.replica_index = rep.replica_index
|
AND r.replica_index = rep.replica_index
|
||||||
GROUP BY user_id, scenario_id, speaker_id
|
GROUP BY r.user_id, r.scenario_id, rep.speaker_id
|
||||||
HAVING cnt = (
|
HAVING cnt = (
|
||||||
SELECT COUNT(*) FROM replicas
|
SELECT COUNT(*) FROM replicas rp
|
||||||
WHERE scenario_id = r.scenario_id AND speaker_id = rep.speaker_id
|
WHERE rp.scenario_id = r.scenario_id
|
||||||
|
AND rp.speaker_id = rep.speaker_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
""").fetchone()[0]
|
""").fetchone()[0]
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
def get_scenario_stats(scenario_id: str) -> dict:
|
||||||
|
"""Получает статистику конкретного сценария."""
|
||||||
|
with get_connection() as conn:
|
||||||
|
stats = {}
|
||||||
|
|
||||||
|
# Количество реплик
|
||||||
|
stats["total_replicas"] = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM replicas WHERE scenario_id = ?", (scenario_id,)
|
||||||
|
).fetchone()[0]
|
||||||
|
|
||||||
|
# Количество дорожек
|
||||||
|
stats["total_tracks"] = conn.execute(
|
||||||
|
"SELECT COUNT(DISTINCT speaker_id) FROM replicas WHERE scenario_id = ?",
|
||||||
|
(scenario_id,),
|
||||||
|
).fetchone()[0]
|
||||||
|
|
||||||
|
# Количество записей
|
||||||
|
stats["total_recordings"] = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM recordings WHERE scenario_id = ?", (scenario_id,)
|
||||||
|
).fetchone()[0]
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
def get_users_with_scenario(scenario_id: str) -> list[tuple[int, int]]:
|
||||||
|
"""Получает пользователей, озвучивающих сценарий."""
|
||||||
|
with get_connection() as conn:
|
||||||
|
cursor = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT DISTINCT u.id, u.telegram_id
|
||||||
|
FROM user_sessions us
|
||||||
|
JOIN users u ON us.user_id = u.id
|
||||||
|
WHERE us.scenario_id = ?
|
||||||
|
""",
|
||||||
|
(scenario_id,),
|
||||||
|
)
|
||||||
|
return [(row["id"], row["telegram_id"]) for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
|
def delete_scenario_data(scenario_id: str) -> None:
|
||||||
|
"""Удаляет сценарий и все связанные данные из БД."""
|
||||||
|
with get_connection() as conn:
|
||||||
|
conn.execute("DELETE FROM recordings WHERE scenario_id = ?", (scenario_id,))
|
||||||
|
conn.execute("DELETE FROM replicas WHERE scenario_id = ?", (scenario_id,))
|
||||||
|
conn.execute("DELETE FROM scenarios WHERE id = ?", (scenario_id,))
|
||||||
|
conn.commit()
|
||||||
|
logger.info(f"Deleted scenario {scenario_id} from database")
|
||||||
|
|||||||
198
src/handlers.py
198
src/handlers.py
@@ -6,14 +6,18 @@ from src.database import (
|
|||||||
User,
|
User,
|
||||||
UserSession,
|
UserSession,
|
||||||
UserState,
|
UserState,
|
||||||
|
delete_scenario_data,
|
||||||
delete_user_recordings_for_scenario,
|
delete_user_recordings_for_scenario,
|
||||||
|
get_all_scenarios,
|
||||||
get_connection,
|
get_connection,
|
||||||
get_or_create_user,
|
get_or_create_user,
|
||||||
get_replicas_for_track,
|
get_replicas_for_track,
|
||||||
get_scenario,
|
get_scenario,
|
||||||
|
get_scenario_stats,
|
||||||
get_stats,
|
get_stats,
|
||||||
get_user_session,
|
get_user_session,
|
||||||
get_users_in_state,
|
get_users_in_state,
|
||||||
|
get_users_with_scenario,
|
||||||
upsert_user_session,
|
upsert_user_session,
|
||||||
)
|
)
|
||||||
from src.decorators import answer_callback, require_state, with_user_and_session
|
from src.decorators import answer_callback, require_state, with_user_and_session
|
||||||
@@ -135,11 +139,16 @@ def get_ask_replica_number_keyboard() -> InlineKeyboardMarkup:
|
|||||||
def get_admin_keyboard() -> InlineKeyboardMarkup:
|
def get_admin_keyboard() -> InlineKeyboardMarkup:
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
[
|
[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"🗑 Удалить сценарий", callback_data="delete_scenario_list"
|
||||||
|
)
|
||||||
|
],
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
"👤 Вернуться в пользовательский режим", callback_data="exit_admin"
|
"👤 Вернуться в пользовательский режим", callback_data="exit_admin"
|
||||||
)
|
)
|
||||||
]
|
],
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -153,6 +162,28 @@ def get_admin_upload_confirm_keyboard() -> InlineKeyboardMarkup:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_admin_delete_list_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
"""Клавиатура со списком сценариев для удаления."""
|
||||||
|
scenarios = get_all_scenarios()
|
||||||
|
buttons = [
|
||||||
|
[InlineKeyboardButton(f"📄 {s.id}", callback_data=f"delete_scenario:{s.id}")]
|
||||||
|
for s in scenarios
|
||||||
|
]
|
||||||
|
buttons.append(
|
||||||
|
[InlineKeyboardButton("❌ Отмена", callback_data="cancel_delete_list")]
|
||||||
|
)
|
||||||
|
return InlineKeyboardMarkup(buttons)
|
||||||
|
|
||||||
|
|
||||||
|
def get_admin_delete_confirm_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[InlineKeyboardButton("🗑 Да, удалить", callback_data="confirm_delete")],
|
||||||
|
[InlineKeyboardButton("❌ Отмена", callback_data="cancel_delete")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# === Вспомогательные функции ===
|
# === Вспомогательные функции ===
|
||||||
|
|
||||||
|
|
||||||
@@ -580,6 +611,171 @@ async def handle_cancel_upload(
|
|||||||
upsert_user_session(session)
|
upsert_user_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
# === Delete scenario handlers ===
|
||||||
|
|
||||||
|
|
||||||
|
@answer_callback
|
||||||
|
@with_user_and_session
|
||||||
|
@require_state(UserState.ADMIN)
|
||||||
|
async def handle_delete_scenario_list(
|
||||||
|
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession
|
||||||
|
) -> None:
|
||||||
|
"""Показывает список сценариев для удаления."""
|
||||||
|
query = update.callback_query
|
||||||
|
scenarios = get_all_scenarios()
|
||||||
|
|
||||||
|
if not scenarios:
|
||||||
|
await query.edit_message_text(
|
||||||
|
"📭 Нет сценариев для удаления.\n\n" + format_admin_stats(),
|
||||||
|
reply_markup=get_admin_keyboard(),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await query.edit_message_text(
|
||||||
|
"🗑 Выберите сценарий для удаления:",
|
||||||
|
reply_markup=get_admin_delete_list_keyboard(),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # Сообщение уже такое же
|
||||||
|
session.last_bot_message_id = query.message.message_id
|
||||||
|
upsert_user_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
@answer_callback
|
||||||
|
@with_user_and_session
|
||||||
|
@require_state(UserState.ADMIN)
|
||||||
|
async def handle_select_scenario_delete(
|
||||||
|
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession
|
||||||
|
) -> None:
|
||||||
|
"""Обработчик выбора сценария для удаления."""
|
||||||
|
query = update.callback_query
|
||||||
|
scenario_id = query.data.replace("delete_scenario:", "")
|
||||||
|
|
||||||
|
stats = get_scenario_stats(scenario_id)
|
||||||
|
context.user_data["pending_delete_scenario"] = scenario_id
|
||||||
|
|
||||||
|
text = f"""🗑 Удаление сценария: {scenario_id}
|
||||||
|
|
||||||
|
📊 Статистика:
|
||||||
|
- Дорожек: {stats["total_tracks"]}
|
||||||
|
- Реплик: {stats["total_replicas"]}
|
||||||
|
- Записей: {stats["total_recordings"]}
|
||||||
|
|
||||||
|
⚠️ Это действие необратимо!
|
||||||
|
Будут удалены все записи из БД и файлы из data/ и data_partial/.
|
||||||
|
Пользователи, озвучивающие этот сценарий, будут перенаправлены."""
|
||||||
|
|
||||||
|
session.state = UserState.ADMIN_DELETE_CONFIRM
|
||||||
|
await query.edit_message_text(
|
||||||
|
text, reply_markup=get_admin_delete_confirm_keyboard()
|
||||||
|
)
|
||||||
|
session.last_bot_message_id = query.message.message_id
|
||||||
|
upsert_user_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
@answer_callback
|
||||||
|
@with_user_and_session
|
||||||
|
@require_state(UserState.ADMIN_DELETE_CONFIRM)
|
||||||
|
async def handle_confirm_delete(
|
||||||
|
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession
|
||||||
|
) -> None:
|
||||||
|
"""Подтверждение удаления сценария."""
|
||||||
|
query = update.callback_query
|
||||||
|
scenario_id = context.user_data.get("pending_delete_scenario")
|
||||||
|
|
||||||
|
if not scenario_id:
|
||||||
|
await query.edit_message_text("❌ Данные потеряны. Попробуйте снова.")
|
||||||
|
session.state = UserState.ADMIN
|
||||||
|
upsert_user_session(session)
|
||||||
|
return
|
||||||
|
|
||||||
|
from src.scenarios import delete_scenario_files
|
||||||
|
|
||||||
|
# Получаем пользователей, которые озвучивают этот сценарий
|
||||||
|
affected_users = get_users_with_scenario(scenario_id)
|
||||||
|
|
||||||
|
# Удаляем файлы и данные
|
||||||
|
deleted_files = delete_scenario_files(scenario_id)
|
||||||
|
delete_scenario_data(scenario_id)
|
||||||
|
|
||||||
|
del context.user_data["pending_delete_scenario"]
|
||||||
|
|
||||||
|
# Уведомляем и перенаправляем пользователей
|
||||||
|
for affected_user_id, telegram_id in affected_users:
|
||||||
|
if affected_user_id == user.id:
|
||||||
|
continue # Админа обработаем отдельно
|
||||||
|
|
||||||
|
try:
|
||||||
|
affected_session = get_user_session(affected_user_id)
|
||||||
|
if affected_session:
|
||||||
|
track = find_available_track(affected_user_id)
|
||||||
|
if track:
|
||||||
|
new_scenario_id, speaker_id = track
|
||||||
|
affected_session.state = UserState.FIRST_REPLICA
|
||||||
|
affected_session.scenario_id = new_scenario_id
|
||||||
|
affected_session.speaker_id = speaker_id
|
||||||
|
affected_session.replica_index = 0
|
||||||
|
msg = (
|
||||||
|
"ℹ️ Сценарий, который вы озвучивали, был удалён. Начинаем новый!"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
affected_session.state = UserState.NO_MORE_SCENARIOS
|
||||||
|
affected_session.scenario_id = None
|
||||||
|
affected_session.speaker_id = None
|
||||||
|
affected_session.replica_index = None
|
||||||
|
msg = (
|
||||||
|
"ℹ️ Сценарий, который вы озвучивали, был удалён.\n\n"
|
||||||
|
+ NO_MORE_SCENARIOS_TEXT
|
||||||
|
)
|
||||||
|
upsert_user_session(affected_session)
|
||||||
|
await context.bot.send_message(telegram_id, msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
session.state = UserState.ADMIN
|
||||||
|
await query.edit_message_text(
|
||||||
|
f"✅ Сценарий {scenario_id} удалён! (файлов: {deleted_files})\n\n"
|
||||||
|
+ format_admin_stats(),
|
||||||
|
reply_markup=get_admin_keyboard(),
|
||||||
|
)
|
||||||
|
session.last_bot_message_id = query.message.message_id
|
||||||
|
upsert_user_session(session)
|
||||||
|
logger.info(f"Scenario {scenario_id} deleted, affected: {len(affected_users)}")
|
||||||
|
|
||||||
|
|
||||||
|
@answer_callback
|
||||||
|
@with_user_and_session
|
||||||
|
@require_state(UserState.ADMIN_DELETE_CONFIRM)
|
||||||
|
async def handle_cancel_delete(
|
||||||
|
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession
|
||||||
|
) -> None:
|
||||||
|
"""Отмена удаления сценария."""
|
||||||
|
query = update.callback_query
|
||||||
|
context.user_data.pop("pending_delete_scenario", None)
|
||||||
|
session.state = UserState.ADMIN
|
||||||
|
await query.edit_message_text(
|
||||||
|
format_admin_stats(), reply_markup=get_admin_keyboard()
|
||||||
|
)
|
||||||
|
session.last_bot_message_id = query.message.message_id
|
||||||
|
upsert_user_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
@answer_callback
|
||||||
|
@with_user_and_session
|
||||||
|
@require_state(UserState.ADMIN)
|
||||||
|
async def handle_cancel_delete_list(
|
||||||
|
update: Update, context: ContextTypes.DEFAULT_TYPE, user: User, session: UserSession
|
||||||
|
) -> None:
|
||||||
|
"""Отмена выбора сценария для удаления."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.edit_message_text(
|
||||||
|
format_admin_stats(), reply_markup=get_admin_keyboard()
|
||||||
|
)
|
||||||
|
session.last_bot_message_id = query.message.message_id
|
||||||
|
upsert_user_session(session)
|
||||||
|
|
||||||
|
|
||||||
# === Message handlers ===
|
# === Message handlers ===
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -250,3 +250,25 @@ def delete_partial_track(user_id: int, scenario_id: str, speaker_id: int) -> Non
|
|||||||
f"Удалено {deleted_count} частичных записей для дорожки "
|
f"Удалено {deleted_count} частичных записей для дорожки "
|
||||||
f"{scenario_id}/{speaker_id} (user_id={user_id})"
|
f"{scenario_id}/{speaker_id} (user_id={user_id})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_scenario_files(scenario_id: str) -> int:
|
||||||
|
"""Удаляет все файлы сценария из data/ и data_partial/. Возвращает число файлов."""
|
||||||
|
deleted_count = 0
|
||||||
|
|
||||||
|
for base_dir in [DATA_DIR, DATA_PARTIAL_DIR]:
|
||||||
|
scenario_dir = base_dir / scenario_id
|
||||||
|
if scenario_dir.exists():
|
||||||
|
for file in scenario_dir.glob("*.wav"):
|
||||||
|
file.unlink()
|
||||||
|
deleted_count += 1
|
||||||
|
# Удаляем пустую папку
|
||||||
|
try:
|
||||||
|
scenario_dir.rmdir()
|
||||||
|
except OSError:
|
||||||
|
pass # Папка не пуста
|
||||||
|
|
||||||
|
if deleted_count > 0:
|
||||||
|
logger.info(f"Удалено {deleted_count} файлов для сценария {scenario_id}")
|
||||||
|
|
||||||
|
return deleted_count
|
||||||
|
|||||||
Reference in New Issue
Block a user