()
| 622 | try: |
| 623 | with os.fdopen(fd, "wb") as f: |
| 624 | f.write(data) |
| 625 | f.flush() |
| 626 | os.fsync(f.fileno()) |
| 627 | temp.replace(target) |
| 628 | mode = item.get("mode") |
| 629 | if isinstance(mode, int): |
| 630 | os.chmod(target, mode) |
| 631 | finally: |
| 632 | temp.unlink(missing_ok=True) |
| 633 | SUMMARY.success(f"Restored {target}") |
| 634 | |
| 635 | reload_termux_settings() |
| 636 | |
| 637 | def choose_backup_interactive() -> Optional[Path]: |
| 638 | backups = list_backups() |
| 639 | if not backups: |
| 640 | print(c(f"No backups found in {BACKUPS_DIR}", C.WARN)) |
| 641 | return None |
| 642 | if NON_INTERACTIVE: |
| 643 | return backups[0] |
| 644 | |
| 645 | print(c("\nAvailable backups:", C.INFO)) |
| 646 | for idx, backup in enumerate(backups, 1): |
| 647 | print(f" {idx}) {backup.name}") |
| 648 | while True: |
| 649 | answer = input(c("Pick a backup number (0 to cancel): ", C.INFO)).strip() |
| 650 | if answer == "0": |
| 651 | return None |
| 652 | if answer.isdigit() and 1 <= int(answer) <= len(backups): |
| 653 | return backups[int(answer) - 1] |
| 654 | print(c("Invalid choice.", C.WARN)) |
| 655 | |
| 656 | |
| 657 | # ----------------------------------------------------------------------------- |
| 658 | # Termux package management |
| 659 | # ----------------------------------------------------------------------------- |
| 660 | |
| 661 | |
| 662 | def dpkg_installed(package: str) -> bool: |
| 663 | if not shutil.which("dpkg-query"): |
| 664 | return False |
| 665 | result = run_cmd( |
| 666 | ["dpkg-query", "-W", "-f=${Status}", package], |
no test coverage detected