WebApi+Python PyQ5实现大文件下载,Ui增加进度条和下载速率+已验证uos和Windows环境

Web Api接品代码:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Concurrent;
using System.Net;

namespace LargeFileHandling.Controllers
{
    [ApiController]
    [Route("[controller]")]

    public class TestProgramFileManagerController : Controller
    {
        private readonly string _testProgramPath;

        // 用于存储文件块的信息
        private static ConcurrentDictionary<string, int> fileChunkTracker = new ConcurrentDictionary<string, int>();

        public TestProgramFileManagerController(FileStorageSettings fileStorageSettings)
        {
            _testProgramPath = fileStorageSettings.TestProgramPath;

            //确保上传文件夹存在
            if (!Directory.Exists(_testProgramPath))
            {
                Directory.CreateDirectory(_testProgramPath);//如果不存在则创建
            }
        }

        //上传测试日志
        [HttpPost("uploadTestProgram")]
        /*
         testProgramFile:测试程式文件
         ProgramDriectory:程式存放目录
         */
        public async Task<IActionResult> UploadTestProgram(IFormFile testProgramFile, string programDriectory)
        {
            try
            {
                //判断产品文件夹是否存在
                if (!Directory.Exists($@"{_testProgramPath}\{programDriectory}"))
                {
                    Directory.CreateDirectory($@"{_testProgramPath}\{programDriectory}");//如果不存在则创建
                }

                if (testProgramFile == null || testProgramFile.Length == 0)
                {
                    return BadRequest(new { code = 400, message = "Upload failed. No file provided.", data = new { } });
                }

                var filename = Path.GetFileName(testProgramFile.FileName);
                var filePath = Path.Combine($@"{_testProgramPath}\{programDriectory}", filename);

                await using (var stream = System.IO.File.Create(filePath))
                {
                    await testProgramFile.CopyToAsync(stream);
                }

                return Ok(new { code = 200, message = "test log file uploaded successfully.", data = new { filePath } });
            }
            catch (Exception ex)
            {
                // 增加更详细的错误日志
                Console.WriteLine($"Error: {ex.GetType().FullName}: {ex.Message}");
                Console.WriteLine($"Stack Trace: {ex.StackTrace}");

                // 记录到日志文件或其他监控工具(根据您的日志配置)
                // Logger.LogError(ex, "Error listing files");

                return StatusCode(500, new { code = 500, message = "UploadTestProgram Error.", details = ex.Message });
            }
        }

        // Check if file exists and return JSON response
        /*
         * 查找测试程式文件是否存在
         testProgramFile:测试程式文件
         programDriectory:程式目录
         */
        [HttpGet("exists")]
        public IActionResult CheckProgramFileExists(string testProgramFile, string programDriectory)
        {
            var filePath = Path.Combine($@"{_testProgramPath}\{programDriectory}", testProgramFile);
            if (System.IO.File.Exists(filePath))
            {
                return Ok(new { code = 200, message = "File exists.", data = new { filePath } });
            }
            else
            {
                return NotFound(new { code = 404, message = "File not found.", data = new { } });
            }
        }

        // Actual file download operation
        /*
         下载测试程式
        testProgramFile:程式文件
        programDriectory:程式目录
         */
        [HttpGet("download")]
        public IActionResult DownloadTestProgramFile(string testProgramFile, string programDriectory)
        {
            try
            {
                //var filePath = Path.Combine(_uploadFolderPath, fileName);
                // 解码路径
                string decodedPath = WebUtility.UrlDecode($@"{_testProgramPath}\{programDriectory}\{testProgramFile}");

                if (!System.IO.File.Exists(decodedPath))
                {
                    return NotFound(new { code = 404, message = "File not found.", data = new { } });
                }

                var stream = new FileStream(decodedPath, FileMode.Open, FileAccess.Read);
                return new FileStreamResult(stream, "application/octet-stream")
                {
                    FileDownloadName = testProgramFile
                };
            }
            catch (Exception ex)
            {
                // 增加更详细的错误日志
                Console.WriteLine($"Error: {ex.GetType().FullName}: {ex.Message}");
                Console.WriteLine($"Stack Trace: {ex.StackTrace}");

                // 记录到日志文件或其他监控工具(根据您的日志配置)
                // Logger.LogError(ex, "Error listing files");

                return StatusCode(500, new { code = 500, message = "Internal Server Error.", details = ex.Message });
            }

        }

