给我完整的 测试好一定 要能下载 是在搞不懂import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
import threading
import subprocess
import os
import time
import requests
from requests.exceptions import RequestException
class DeviceBackupTool:
def __init__(self, root):
self.root = root
self.root.title("设备备份与下载工具")
self.root.geometry("900x600")
self.root.minsize(800, 500)
# 设备状态
self.is_connected = False
self.current_device = None
# 下载状态
self.downloading = False
# 创建UI
self.setup_ui()
# 检查ADB是否在环境变量中
self.adb_path = self._find_adb()
# 更新ADB状态UI
self._update_adb_status_ui()
def _find_adb(self):
"""查找环境变量中的ADB路径"""
try:
self.log("正在检查ADB环境变量...")
# 尝试直接执行adb version命令
result = subprocess.run(
"adb version",
shell=True,
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0 and "Android Debug Bridge" in result.stdout:
self.log("找到ADB环境变量")
self.log(f"ADB版本: {result.stdout.strip()}")
# 尝试获取ADB的完整路径
which_result = subprocess.run(
"where adb" if os.name == "nt" else "which adb",
shell=True,
capture_output=True,
text=True,
timeout=10
)
if which_result.returncode == 0:
adb_full_path = which_result.stdout.strip().splitlines()[0]
self.log(f"ADB完整路径: {adb_full_path}")
return "adb" # 使用环境变量中的adb
else:
self.log("无法获取ADB完整路径,但ADB命令可用")
return "adb"
else:
self.log("执行'adb version'失败")
self.log(f"标准输出: {result.stdout.strip()}")
self.log(f"错误输出: {result.stderr.strip()}")
# 备选方案:检查常见的ADB安装位置
common_locations = [
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Android", "Sdk", "platform-tools", "adb.exe"),
os.path.join(os.environ.get("ProgramFiles", ""), "Android", "Android Studio", "sdk", "platform-tools", "adb.exe"),
"/usr/bin/adb",
"/usr/local/bin/adb"
]
for location in common_locations:
if os.path.exists(location):
self.log(f"在常见位置找到ADB: {location}")
return location
self.log("未找到ADB环境变量,请确保ADB已安装并添加到PATH中")
return None
except Exception as e:
self.log(f"检查ADB时出错: {str(e)}")
# 备选方案:尝试解析PATH环境变量
try:
path_dirs = os.environ.get("PATH", "").split(os.pathsep)
self.log("正在解析PATH环境变量...")
for dir_path in path_dirs:
adb_path = os.path.join(dir_path, "adb.exe" if os.name == "nt" else "adb")
if os.path.exists(adb_path):
self.log(f"在PATH中找到ADB: {adb_path}")
return adb_path
self.log("在PATH环境变量中未找到ADB")
except Exception as path_e:
self.log(f"解析PATH环境变量时出错: {str(path_e)}")
return None
def _update_adb_status_ui(self):
"""更新ADB状态UI显示"""
adb_status = "已找到" if self.adb_path else "未找到"
adb_color = "green" if self.adb_path else "red"
self.adb_status_label.config(
text=self.adb_path or "未找到ADB,请检查环境变量",
foreground=adb_color
)
self.connect_btn.config(
state=tk.NORMAL if self.adb_path else tk.DISABLED
)
def setup_ui(self):
"""创建用户界面"""
# 主框架
main_frame = ttk.Frame(self.root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
# ADB状态区域
adb_frame = ttk.LabelFrame(main_frame, text="ADB状态", padding=10)
adb_frame.pack(fill=tk.X, pady=5)
ttk.Label(adb_frame, text="ADB路径:").grid(row=0, column=0, sticky=tk.W, padx=5)
self.adb_status_label = ttk.Label(
adb_frame,
text="正在检查ADB...",
foreground="blue"
)
self.adb_status_label.grid(row=0, column=1, sticky=tk.W, padx=5)
# 设备连接区域
device_frame = ttk.LabelFrame(main_frame, text="设备连接", padding=10)
device_frame.pack(fill=tk.X, pady=5)
ttk.Label(device_frame, text="设备IP:").grid(row=0, column=0, sticky=tk.W, padx=5)
self.ip_entry = ttk.Entry(device_frame, width=15)
self.ip_entry.grid(row=0, column=1, padx=5)
self.ip_entry.insert(0, "192.168.31.38")
ttk.Label(device_frame, text="端口:").grid(row=0, column=2, sticky=tk.W, padx=5)
self.port_entry = ttk.Entry(device_frame, width=8)
self.port_entry.grid(row=0, column=3, padx=5)
self.port_entry.insert(0, "5555")
self.connect_btn = ttk.Button(
device_frame,
text="连接设备",
command=self.connect_device,
width=12,
state=tk.DISABLED
)
self.connect_btn.grid(row=0, column=4, padx=10)
self.status_var = tk.StringVar(value="未连接")
ttk.Label(device_frame, textvariable=self.status_var).grid(row=0, column=5, padx=10, sticky=tk.W)
# 文件管理区域
file_frame = ttk.LabelFrame(main_frame, text="文件管理", padding=10)
file_frame.pack(fill=tk.BOTH, expand=True, pady=5)
# 左侧:设备文件列表
left_frame = ttk.Frame(file_frame)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
ttk.Label(left_frame, text="设备文件列表:").pack(anchor=tk.W, padx=5, pady=5)
# 文件列表和滚动条
self.file_listbox = tk.Listbox(left_frame, width=40, height=15)
self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 0))
scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.file_listbox.config(yscrollcommand=scrollbar.set)
# 刷新和选择按钮
btn_frame = ttk.Frame(left_frame)
btn_frame.pack(fill=tk.X, pady=5)
self.refresh_btn = ttk.Button(
btn_frame,
text="刷新文件列表",
command=self.refresh_file_list,
state=tk.DISABLED
)
self.refresh_btn.pack(side=tk.LEFT, padx=5)
self.select_btn = ttk.Button(
btn_frame,
text="选择文件",
command=self.select_file,
state=tk.DISABLED
)
self.select_btn.pack(side=tk.LEFT, padx=5)
# 右侧:下载设置
right_frame = ttk.LabelFrame(file_frame, text="下载设置", padding=10)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5)
ttk.Label(right_frame, text="选择的文件:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.selected_file_var = tk.StringVar(value="未选择文件")
ttk.Label(right_frame, textvariable=self.selected_file_var).grid(row=0, column=1, sticky=tk.W, pady=5)
ttk.Label(right_frame, text="保存路径:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.save_path_var = tk.StringVar(value=os.path.join(os.path.expanduser("~"), "Downloads"))
ttk.Entry(right_frame, textvariable=self.save_path_var, width=30).grid(row=1, column=1, sticky=tk.W, pady=5)
ttk.Button(
right_frame,
text="浏览...",
command=self.browse_save_path
).grid(row=1, column=2, padx=5, pady=5)
self.download_btn = ttk.Button(
right_frame,
text="开始下载",
command=self.start_download,
state=tk.DISABLED,
width=15
)
self.download_btn.grid(row=4, column=0, columnspan=3, pady=10)
# 下载进度区域
progress_frame = ttk.LabelFrame(main_frame, text="下载进度", padding=10)
progress_frame.pack(fill=tk.X, pady=5)
self.progress_var = tk.DoubleVar(value=0)
progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
progress_bar.pack(fill=tk.X, pady=5)
self.download_status_var = tk.StringVar(value="就绪")
ttk.Label(progress_frame, textvariable=self.download_status_var).pack(anchor=tk.W, pady=5)
# 日志区域
log_notebook = ttk.Notebook(main_frame)
log_notebook.pack(fill=tk.BOTH, expand=True, pady=5)
# 操作日志
self.log_text = scrolledtext.ScrolledText(width=80, height=8, wrap=tk.WORD)
self.log_text.config(state=tk.DISABLED)
log_notebook.add(self.log_text, text="操作日志")
# ADB命令日志
self.adb_log_text = scrolledtext.ScrolledText(width=80, height=8, wrap=tk.WORD)
self.adb_log_text.config(state=tk.DISABLED)
log_notebook.add(self.adb_log_text, text="ADB命令日志")
def log(self, message, is_adb=False):
"""添加日志信息"""
timestamp = time.strftime("%H:%M:%S")
# 确保UI已初始化
if not hasattr(self, 'log_text') or not hasattr(self, 'adb_log_text'):
return
if is_adb:
self.adb_log_text.config(state=tk.NORMAL)
self.adb_log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.adb_log_text.see(tk.END)
self.adb_log_text.config(state=tk.DISABLED)
else:
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def browse_save_path(self):
"""浏览并选择保存路径"""
path = filedialog.askdirectory(title="选择保存位置")
if path:
self.save_path_var.set(path)
def connect_device(self):
"""连接到设备"""
if self.is_connected:
self.disconnect_device()
return
if not self.adb_path:
messagebox.showerror("错误", "未找到ADB,请检查环境变量", parent=self.root)
return
ip = self.ip_entry.get().strip()
port = self.port_entry.get().strip()
if not ip or not port:
messagebox.showerror("错误", "请输入设备IP和端口", parent=self.root)
return
self.log(f"尝试连接到设备: {ip}:{port}")
self.connect_btn.config(state=tk.DISABLED, text="连接中...")
self.status_var.set("连接中...")
threading.Thread(target=self._connect_device_thread, args=(ip, port), daemon=True).start()
def _connect_device_thread(self, ip, port):
"""在后台线程中执行设备连接"""
try:
# 执行ADB连接命令
command = f"{self.adb_path} connect {ip}:{port}"
self.log(command, is_adb=True)
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0 or "unable to connect" in result.stdout.lower():
raise Exception(f"连接失败: {result.stdout.strip()}")
# 验证设备是否真的连接成功
devices_command = f"{self.adb_path} devices"
self.log(devices_command, is_adb=True)
devices_output = subprocess.check_output(devices_command, shell=True, text=True)
if f"{ip}:{port}" not in devices_output:
raise Exception("设备未成功连接")
self.is_connected = True
self.current_device = f"{ip}:{port}"
self.root.after(0, lambda: self._on_device_connected())
except Exception as e:
error_msg = str(e)
self.log(f"连接失败: {error_msg}")
self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"连接失败: {msg}", parent=self.root))
self.root.after(0, lambda: self.status_var.set("连接失败"))
finally:
self.root.after(0, lambda: self.connect_btn.config(
state=tk.NORMAL,
text="断开连接" if self.is_connected else "连接设备"
))
def _on_device_connected(self):
"""设备连接成功后的处理"""
self.status_var.set(f"已连接: {self.current_device}")
self.log(f"设备连接成功: {self.current_device}")
# 启用相关按钮
self.refresh_btn.config(state=tk.NORMAL)
# 尝试获取设备文件列表
self.refresh_file_list()
def disconnect_device(self):
"""断开与设备的连接"""
if not self.is_connected:
return
try:
command = f"{self.adb_path} disconnect {self.current_device}"
self.log(command, is_adb=True)
subprocess.run(command, shell=True, capture_output=True)
self.log(f"已断开与设备的连接: {self.current_device}")
except Exception as e:
self.log(f"断开连接时出错: {str(e)}")
self.is_connected = False
self.current_device = None
self.status_var.set("未连接")
# 清空文件列表
self.file_listbox.delete(0, tk.END)
self.selected_file_var.set("未选择文件")
# 禁用相关按钮
self.refresh_btn.config(state=tk.DISABLED)
self.select_btn.config(state=tk.DISABLED)
self.download_btn.config(state=tk.DISABLED)
def refresh_file_list(self):
"""刷新设备上的文件列表"""
if not self.is_connected:
messagebox.showwarning("提示", "请先连接设备", parent=self.root)
return
self.log("正在获取设备文件列表...")
self.file_listbox.delete(0, tk.END)
self.select_btn.config(state=tk.DISABLED)
self.download_btn.config(state=tk.DISABLED)
self.selected_file_var.set("未选择文件")
threading.Thread(target=self._refresh_file_list_thread, daemon=True).start()
def _refresh_file_list_thread(self):
"""在后台线程中刷新文件列表"""
try:
# 检查设备上的备份目录是否存在
check_command = f"{self.adb_path} -s {self.current_device} shell ls /data/local/update"
self.log(check_command, is_adb=True)
result = subprocess.run(
check_command,
shell=True,
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
raise Exception("无法访问设备上的备份目录,请确保目录存在")
files = result.stdout.strip().split()
if not files:
self.log("设备上的备份目录为空")
self.root.after(0, lambda: messagebox.showinfo("提示", "设备上的备份目录为空", parent=self.root))
return
self.log(f"找到 {len(files)} 个文件")
# 更新文件列表
self.root.after(0, lambda: self._update_file_list(files))
except Exception as e:
error_msg = str(e)
self.log(f"获取文件列表失败: {error_msg}")
self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"获取文件列表失败: {msg}", parent=self.root))
def _update_file_list(self, files):
"""更新文件列表UI"""
for file in files:
self.file_listbox.insert(tk.END, file)
self.select_btn.config(state=tk.NORMAL)
self.log("文件列表已更新")
def select_file(self):
"""选择要下载的文件"""
selection = self.file_listbox.curselection()
if not selection:
messagebox.showwarning("提示", "请先选择一个文件", parent=self.root)
return
file_name = self.file_listbox.get(selection[0])
self.selected_file_var.set(file_name)
self.download_btn.config(state=tk.NORMAL)
self.log(f"已选择文件: {file_name}")
def start_download(self):
"""开始下载选中的文件"""
if not self.is_connected:
messagebox.showwarning("提示", "请先连接设备", parent=self.root)
return
file_name = self.selected_file_var.get()
if file_name == "未选择文件":
messagebox.showwarning("提示", "请先选择一个文件", parent=self.root)
return
save_path = self.save_path_var.get()
if not save_path or not os.path.exists(save_path):
messagebox.showwarning("提示", "请选择有效的保存路径", parent=self.root)
return
self.log(f"准备下载文件: {file_name}")
self.log(f"保存路径: {save_path}")
# 更新UI状态
self.downloading = True
self.download_btn.config(state=tk.DISABLED)
self.download_status_var.set("准备下载...")
self.progress_var.set(0)
threading.Thread(target=self._start_download_thread, args=(file_name, save_path), daemon=True).start()
def _start_download_thread(self, file_name, save_path):
"""在后台线程中执行下载"""
http_port = 8686
httpd_process = None
try:
# 启动设备上的HTTP服务器
self.log(f"在设备上启动HTTP服务器,端口: {http_port}")
httpd_command = f"{self.adb_path} -s {self.current_device} shell 'busybox httpd -p {http_port} -h /data/local/update &'"
self.log(httpd_command, is_adb=True)
subprocess.run(
httpd_command,
shell=True,
capture_output=True,
timeout=10
)
# 等待服务器启动
time.sleep(2)
# 构建下载URL
device_ip = self.ip_entry.get().strip()
file_url = f"http://{device_ip}:{http_port}/{file_name}"
local_path = os.path.join(save_path, file_name)
self.log(f"开始下载: {file_url}")
self.log(f"保存到: {local_path}")
# 发送HTTP请求
with requests.get(file_url, stream=True, timeout=30) as response:
response.raise_for_status()
# 获取文件总大小
total_size = int(response.headers.get('content-length', 0))
downloaded_size = 0
start_time = time.time()
# 写入文件
with open(local_path, 'wb') as file:
for data in response.iter_content(chunk_size=8192):
if not self.downloading: # 检查是否需要停止下载
raise Exception("下载已被用户取消")
file.write(data)
downloaded_size += len(data)
# 更新进度
if total_size > 0:
progress = int(downloaded_size / total_size * 100)
elapsed_time = time.time() - start_time
download_speed = downloaded_size / elapsed_time if elapsed_time > 0 else 0
# 格式化速度显示
if download_speed < 1024:
speed_text = f"{download_speed:.2f} B/s"
elif download_speed < 1024 * 1024:
speed_text = f"{download_speed/1024:.2f} KB/s"
else:
speed_text = f"{download_speed/(1024*1024):.2f} MB/s"
# 更新UI
self.root.after(0, lambda p=progress, s=speed_text: self._update_download_status(p, s))
# 下载完成
self.root.after(0, lambda: self._update_download_status(100, "下载完成"))
self.log(f"✅ 文件下载完成: {file_name}")
self.root.after(0, lambda: messagebox.showinfo("成功", f"文件下载完成: {file_name}", parent=self.root))
except RequestException as e:
error_msg = f"HTTP请求错误: {str(e)}"
self.log(f"❌ 下载失败: {error_msg}")
self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"下载失败: {msg}", parent=self.root))
except Exception as e:
error_msg = str(e)
self.log(f"❌ 下载失败: {error_msg}")
self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"下载失败: {msg}", parent=self.root))
finally:
# 停止HTTP服务器
try:
self.log(f"停止设备上的HTTP服务器,端口: {http_port}")
stop_command = f"{self.adb_path} -s {self.current_device} shell pkill -f 'busybox httpd -p {http_port}'"
self.log(stop_command, is_adb=True)
subprocess.run(
stop_command,
shell=True,
capture_output=True,
timeout=10
)
except Exception as e:
self.log(f"停止HTTP服务器时出错: {str(e)}")
# 恢复UI状态
self.downloading = False
self.root.after(0, lambda: self.download_btn.config(state=tk.NORMAL))
self.root.after(0, lambda: self.download_status_var.set("就绪"))
def _update_download_status(self, progress, speed):
"""更新下载状态UI"""
file_name = self.selected_file_var.get()
self.progress_var.set(progress)
self.download_status_var.set(f"正在下载: {file_name} - {progress}% ({speed})")
def on_closing(self):
"""窗口关闭时的处理"""
if self.downloading:
if messagebox.askyesno("确认", "正在下载中,是否要退出?", parent=self.root):
self.downloading = False
self.root.destroy()
elif self.is_connected:
if messagebox.askyesno("确认", "设备仍处于连接状态,是否要退出?", parent=self.root):
self.disconnect_device()
self.root.destroy()
else:
self.root.destroy()
if __name__ == "__main__":
# 确保文件命名不会与标准库冲突
# 不要将此文件命名为http.py或任何与标准库模块冲突的名称
root = tk.Tk()
app = DeviceBackupTool(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop() 在这里面提取 整合到 import tkinter as tk
from tkinter import scrolledtext, ttk, filedialog, messagebox
import subprocess
import threading
import os
import datetime
class ADBBackupTool:
def __init__(self, root):
self.root = root
root.title("晶晨/海思ADB备份工具")
def center_window(width, height, y_offset=0):
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2 + y_offset
return f"{width}x{height}+{x}+{y}"
root.geometry(center_window(1200, 700, -50))
self.context_menu = tk.Menu(root, tearoff=0)
self.context_menu.add_command(label="复制", command=lambda: self.copy_text())
self.context_menu.add_command(label="粘贴", command=lambda: self.paste_text())
self.context_menu.add_separator()
self.context_menu.add_command(label="全选", command=lambda: self.select_all())
self.main_frame = tk.Frame(root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.main_frame.columnconfigure(0, weight=1)
self.main_frame.rowconfigure(0, weight=1)
RIGHT_PANE_WIDTH = 300
self.cmd_frame = tk.Frame(self.main_frame)
self.cmd_frame.grid(row=0, column=0, sticky="nsew")
self.cmd_label = tk.Label(self.cmd_frame, text="命令窗口", font=('Arial', 10, 'bold'))
self.cmd_label.pack(anchor='w')
self.cmd_text = scrolledtext.ScrolledText(
self.cmd_frame,
wrap=tk.WORD,
font=('Consolas', 10),
bg='black',
fg='white',
insertbackground='white'
)
self.cmd_text.pack(fill=tk.BOTH, expand=True)
self.cmd_text.bind("<Button-3>", self.show_context_menu)
self.function_frame = tk.Frame(self.main_frame, width=RIGHT_PANE_WIDTH)
self.function_frame.grid(row=0, column=1, sticky="nsew", padx=(5, 0))
self.function_frame.pack_propagate(False)
self.conn_frame = tk.LabelFrame(self.function_frame, text="连接设置", padx=5, pady=5)
self.conn_frame.pack(fill=tk.X, pady=(0, 5))
ip_port_frame = tk.Frame(self.conn_frame)
ip_port_frame.pack(fill=tk.X, pady=2)
tk.Label(ip_port_frame, text="IP地址:").pack(side=tk.LEFT, padx=(0, 5))
self.ip_entry = tk.Entry(ip_port_frame, width=15)
self.ip_entry.pack(side=tk.LEFT, padx=(0, 10))
self.ip_entry.insert(0, "192.168.31.200")
self.ip_entry.bind("<Button-3>", self.show_context_menu)
tk.Label(ip_port_frame, text="端口号:").pack(side=tk.LEFT, padx=(0, 5))
self.port_entry = tk.Entry(ip_port_frame, width=9)
self.port_entry.pack(side=tk.LEFT)
self.port_entry.insert(0, "5555")
self.port_entry.bind("<Button-3>", self.show_context_menu)
self.connect_btn = tk.Button(
self.conn_frame,
text="连接设备",
command=self.connect_device,
bg="#4CAF50",
fg="white"
)
self.connect_btn.pack(fill=tk.X, pady=(5, 0))
self.query_frame = tk.LabelFrame(self.function_frame, text="快速查询", padx=5, pady=5)
self.query_frame.pack(fill=tk.X, pady=(0, 5))
self.cmd_var = tk.StringVar()
self.cmd_combobox = ttk.Combobox(
self.query_frame,
textvariable=self.cmd_var,
values=["请选择命令", "晶晨分区获取", "晶晨dtb备份到桌面", "海思分区获取","海思分区获取2", "保存海思分区表", "U盘路径查询"],
state="readonly",
height=5,
width=25
)
self.cmd_combobox.pack(fill=tk.X, pady=2)
self.cmd_combobox.current(0) # 默认选中"请选择命令"
self.cmd_combobox.bind("<<ComboboxSelected>>", self.update_cmd_entry)
self.cmd_mapping = {
"晶晨分区获取": "adb shell ls /dev/block",
"晶晨dtb备份到桌面": 'adb pull "/dev/dtb" "C:\\Users\\Administrator\\Desktop\\dtb"',
"海思分区获取": 'adb shell "cd /dev/block/platform/soc/by-name && ls -l"',
"海思分区获取2": "adb shell cat /proc/partitions ",
"保存海思分区表": "save_hisilicon_partitions",
"U盘路径查询": "adb shell df"
}
self.cmd_entry = tk.Entry(self.query_frame)
self.cmd_entry.pack(fill=tk.X, pady=2)
self.cmd_entry.insert(0, "可手动输入")
self.cmd_entry.bind("<Button-3>", self.show_context_menu)
self.cmd_entry.bind("<FocusIn>", self.on_entry_focus_in)
self.cmd_entry.bind("<Key>", self.on_entry_key_press)
self.run_cmd_btn = tk.Button(
self.query_frame,
text="执行命令",
command=self.execute_custom_cmd,
bg="#2196F3",
fg="white"
)
self.run_cmd_btn.pack(fill=tk.X, pady=(0, 5))
self.partition_select_frame = tk.LabelFrame(self.function_frame, text="选择要备份的分区", padx=5, pady=5)
self.partition_select_frame.pack(fill=tk.BOTH, expand=True)
self.partition_container = tk.Frame(self.partition_select_frame)
self.partition_container.pack(fill=tk.BOTH, expand=True)
self.partition_scrollbar = ttk.Scrollbar(self.partition_container)
self.partition_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.partition_canvas = tk.Canvas(
self.partition_container,
yscrollcommand=self.partition_scrollbar.set,
bg='#f0f0f0',
highlightthickness=0
)
self.partition_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.partition_scrollbar.config(command=self.partition_canvas.yview)
self.partition_list_frame = tk.Frame(self.partition_canvas, bg='#f0f0f0')
self.partition_canvas.create_window((0, 0), window=self.partition_list_frame, anchor="nw")
self.partition_list_frame.bind(
"<Configure>",
lambda e: self.partition_canvas.configure(
scrollregion=self.partition_canvas.bbox("all")
)
)
self.partition_vars = {}
self.chip_type = None
self.select_all_var = tk.BooleanVar()
self.select_all_check = tk.Checkbutton(
self.partition_list_frame,
text="全选",
variable=self.select_all_var,
command=self.toggle_select_all,
bg='#f0f0f0',
anchor='w',
font=('Arial', 10)
)
self.select_all_check.pack(fill=tk.X, pady=2, padx=5)
self.backup_btn = tk.Button(
self.function_frame,
text="备份选中分区",
command=self.start_backup,
bg="#9C27B0",
fg="white"
)
self.backup_btn.pack(fill=tk.X, pady=(5, 0))
self.status_bar = tk.Label(root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
self.check_adb()
def save_hisilicon_partitions(self):
"""保存海思分区表到桌面"""
if not hasattr(self, 'chip_type') or self.chip_type != "海思":
messagebox.showerror("错误", "请先获取海思分区表")
return
desktop_path = "C:\\Users\\Administrator\\Desktop"
save_path = os.path.join(desktop_path, "海思分区表.txt")
try:
content = self.cmd_text.get("1.0", tk.END)
lines = content.split('\n')
partition_lines = [line for line in lines if "->" in line]
if not partition_lines:
messagebox.showerror("错误", "未找到分区信息")
return
with open(save_path, 'w', encoding='utf-8') as f:
f.write("海思设备分区表\n")
f.write(f"生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("="*50 + "\n")
for line in partition_lines:
f.write(line + "\n")
self.append_cmd_output(f"分区表已保存到桌面: 海思分区表.txt")
messagebox.showinfo("成功", "分区表已保存到桌面:\n海思分区表.txt")
except Exception as e:
self.append_cmd_output(f"保存分区表出错: {str(e)}")
messagebox.showerror("错误", f"保存分区表出错:\n{str(e)}")
def on_entry_focus_in(self, event):
if self.cmd_entry.get() == "请选择命令或手动输入":
self.cmd_entry.delete(0, tk.END)
def on_entry_key_press(self, event):
if self.cmd_entry.get() == "请选择命令或手动输入":
self.cmd_entry.delete(0, tk.END)
def update_cmd_entry(self, event):
selected = self.cmd_combobox.get()
self.cmd_entry.delete(0, tk.END)
self.cmd_entry.insert(0, self.cmd_mapping.get(selected, ""))
def show_context_menu(self, event):
self.current_widget = event.widget
try:
self.current_widget.focus_set()
self.context_menu.post(event.x_root, event.y_root)
except Exception as e:
print(f"显示右键菜单出错: {e}")
return "break"
def copy_text(self):
try:
if isinstance(self.current_widget, (tk.Entry, scrolledtext.ScrolledText)):
self.current_widget.event_generate("<<Copy>>")
except Exception as e:
print(f"复制出错: {e}")
def paste_text(self):
try:
if isinstance(self.current_widget, (tk.Entry, scrolledtext.ScrolledText)):
self.current_widget.event_generate("<<Paste>>")
except Exception as e:
print(f"粘贴出错: {e}")
def select_all(self):
try:
if isinstance(self.current_widget, tk.Entry):
self.current_widget.select_range(0, tk.END)
self.current_widget.icursor(tk.END)
elif isinstance(self.current_widget, scrolledtext.ScrolledText):
self.current_widget.tag_add(tk.SEL, "1.0", tk.END)
self.current_widget.mark_set(tk.INSERT, "1.0")
self.current_widget.see(tk.INSERT)
except Exception as e:
print(f"全选出错: {e}")
def check_adb(self):
try:
result = subprocess.run("adb version", shell=True, capture_output=True, text=True)
if "Android Debug Bridge" in result.stdout:
self.append_cmd_output("ADB已就绪\n" + result.stdout.split('\n')[0])
else:
self.append_cmd_output("错误: ADB未正确安装")
messagebox.showerror("错误", "ADB未正确安装,请先安装ADB工具")
except Exception as e:
self.append_cmd_output(f"检查ADB出错: {str(e)}")
messagebox.showerror("错误", f"检查ADB出错: {str(e)}")
def execute_custom_cmd(self):
cmd = self.cmd_entry.get()
if not cmd:
self.append_cmd_output("错误: 请输入要执行的命令")
return
if cmd == "save_hisilicon_partitions":
self.save_hisilicon_partitions()
return
self.append_cmd_output(f"执行命令: {cmd}")
threading.Thread(target=self._execute_cmd, args=(cmd,), daemon=True).start()
def _execute_cmd(self, cmd):
try:
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.root.after(0, self.append_cmd_output, output.strip())
stderr = process.stderr.read()
if stderr:
self.root.after(0, self.append_cmd_output, f"错误:\n{stderr.strip()}")
self.root.after(0, self.append_cmd_output, f"命令执行完成,返回值: {process.returncode}")
if "ls /dev/block" in cmd or "by-name" in cmd:
self._parse_partitions(cmd)
except Exception as e:
self.root.after(0, self.append_cmd_output, f"执行命令出错: {str(e)}")
def _parse_partitions(self, cmd):
for widget in self.partition_list_frame.winfo_children()[1:]:
widget.destroy()
self.partition_vars.clear()
partitions = []
if "by-name" in cmd:
self.chip_type = "海思"
self.append_cmd_output("检测到海思设备分区表")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
for line in result.stdout.split('\n'):
if "->" in line:
part_name = line.split("->")[-1].split("/")[-1].strip()
if part_name:
partitions.append(part_name)
else:
self.chip_type = "晶晨"
self.append_cmd_output("检测到晶晨设备分区表")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
for line in result.stdout.split('\n'):
if line and not line.startswith("total") and not line.startswith("ls:"):
part_name = line.split()[-1]
if part_name and part_name not in ["", "by-name", "platform"]:
partitions.append(part_name)
if partitions:
self._display_partitions(partitions)
self.append_cmd_output("分区列表已更新,请在右侧选择要备份的分区")
else:
self.append_cmd_output("未找到分区信息")
def connect_device(self):
ip = self.ip_entry.get()
port = self.port_entry.get()
if not ip or not port:
self.append_cmd_output("错误: 请输入IP地址和端口号")
return
self.append_cmd_output(f"尝试连接设备: {ip}:{port}")
self.connect_btn.config(state=tk.DISABLED, text="连接中...")
threading.Thread(target=self._adb_connect, args=(ip, port), daemon=True).start()
def _adb_connect(self, ip, port):
try:
cmd = f"adb connect {ip}:{port}"
self.append_cmd_output(f"执行: {cmd}")
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.root.after(0, self.append_cmd_output, output.strip())
stderr = process.stderr.read()
if stderr:
self.root.after(0, self.append_cmd_output, f"错误:\n{stderr.strip()}")
if process.returncode == 0:
self.root.after(0, self.status_bar.config, {"text": f"已连接: {ip}:{port}"})
else:
self.root.after(0, self.append_cmd_output, "连接失败!")
except Exception as e:
self.root.after(0, self.append_cmd_output, f"连接出错: {str(e)}")
finally:
self.root.after(0, self.connect_btn.config, {"state": tk.NORMAL, "text": "连接设备"})
def _display_partitions(self, partitions):
for part in partitions:
self.partition_vars[part] = tk.BooleanVar()
cb = tk.Checkbutton(
self.partition_list_frame,
text=part,
variable=self.partition_vars[part],
anchor='w',
bg='#f0f0f0',
font=('Arial', 10)
)
cb.pack(fill=tk.X, pady=2, padx=5)
self.partition_list_frame.update_idletasks()
self.partition_canvas.config(scrollregion=self.partition_canvas.bbox("all"))
self.partition_canvas.bind_all("<MouseWheel>",
lambda event: self.partition_canvas.yview_scroll(int(-1 * (event.delta / 120)),
"units"))
def toggle_select_all(self):
select_all = self.select_all_var.get()
for var in self.partition_vars.values():
var.set(select_all)
def start_backup(self):
selected_partitions = [part for part, var in self.partition_vars.items() if var.get()]
if not selected_partitions:
messagebox.showwarning("警告", "请至少选择一个分区进行备份")
return
backup_dir = filedialog.askdirectory(title="选择备份保存目录")
if not backup_dir:
self.append_cmd_output("备份已取消")
return
self.append_cmd_output(f"将备份保存到: {backup_dir}")
self.backup_btn.config(state=tk.DISABLED, text="备份中,请稍等...")
threading.Thread(
target=self._perform_backup,
args=(selected_partitions, backup_dir),
daemon=True
).start()
def _perform_backup(self, partitions, backup_dir):
try:
self.append_cmd_output("准备备份环境...")
self._execute_adb_command("adb shell \"rm -rf /sdcard/backup\"")
self._execute_adb_command("adb shell \"mkdir -p /sdcard/backup\"")
for i, partition in enumerate(partitions):
progress = (i + 1) / len(partitions) * 100
self.append_cmd_output(f"正在备份 {partition} ({i + 1}/{len(partitions)}) - {progress:.1f}%")
backup_cmd = f"adb shell \"dd if=/dev/block/{partition} | gzip -c > /sdcard/backup/{partition}.img.gz\""
self._execute_adb_command(backup_cmd)
self.append_cmd_output("正在从设备下载备份文件...")
pull_cmd = f"adb pull /sdcard/backup \"{backup_dir}\""
self._execute_adb_command(pull_cmd)
self._execute_adb_command("adb shell \"rm -rf /sdcard/backup\"")
self.append_cmd_output(f"备份完成! 文件已保存到: {backup_dir}")
messagebox.showinfo("完成", f"备份已完成,文件保存在:\n{backup_dir}")
except Exception as e:
self.append_cmd_output(f"备份出错: {str(e)}")
messagebox.showerror("错误", f"备份过程中出错:\n{str(e)}")
finally:
self.root.after(0, self.backup_btn.config, {"state": tk.NORMAL, "text": "备份选中分区"})
def _execute_adb_command(self, cmd):
self.append_cmd_output(f"执行: {cmd}")
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.root.after(0, self.append_cmd_output, output.strip())
stderr = process.stderr.read()
if stderr:
self.root.after(0, self.append_cmd_output, f"错误:\n{stderr.strip()}")
if process.returncode != 0:
raise Exception(f"命令执行失败: {cmd}")
def append_cmd_output(self, text):
self.cmd_text.insert(tk.END, text + "\n")
self.cmd_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = ADBBackupTool(root)
root.mainloop() 这里
最新发布