163 lines
6.4 KiB
Python
Executable File
163 lines
6.4 KiB
Python
Executable File
#!/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()
|