feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
robust_transfer_with_status.py
|
||||
==============================
|
||||
Надежный перенос данных со смартфона с живым выводом строки состояния на Рабочий стол:
|
||||
- /home/alexives/Desktop/ПРОГРЕСС_ПЕРЕНОСА.txt (подробный вид)
|
||||
- /home/alexives/Desktop/ПРОГРЕСС_СТРОКА.txt (однострочный вид)
|
||||
- Музыка и аудиокниги НЕ трогаются (остаются на телефоне).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
DEST_ROOT = Path("/mnt/data/архив_с_телефона")
|
||||
DESKTOP_FULL = Path("/home/alexives/Desktop/ПРОГРЕСС_ПЕРЕНОСА.txt")
|
||||
DESKTOP_LINE = Path("/home/alexives/Desktop/ПРОГРЕСС_СТРОКА.txt")
|
||||
LOG_FILE = DEST_ROOT / "transfer_log.txt"
|
||||
|
||||
TARGET_FOLDERS = [
|
||||
("Download", "/storage/emulated/0/Download", DEST_ROOT / "Download", 17.0),
|
||||
("футажи", "/storage/emulated/0/футажи", DEST_ROOT / "футажи", 2.3),
|
||||
("Pictures", "/storage/emulated/0/Pictures", DEST_ROOT / "Pictures", 1.4),
|
||||
("DCIM/MyAlbums", "/storage/emulated/0/DCIM/MyAlbums", DEST_ROOT / "DCIM/MyAlbums", 59.0),
|
||||
("DCIM/Camera", "/storage/emulated/0/DCIM/Camera", DEST_ROOT / "DCIM/Camera", 16.0),
|
||||
]
|
||||
|
||||
TOTAL_TARGET_GB = sum(t[3] for t in TARGET_FOLDERS)
|
||||
|
||||
def log(msg: str):
|
||||
t_str = time.strftime("[%Y-%m-%d %H:%M:%S]")
|
||||
line = f"{t_str} {msg}"
|
||||
print(line, flush=True)
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||||
f.write(line + "\n")
|
||||
|
||||
def get_dir_size_gb(path: Path) -> float:
|
||||
if not path.exists():
|
||||
return 0.0
|
||||
try:
|
||||
res = subprocess.run(["du", "-sb", str(path)], capture_output=True, text=True, check=True)
|
||||
return int(res.stdout.split()[0]) / (1024 ** 3)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
def make_bar(percent: float, length: int = 25) -> str:
|
||||
filled = int(round(length * percent / 100))
|
||||
filled = max(0, min(length, filled))
|
||||
return "█" * filled + "░" * (length - filled)
|
||||
|
||||
def update_status(curr_folder: str, speed_mb: float = 0.0):
|
||||
total_copied_gb = 0.0
|
||||
folder_sizes = {}
|
||||
for name, _, dst_path, _ in TARGET_FOLDERS:
|
||||
sz = get_dir_size_gb(dst_path)
|
||||
folder_sizes[name] = sz
|
||||
total_copied_gb += sz
|
||||
|
||||
percent = min(100.0, (total_copied_gb / TOTAL_TARGET_GB) * 100)
|
||||
bar = make_bar(percent, 25)
|
||||
now_str = time.strftime("%H:%M:%S")
|
||||
|
||||
rem_gb = max(0.0, TOTAL_TARGET_GB - total_copied_gb)
|
||||
eta_min = (rem_gb * 1024 / speed_mb / 60) if speed_mb > 1.0 else 0
|
||||
|
||||
full_report = [
|
||||
"=" * 68,
|
||||
" 📱 СТАТУС ПЕРЕНОСА АРХИВА СО СМАРТФОНА",
|
||||
"=" * 68,
|
||||
f"Прогресс: [{bar}] {percent:.1f}%",
|
||||
f"Объём: {total_copied_gb:.2f} ГБ из ~{TOTAL_TARGET_GB:.1f} ГБ",
|
||||
f"Скорость: {speed_mb:.1f} МБ/сек",
|
||||
f"Осталось: ~{eta_min:.0f} мин." if eta_min > 0 else "Осталось: расчёт...",
|
||||
f"Текущая: «{curr_folder}»",
|
||||
"-" * 68,
|
||||
"📂 Статус папок:",
|
||||
]
|
||||
|
||||
for name, _, _, tgt in TARGET_FOLDERS:
|
||||
sz = folder_sizes.get(name, 0.0)
|
||||
if sz >= tgt * 0.98:
|
||||
st = "[ГОТОВО]"
|
||||
elif sz > 0:
|
||||
st = "[КОПИРУЕТСЯ...]"
|
||||
else:
|
||||
st = "[В ОЧЕРЕДИ]"
|
||||
full_report.append(f" • {name:<14} {st:<16} {sz:.2f} ГБ / {tgt:.1f} ГБ")
|
||||
|
||||
full_report.append("-" * 68)
|
||||
full_report.append(" (Музыка и аудиокниги оставлены на телефоне)")
|
||||
full_report.append(f"Обновлено: {now_str}")
|
||||
full_report.append("=" * 68)
|
||||
|
||||
one_line = f"[{bar}] {percent:.1f}% | {total_copied_gb:.1f}/{TOTAL_TARGET_GB:.1f} ГБ | {speed_mb:.1f} МБ/с | ETA: ~{eta_min:.0f}м | Папка: {curr_folder} ({now_str})\n"
|
||||
|
||||
try:
|
||||
with open(DESKTOP_FULL, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(full_report) + "\n")
|
||||
with open(DESKTOP_LINE, "w", encoding="utf-8") as f:
|
||||
f.write(one_line)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def ensure_adb():
|
||||
subprocess.run(["adb", "start-server"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
def main():
|
||||
log("=== ЗАПУСК ПЕРЕНОСА ДАННЫХ С МОНИТОРИНГОМ НА РАБОЧЕМ СТОЛЕ ===")
|
||||
start_time = time.time()
|
||||
last_speed_time = time.time()
|
||||
last_copied_gb = 0.0
|
||||
|
||||
update_status("Инициализация...")
|
||||
|
||||
for idx, (name, src_path, dst_path, tgt_gb) in enumerate(TARGET_FOLDERS, 1):
|
||||
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
log(f"[{idx}/{len(TARGET_FOLDERS)}] Копирование «{name}»...")
|
||||
|
||||
# Запускаем adb pull в фоне с мониторингом
|
||||
ensure_adb()
|
||||
cmd = ["adb", "-s", "3aeea825", "pull", src_path, str(dst_path.parent)]
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
speed_mb = 35.0
|
||||
while proc.poll() is None:
|
||||
time.sleep(3)
|
||||
now = time.time()
|
||||
dt = now - last_speed_time
|
||||
if dt >= 3.0:
|
||||
current_gb = sum(get_dir_size_gb(t[2]) for t in TARGET_FOLDERS)
|
||||
if last_copied_gb > 0 and current_gb >= last_copied_gb:
|
||||
speed_mb = (current_gb - last_copied_gb) * 1024 / dt
|
||||
last_copied_gb = current_gb
|
||||
last_speed_time = now
|
||||
|
||||
update_status(name, speed_mb)
|
||||
|
||||
# Завершение папки
|
||||
err = proc.stderr.read()
|
||||
if proc.returncode == 0:
|
||||
log(f"-> Папка «{name}» успешно скопирована!")
|
||||
else:
|
||||
log(f"-> Папка «{name}» завершилась с кодом {proc.returncode}: {err.strip()[:150]}")
|
||||
# Авто-перезапуск adb если отвалился
|
||||
ensure_adb()
|
||||
|
||||
update_status(name, 0.0)
|
||||
|
||||
total_time = (time.time() - start_time) / 60
|
||||
log(f"=== ВСЕ ПАПКИ УСПЕШНО ПЕРЕНЕСЕНЫ за {total_time:.1f} минут! ===")
|
||||
update_status("ЗАВЕРШЕНО НА 100%", 0.0)
|
||||
|
||||
try:
|
||||
subprocess.run(["notify-send", "Архив с телефона", "Все выбранные данные успешно перенесены на компьютер!"])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user