        /// <summary>
        /// 上传测试程序块
        /// </summary>
        /// <param name="testProgramFile">测试程式文件</param>
        /// <param name="programDriectory">文件上传目录</param>
        /// <param name="fileName">文件名</param>
        /// <param name="chunkIndex">文件对应块指针</param>
        /// <param name="totalChunks">合总块</param>
        /// <returns></returns>
        [HttpPost("uploadtestProgramChunk")]
        public async Task<IActionResult> UploadtestProgramChunk(IFormFile testProgramFile, string programDriectory, string fileName, int chunkIndex, int totalChunks)
        {
            if (testProgramFile == null || testProgramFile.Length == 0)
            {
                return BadRequest(new { code = 400, message = "Upload failed. No chunk provided." });
            }

            string tempFilePath = Path.Combine($@"{_testProgramPath}\{programDriectory}", $"{fileName}.part{chunkIndex}");
            await using (var fileStream = System.IO.File.Create(tempFilePath))
            {
                await testProgramFile.CopyToAsync(fileStream);
            }

            fileChunkTracker.AddOrUpdate(fileName, 1, (key, oldValue) => oldValue + 1);
            if (fileChunkTracker[fileName] == totalChunks)
            {
                bool mergeSuccess = await MergeFileChunks(fileName, totalChunks, programDriectory);
                if (!mergeSuccess)
                {
                    return StatusCode(500, new { code = 500, message = "Error merging file chunks." });
                }

                fileChunkTracker.TryRemove(fileName, out _);
                return Ok(new { code = 200, message = "File uploaded and merged successfully." });
            }

            return Ok(new { code = 200, message = "Chunk uploaded successfully." });
        }

        /// <summary>
        /// 合并文件块
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="totalChunks">总块数</param>
        /// <param name="programDriectory">程式目录</param>
        /// <returns></returns>
        private async Task<bool> MergeFileChunks(string fileName, int totalChunks, string programDriectory)
        {
            string finalFilePath = Path.Combine($@"{_testProgramPath}\{programDriectory}", fileName);
            try
            {
                // Check if all chunk files exist before starting to merge
                for (int i = 0; i < totalChunks; i++)
                {
                    string tempFilePath = Path.Combine($@"{_testProgramPath}\{programDriectory}", $"{fileName}.part{i}");
                    if (!System.IO.File.Exists(tempFilePath))
                    {
                        Console.WriteLine($"Missing chunk: {tempFilePath}");
                        return false; // If any chunk is missing, abort the merge
                    }
                }

                await using (var finalStream = new FileStream(finalFilePath, FileMode.Create))
                {
                    for (int i = 0; i < totalChunks; i++)
                    {
                        string tempFilePath = Path.Combine($@"{_testProgramPath}\{programDriectory}", $"{fileName}.part{i}");
                        await using (var sourceStream = new FileStream(tempFilePath, FileMode.Open))
                        {
                            await sourceStream.CopyToAsync(finalStream);
                        }
                        System.IO.File.Delete(tempFilePath); // Delete the chunk file after it's been merged
                    }
                }
                return true; // All chunks have been merged successfully
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred while merging chunks: {ex.Message}");
                return false;
            }
        }

        /// <summary>
        /// 合并单元
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="totalChunks">总块数</param>
        /// <param name="programDriectory">程式目录</param>
        /// <returns></returns>
        [HttpPost("mergeChunks")]
        public async Task<IActionResult> MergeChunks(string fileName, int totalChunks, string programDriectory)
        {
            bool mergeSuccess = await MergeFileChunks(fileName, totalChunks, programDriectory);
            if (!mergeSuccess)
            {
                return StatusCode(500, new { code = 500, message = "Error merging file chunks." });
            }

            return Ok(new { code = 200, message = "File merged successfully." });
        }

