差分文件制作小工具

差分文件制作小工具

一、功能模块

  1. 界面布局

    • 采用垂直布局(QVBoxLayout)嵌套水平布局(QHBoxLayout),包含以下控件:
      • 文件选择:旧文件(oldFile)与新文件(newFile)路径输入框,通过 QFileDialog 实现文件浏览。
      • 参数配置:压缩字典大小(dictSize)输入框,支持自定义数值(如 32k1m)。
      • 信息显示:输出日志(QTextEdit)展示命令执行结果,命令显示框实时显示生成的 hdiffi 命令及文件处理信息。
      • 操作按钮:“创建补丁”触发差分包生成,“清空”按钮重置所有输入。
  2. 默认配置

    • 初始预设测试文件路径(V1.00.31.bin 与 32.bin),便于快速验证功能。

二、技术实现

  1. 命令执行

    • 使用 subprocess.run 调用 hdiffi 命令行工具,关键参数包括:

      python

      command = ['hdiffi', f'-c-tuz-{compress_dict_size}', old_file, new_file, out_diff_file]
    • 捕获标准输出(stdout)与错误(stderr),通过 check=True 自动检测非零返回码并抛出异常。
  2. 差分包后处理

    • 文件头注入:将差分包大小转换为 4 字节小端格式(patch_size_bytes),写入文件开头。
    • 128 字节对齐:计算 (patch_size + 34) % 128 的余数,不足则补零(b'\x00'),确保文件长度符合传输协议要求。
    • 追加元数据:读取旧文件末 30 字节,以十六进制格式追加至差分包尾部,用于校验或版本标识。
  3. 日志管理

    • 输出框实时显示命令执行状态、错误信息及文件处理详情(如补丁大小、对齐操作)。

三、核心逻辑与注意事项

  1. 关键流程

    选择文件 → 构建 hdiffi 命令 → 执行命令 → 后处理 → 显示结果
  2. 技术亮点

    • GUI 封装:将命令行操作转化为可视化界面,降低使用门槛。
    • 自动化处理:通过文件大小计算、字节操作实现差分包格式标准化。
    • 错误鲁棒性:捕获 subprocess.CalledProcessError 并显示详细错误信息。
  3. 潜在改进点

    • 路径安全:未处理含空格路径(建议用 shlex.quote 包裹路径)。
    • 参数校验:缺少压缩字典大小格式验证(如 32k 是否合法)。
    • 性能优化:大文件处理时可能因内存加载导致卡顿,可增加进度条提示。
    • 代码复用:后处理逻辑(补零、追加字节)可封装为独立函数提升可维护性。

总结

该程序通过 PyQt5 实现了 hdiffi.exe 的图形化调用,具备差分包生成、文件格式处理及日志反馈功能,适用于自动化固件升级场景。其核心价值在于将复杂的命令行操作简化为用户友好的界面交互,同时通过后处理确保差分包符合特定协议要求。后续可通过增强异常处理、参数校验及性能优化进一步提升可靠性。

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QFileDialog, QTextEdit
from PyQt5.QtCore import Qt
import subprocess
import os

class PatchCreatorApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # 设置窗口标题和大小
        self.setWindowTitle('Patch Creator')
        self.setGeometry(100, 100, 600, 400)

        # 创建布局
        layout = QVBoxLayout()

        # 创建原始文件选择框
        old_file_label = QLabel('旧文件 (oldFile):')
        self.old_file_edit = QLineEdit()
        self.old_file_edit.setReadOnly(True)
        old_file_button = QPushButton('选择文件')
        old_file_button.clicked.connect(self.select_old_file)
        old_file_layout = QHBoxLayout()
        old_file_layout.addWidget(old_file_label)
        old_file_layout.addWidget(self.old_file_edit)
        old_file_layout.addWidget(old_file_button)
        layout.addLayout(old_file_layout)


        # 创建新文件选择框
        new_file_label = QLabel('新文件 (newFile):')
        self.new_file_edit = QLineEdit()
        self.new_file_edit.setReadOnly(True)
        new_file_button = QPushButton('选择文件')
        new_file_button.clicked.connect(self.select_new_file)
        new_file_layout = QHBoxLayout()
        new_file_layout.addWidget(new_file_label)
        new_file_layout.addWidget(self.new_file_edit)
        new_file_layout.addWidget(new_file_button)
        layout.addLayout(new_file_layout)


        # 创建压缩字典大小输入框
        compress_dict_label = QLabel('压缩字典大小 (dictSize):')
        self.compress_dict_edit = QLineEdit()
        self.compress_dict_edit.setPlaceholderText('例如: 32, 64k, 1m, 1g')
        compress_dict_layout = QHBoxLayout()
        compress_dict_layout.addWidget(compress_dict_label)
        compress_dict_layout.addWidget(self.compress_dict_edit)
        layout.addLayout(compress_dict_layout)

        # 创建输出框
        output_label = QLabel('输出信息:')
        self.output_edit = QTextEdit()
        self.output_edit.setReadOnly(True)
        output_layout = QVBoxLayout()
        output_layout.addWidget(output_label)
        output_layout.addWidget(self.output_edit)
        layout.addLayout(output_layout)

        # 创建命令显示框
        command_label = QLabel('生成的命令:')
        self.command_edit = QTextEdit()
        self.command_edit.setReadOnly(True)
        command_layout = QVBoxLayout()
        command_layout.addWidget(command_label)
        command_layout.addWidget(self.command_edit)
        layout.addLayout(command_layout)


        # 创建按钮
        create_patch_button = QPushButton('创建补丁')
        clear_button = QPushButton('清空')
        button_layout = QHBoxLayout()
        button_layout.addWidget(create_patch_button)
        button_layout.addWidget(clear_button)
        layout.addLayout(button_layout)

        # 设置布局
        self.setLayout(layout)

        # 连接按钮事件
        create_patch_button.clicked.connect(self.create_patch)
        clear_button.clicked.connect(self.clear_fields)

    def select_old_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, '选择旧文件', '', 'All Files (*);;Text Files (*.txt)')
        if file_path:
            self.old_file_edit.setText(file_path)

    def select_new_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, '选择新文件', '', 'All Files (*);;Text Files (*.txt)')
        if file_path:
            self.new_file_edit.setText(file_path)

    def create_patch(self):
        old_file = self.old_file_edit.text().strip()
        new_file = self.new_file_edit.text().strip()

        if not new_file:
            self.output_edit.append("请确保选择了新文件。")
            return

        # 生成补丁文件路径
        new_file_dir = os.path.dirname(new_file)
        new_file_name = os.path.basename(new_file)
        patch_file_name = f"patch-tuz-{self.compress_dict_edit.text().strip()}-{new_file_name}"
        out_diff_file = os.path.join(new_file_dir, patch_file_name)

        # 获取压缩字典大小
        compress_dict_size = self.compress_dict_edit.text().strip()
        if not compress_dict_size:
            compress_dict_size = '32k'  # 默认值

        command = ['hdiffi', f'-c-tuz-{compress_dict_size}', old_file, new_file, out_diff_file]

        # 显示生成的命令
        # self.command_edit.setText(f"执行命令: {' '.join(command)}")

        self.output_edit.append(f"执行命令: {' '.join(command)}")

        try:
            result = subprocess.run(command, check=True, text=True, capture_output=True)
            self.output_edit.append("命令输出:")
            self.output_edit.append(result.stdout)
            self.output_edit.append("命令返回码: 0")
            self.output_edit.append(f"补丁文件已生成: {out_diff_file}")
        except subprocess.CalledProcessError as e:
            self.output_edit.append("命令执行出错:")
            self.output_edit.append(e.stderr)
            self.output_edit.append(f"命令返回码: {e.returncode}")

        # print(f"路径:{out_diff_file}")
        patch_size = os.path.getsize(out_diff_file)
        # print(f"分输出的差分文件大小: {patch_size} 字节")
        self.command_edit.append(f"差分输出的差分文件大小: {patch_size} 字节\n")
        # 将patch_size转化为16进制的格式并输出在命令显示框中
        # 将 patch_size 转换为4个字节的字节数组
        patch_size_bytes = patch_size.to_bytes(4, byteorder='little')
        hex_data1 = ' '.join(f'{byte:02X}' for byte in patch_size_bytes)
        self.command_edit.append(f"4字节: {hex_data1} 字节\n")
        # 我想将hex_data1添加在out_diff_file文件的最前面
        with open(out_diff_file, 'rb') as file:
            file_content = file.read()
        with open(out_diff_file, 'wb') as file:
            file.write(patch_size_bytes)
            file.write(file_content)
        # 计算patch_size_bytes对128取余的结果
        remainder = (patch_size+30+4) % 128
        if remainder == 0:
            self.command_edit.append(f"patch_size对128取余的结果为0,不需要补0\n")
        else:
            self.command_edit.append(f"patch_size对128取余的结果为{remainder},需要补0\n")
            # 将patch_size_bytes对128取余的结果补0
            for i in range(128-remainder):
                with open(out_diff_file, 'ab') as file:
                    file.write(b'\x00')

        # 获取旧文件路径
        old_file_path = self.new_file_edit.text().strip()
        print(old_file_path)
        # 获取旧文件内容
        with open(old_file_path, 'rb') as file:
            old_file_content = file.read()
        # 获取旧文件最后30个字节的内容
        last_30_bytes = old_file_content[-30:]
        self.command_edit.append(f"旧文件最后30个字节的内容: {last_30_bytes}\n")
        # 以16进制的格式显示last_30_bytes数据
        hex_data = ' '.join(f'{byte:02X}' for byte in last_30_bytes)
        self.command_edit.append(f"旧文件最后30个字节的内容: {hex_data}\n")
    # 将最后30个字节添加到out_diff_file这个文件的最后面
        with open(out_diff_file, 'ab') as file:
            file.write(last_30_bytes)

        patch_size = os.path.getsize(out_diff_file)
        self.command_edit.append(f"差分输出的差分文件大小: {patch_size} 字节\n")

    def clear_fields(self):
        self.old_file_edit.clear()
        self.new_file_edit.clear()
        self.compress_dict_edit.clear()
        self.output_edit.clear()
        self.command_edit.clear()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = PatchCreatorApp()
    ex.show()
    sys.exit(app.exec_())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值