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_())