        /// <summary>
        /// 列出下载程式清单
        /// </summary>
        /// <param name="programDriectory">程式目录</param>
        /// <returns></returns>
        [HttpGet("listdownFilePath")]
        public IActionResult ListDownloadableTestProgramFiles(string programDriectory)
        {
            try
            {
                // 解码路径
                string decodedPath = WebUtility.UrlDecode($@"{_testProgramPath}\{programDriectory}");

                if (!Directory.Exists(decodedPath))
                {
                    return new JsonResult(new { code = 404, message = "Upload folder not found." }) { StatusCode = StatusCodes.Status404NotFound };
                }

                var files = Directory.GetFiles(decodedPath, "*.*", SearchOption.AllDirectories)
                                     .Select(file => file.Replace(decodedPath, "").TrimStart(Path.DirectorySeparatorChar))
                                     .ToList();

                return new JsonResult(new { code = 200, message = "File list retrieved successfully.", data = new { files } }) { StatusCode = StatusCodes.Status200OK };
            }
            catch (Exception ex)
            {
                // 增加更详细的错误日志
                Console.WriteLine($"Error: {ex.GetType().FullName}: {ex.Message}");
                Console.WriteLine($"Stack Trace: {ex.StackTrace}");

                // 记录到日志文件或其他监控工具(根据您的日志配置)
                // Logger.LogError(ex, "Error listing files");

                return StatusCode(500, new { code = 500, message = "Internal Server Error.", details = ex.Message });
            }
        }
    }
}

 

import configparser
import logging
import sys
import time  # 修改这里
#import time as time_module  # 修改这里,避免命名冲突
import requests
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QProgressBar, QLabel, QDesktopWidget, \
    QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QPoint
import os
import platform
import subprocess


class DownloadThread(QThread):
    progress_updated = pyqtSignal(int)
    speed_updated = pyqtSignal(str)
    download_completed = pyqtSignal()  # 新信号,表示下载完成
    current_file = pyqtSignal(str)  # 新增信号,传递当前下载文件的名称
    # 其他信号保持不变
    error_occurred = pyqtSignal()  # 新增信号,表示发生了错误

    def __init__(self, url, params_list, save_path_prefix):
        super().__init__()
        self.url = url
        self.params_list = params_list  # 现在是参数列表,每个元素是一个文件的参数

        self.save_path_prefix = save_path_prefix.replace("\\", "/")  # 确保使用正斜杠

    #执行
    def run(self):
        for params in self.params_list:
            # 用于Windows路径和兼容UNIX-like系统的路径处理
            file_path = params['testProgramFile'].replace("\\", "/")
            self.current_file.emit(file_path)  # 发出当前下载文件的名称
            # 检查文件路径中是否包含目录
            if "/" in file_path:
                # 存在目录,创建目录结构
                full_path = os.path.join(self.save_path_prefix, file_path)
                directory = os.path.dirname(full_path)
                if not os.path.exists(directory):
                    os.makedirs(directory, exist_ok=True)
            else:
                # 文件不包含目录,直接保存到根目录下
                full_path = os.path.join(self.save_path_prefix, file_path)

            try:
                with requests.get(self.url, params=params, stream=True) as r:
                    r.raise_for_status()
                    total_length = int(r.headers.get('content-length', 0))
                    download_length = 0
                    start_time = time.time()
                    with open(full_path, 'wb') as f:
                        for chunk in r.iter_content(chunk_size=1024):
                            if chunk:
                                f.write(chunk)
                                download_length += len(chunk)
                                if total_length > 0:
                                    progress = int(100 * download_length / total_length)
                                    self.progress_updated.emit(progress)  # 发出进度更新信号

                                elapsed_time = time.time() - start_time
                                if elapsed_time > 0:
                                    speed = download_length / elapsed_time
                                    self.speed_updated.emit(f"{speed / 1024:.2f} KB/s")  # 发出速度更新信号
            except Exception as e:
                print(f"下载{file_path}失败: {e}")
                self.error_occurred.emit()

        self.download_completed.emit()

        # 检查操作系统,如果不是Windows,则执行chmod命令
        print(platform.system())
        if platform.system() != "Windows":
            try:
                # 构建命令字符串
                cmd = f"chmod 777 {self.save_path_prefix} -R"
                subprocess.run(cmd, shell=True, check=True)
            except subprocess.CalledProcessError as e:
                print(f"修改权限失败: {e}")

    # def run(self):
    #     try:
    #         for params in self.params_list:
    #             file_name = params['testProgramFile']
    #             self.current_file.emit(file_name)  # 发出当前文件名信号
    #             save_path = f"{self.save_path_prefix}{file_name}"
    #             try:
    #                 with requests.get(self.url, params=params, stream=True) as r:
    #                     r.raise_for_status()  # 确保请求成功
    #                     total_length = int(r.headers.get('content-length'))
    #                     download_length = 0
    #                     start_time = time.time()
    #                     with open(save_path, 'wb') as f:
    #                         for chunk in r.iter_content(chunk_size=1024):
    #                             if chunk:
    #                                 f.write(chunk)
    #                                 download_length += len(chunk)
    #                                 progress = int(100 * download_length / total_length)
    #                                 self.progress_updated.emit(progress)
    #
    #                                 elapsed_time = time.time() - start_time
    #                                 if elapsed_time > 0:
    #                                     speed = download_length / elapsed_time
    #                                     self.speed_updated.emit(f"{speed / 1024:.2f} KB/s")
    #             except Exception as e:
    #                 print(f"下载{file_name}失败: {e}")
    #
    #         self.download_completed.emit()  # 下载完成后发出信号
    #
    #     except Exception as e:
    #         print(f"下载过程中发生错误: {e}")
    #         self.error_occurred.emit()


class MainWindow(QWidget):
    # 定义颜色代码
    COLOR_SUCCESS = "\033[1;32;43m"
    COLOR_ERROR = "\033[1;31m"
    COLOR_RESET = "\033[0m"

    def __init__(self):
        super().__init__()
        self.initUI()
        self.start_download()  # 添加这行代码来在窗口初始化后自动开始下载
        self.config = configparser.ConfigParser()

        # 生成日志信息
        self.logger = logging.getLogger('my_logger')  # 步骤1 创建日志记录器
        self.logger.setLevel(logging.DEBUG)  # 步骤2 将指定日志级别
        self.file_handler = logging.FileHandler('log/log.txt')  # 步骤3 创建文件处理器
        self.formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')  # 步骤4 创建格式化器
        self.file_handler.setFormatter(self.formatter)  # 步骤4 将格式化器添加到处理器
        self.logger.addHandler(self.file_handler)  # 步骤5 将处理器添加到日志记录器

        # 读取配置
        self.config.read('./Conf/config.conf', encoding='utf-8')  # 读取配置文件,如果配置文件不存在则创建

        # 读取Test_WebApi接口配置
        self.TestWebApi = self.config.get('TestWepApi', 'url')


    def initUI(self):
        self.setWindowTitle('File Downloader')
        self.setGeometry(100, 100, 400, 200)
        self.setWindowFlags(Qt.FramelessWindowHint)  # 设置为无边框窗口

        self.layout = QVBoxLayout()

        self.progress_bar = QProgressBar(self)
        self.speed_label = QLabel('Speed: 0 KB/s', self)
        self.current_file_label = QLabel('Current File: None', self)  # 新增标签
        #self.download_button = QPushButton('Download', self)
        #self.download_button.clicked.connect(self.start_download)

        self.layout.addWidget(self.progress_bar)
        self.layout.addWidget(self.speed_label)
        self.layout.addWidget(self.current_file_label)  # 将新标签添加到布局
        #self.layout.addWidget(self.download_button)

        self.setLayout(self.layout)
        self.centerWindow()#窗体居中显示

        self.oldPos = self.pos()

    def mousePressEvent(self, event):
        self.oldPos = event.globalPos()

    def mouseMoveEvent(self, event):
        delta = QPoint(event.globalPos() - self.oldPos)
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.oldPos = event.globalPos()

    #获取文件列表
    def get_file_list(self):
        url = f'{self.TestWebApi}/TestProgramFileManager/listdownFilePath'
        params = {'programDriectory': 'Online_UpdateProgram\\X86_UOS'}
        try:
            response = requests.get(url, params=params)
            response.raise_for_status()  # 这将抛出异常,如果请求返回了一个错误
            file_list = response.json().get('data', {}).get('files', [])
            return file_list
        except requests.RequestException as e:
            #print(f"获取文件列表失败: {e}")
            self.ShowLog(f"获取文件列表失败: {e}",False)
            return []

    #执行文件下载
    # MainWindow中start_download方法的修改
    def start_download(self):
        try:
            file_list = self.get_file_list()
            if not file_list:
                #print("文件列表为空,无法下载")
                self.ShowLog("文件列表为空,无法下载",False)
                return

            params_list = [{'testProgramFile': file_name, 'programDriectory': 'Online_UpdateProgram\\X86_UOS'} for
                           file_name in file_list]
            self.download_thread = DownloadThread(f'{self.TestWebApi}/TestProgramFileManager/download',
                                                  params_list, "")
            self.download_thread.progress_updated.connect(self.progress_bar.setValue)
            self.download_thread.speed_updated.connect(self.speed_label.setText)
            self.download_thread.current_file.connect(self.current_file_label.setText)  # 连接新信号
            self.download_thread.start()

            # 获取文件列表和初始化下载线程的代码保持不变
            self.download_thread.download_completed.connect(self.on_download_completed)
            self.download_thread.error_occurred.connect(self.on_error_occurred)
        except Exception as e:
            self.ShowLog(f"下载失败: {e}",False)
            #print(f"下载失败: {e}")

    #显示当前下载的文件
    def update_current_file_label(self, file_name):
        # 设置标签的文本
        self.current_file_label.setText(f"Current File: {file_name}")
        # 设置工具提示以显示完整的文件名
        self.current_file_label.setToolTip(file_name)

    #窗体居中显示
    def centerWindow(self):
        screen = QDesktopWidget().screenGeometry()  # 获取屏幕尺寸
        # 设置窗口宽度为屏幕宽度的1/3,高度为屏幕高度的1/12
        width = screen.width() * 2 / 3
        height = screen.height() * 1 / 15
        self.setFixedSize(width, height)  # 设置固定大小

        qr = self.frameGeometry()  # 获取主窗口的矩形框架
        cp = screen.center()  # 获取显示屏幕的中心点
        qr.moveCenter(cp)  # 将窗口框架的中心点移动到屏幕的中心位置
        self.move(qr.topLeft())  # 将窗口的左上角移动到矩形框架的左上角,实际上就是将窗口居中

        qr = self.frameGeometry()  # 获取主窗口的矩形框架
        cp = QDesktopWidget().availableGeometry().center()  # 获取显示屏幕的中心点
        qr.moveCenter(cp)  # 将窗口框架的中心点移动到屏幕的中心位置
        self.move(qr.topLeft())  # 将窗口的左上角移动到矩形框架的左上角,实际上就是将窗口居中

    def on_download_completed(self):
        QApplication.exit(0)  # 下载完成后正常退出

    def on_error_occurred(self):
        QApplication.exit(1)  # 发生错误时退出并返回1

    # 日志
    def ShowLog(self, log, is_success):
        try:
            if is_success:
                color = self.COLOR_SUCCESS
                self.logger.info(log)
            else:
                color = self.COLOR_ERROR
                self.logger.error(log)
                # QMessageBox.critical(self, '系统提醒', f"{log}", QMessageBox.Yes, QMessageBox.Yes)
            print(f"{color}{log}{self.COLOR_RESET}")
        except Exception as e:
            QMessageBox.critical(self, '系统提醒', f"{log}", QMessageBox.Yes, QMessageBox.Yes)
            print(f"{self.COLOR_ERROR}{e}{self.COLOR_RESET}")
            sys.exit(1)

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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值