diff --git a/AppCard.cs b/AppCard.cs new file mode 100644 index 0000000..f23574e --- /dev/null +++ b/AppCard.cs @@ -0,0 +1,82 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace AppStore +{ + public class AppCard : UserControl + { + private PictureBox iconBox; + private Label nameLabel; + private Button downloadBtn; + + public string AppName { get; set; } = string.Empty; + public Image AppIcon { get; set; } = SystemIcons.Application.ToBitmap(); + public string DownloadUrl { get; set; } = string.Empty; + + public AppCard() + { + iconBox = new PictureBox(); + nameLabel = new Label(); + downloadBtn = new Button(); + InitializeComponent(); + } + + private void InitializeComponent() + { + this.Size = new Size(200, 120); + this.BackColor = Color.White; + this.BorderStyle = BorderStyle.FixedSingle; + + // 应用图标 + iconBox = new PictureBox(); + iconBox.Size = new Size(64, 64); + iconBox.Location = new Point(10, 10); + iconBox.SizeMode = PictureBoxSizeMode.StretchImage; + this.Controls.Add(iconBox); + + // 应用名称 + nameLabel = new Label(); + nameLabel.AutoSize = true; + nameLabel.Location = new Point(80, 15); + nameLabel.Font = new Font("Microsoft YaHei", 10, FontStyle.Bold); + this.Controls.Add(nameLabel); + + // 下载按钮 + downloadBtn = new Button(); + downloadBtn.Text = "下载"; + downloadBtn.Size = new Size(80, 30); + downloadBtn.Location = new Point(60, 80); + downloadBtn.BackColor = Color.FromArgb(0, 120, 215); + downloadBtn.ForeColor = Color.White; + downloadBtn.FlatStyle = FlatStyle.Flat; + downloadBtn.FlatAppearance.BorderSize = 0; + downloadBtn.Click += DownloadBtn_Click; + this.Controls.Add(downloadBtn); + } + + public void UpdateDisplay() + { + nameLabel.Text = AppName; + iconBox.Image = AppIcon; + } + + private void DownloadBtn_Click(object sender, EventArgs e) + { + if (sender == null || e == null) return; + if (!string.IsNullOrEmpty(DownloadUrl)) + { + try + { + string fileName = $"{AppName.Replace(" ", "_")}.exe"; + DownloadManager.Instance.StartDownload(fileName, DownloadUrl); + MessageBox.Show($"已开始下载: {AppName}", "下载中", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show($"下载失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } +} diff --git a/AppStore.csproj b/AppStore.csproj new file mode 100644 index 0000000..f8301fd --- /dev/null +++ b/AppStore.csproj @@ -0,0 +1,30 @@ + + + CS8622 + + + + WinExe + net8.0-windows + enable + true + enable + CS8618 + img\ico\icon.ico + x86;x64 + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/DownloadItem.cs b/DownloadItem.cs new file mode 100644 index 0000000..6882f11 --- /dev/null +++ b/DownloadItem.cs @@ -0,0 +1,92 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace AppStore +{ + public class DownloadItem : UserControl + { + private Label nameLabel; + private ProgressBar progressBar; + private Label statusLabel; + private Button cancelBtn; + + public string FileName { get; set; } = string.Empty; + public int Progress { get; set; } + public string Status { get; set; } = string.Empty; + + public DownloadItem() + { + nameLabel = new Label(); + progressBar = new ProgressBar(); + statusLabel = new Label(); + cancelBtn = new Button(); + + InitializeComponent(); + } + + private void InitializeComponent() + { + this.Size = new Size(400, 60); + this.BackColor = Color.White; + this.BorderStyle = BorderStyle.FixedSingle; + + // 文件名标签 + nameLabel = new Label(); + nameLabel.AutoSize = true; + nameLabel.Location = new Point(10, 10); + nameLabel.Font = new Font("Microsoft YaHei", 9, FontStyle.Bold); + this.Controls.Add(nameLabel); + + // 进度条 + progressBar = new ProgressBar(); + progressBar.Size = new Size(200, 20); + progressBar.Location = new Point(10, 30); + this.Controls.Add(progressBar); + + // 状态标签 + statusLabel = new Label(); + statusLabel.AutoSize = true; + statusLabel.Location = new Point(220, 30); + statusLabel.Font = new Font("Microsoft YaHei", 8); + this.Controls.Add(statusLabel); + + // 取消按钮 + cancelBtn = new Button(); + cancelBtn.Text = "取消"; + cancelBtn.Size = new Size(60, 25); + cancelBtn.Location = new Point(320, 30); + cancelBtn.Click += CancelBtn_Click; + this.Controls.Add(cancelBtn); + } + + public void UpdateDisplay() + { + nameLabel.Text = FileName; + progressBar.Value = Progress; + statusLabel.Text = Status; + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + if (sender == null || e == null) return; + if (InvokeRequired) + { + Invoke(new EventHandler(CancelBtn_Click), sender, e); + return; + } + + try + { + DownloadManager.Instance.CancelDownload(this); + Status = "已取消"; + UpdateDisplay(); + } + catch (Exception ex) + { + Status = $"取消失败: {ex.Message}"; + UpdateDisplay(); + } + } + } +} diff --git a/DownloadManager.cs b/DownloadManager.cs new file mode 100644 index 0000000..fbe7f39 --- /dev/null +++ b/DownloadManager.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppStore +{ + public class DownloadManager + { + private static DownloadManager instance = null!; + public static DownloadManager Instance => instance ??= new DownloadManager(); + + private Process? currentProcess; + public List DownloadItems { get; } = new List(); + + public event Action DownloadAdded = delegate { }; + public event Action DownloadProgressChanged = delegate { }; + public event Action DownloadCompleted = delegate { }; + + // 内部类封装进程结果 + private class ProcessResult + { + public int ExitCode { get; set; } = -1; + public bool HasExited { get; set; } + } + + // 安全获取进程结果 + private ProcessResult GetProcessResult(Process? process) + { + var result = new ProcessResult(); + if (process == null) return result; + + try + { + if (!process.HasExited) + { + process.WaitForExit(5000); + } + + result.HasExited = process.HasExited; + if (result.HasExited) + { + result.ExitCode = process.ExitCode; + } + } + catch + { + // 忽略所有异常,使用默认值 + } + + return result; + } + + public void StartDownload(string fileName, string url) + { + var downloadItem = new DownloadItem + { + FileName = fileName, + Progress = 0, + Status = "准备下载" + }; + + DownloadItems.Add(downloadItem); + DownloadAdded?.Invoke(downloadItem); + + Task.Run(() => DownloadFile(downloadItem, fileName, url)); + } + + private void DownloadFile(DownloadItem downloadItem, string fileName, string url) + { + try + { + // 设置下载目录为用户文件夹中的Downloads + var downloadsDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "Downloads"); + Directory.CreateDirectory(downloadsDir); + + // 构建aria2c路径 + var aria2cPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "resource", "aria2c.exe"); + + if (!File.Exists(aria2cPath)) + { + throw new FileNotFoundException($"找不到aria2c.exe: {aria2cPath}"); + } + + // 设置线程数为16并添加详细日志 + var arguments = $"--out={fileName} --dir=\"{downloadsDir}\" --split=16 --max-connection-per-server=16 {url}"; + Console.WriteLine($"下载目录: {downloadsDir}"); + + currentProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = aria2cPath, + Arguments = arguments, + WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + } + }; + + // 获取目标文件路径 + string downloadPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "Downloads", + fileName); + + long totalSize = 0; + + // 添加进度检测超时机制 + var progressTimer = new System.Timers.Timer(5000); // 5秒无更新视为完成 + progressTimer.Elapsed += (s, e) => { + progressTimer.Stop(); + if (downloadItem.Progress < 100) { + downloadItem.Progress = 100; + downloadItem.Status = "下载完成 (100%)"; + DownloadProgressChanged?.Invoke(downloadItem); + } + }; + + currentProcess.OutputDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + { + Console.WriteLine($"输出: {e.Data}"); + + // 重置超时计时器 + progressTimer.Stop(); + progressTimer.Start(); + + // 解析总大小 + if (e.Data.Contains("Length:")) + { + var sizeStr = e.Data.Split(new[]{"Length:"}, StringSplitOptions.RemoveEmptyEntries)[1] + .Split('(')[0].Trim(); + if (long.TryParse(sizeStr, out totalSize)) + { + Console.WriteLine($"检测到文件总大小: {totalSize} bytes"); + } + } + + // 解析进度百分比 + if (e.Data.Contains("%)")) + { + var start = e.Data.IndexOf("(") + 1; + var end = e.Data.IndexOf("%)"); + if (start > 0 && end > start) + { + var progressStr = e.Data.Substring(start, end - start); + if (int.TryParse(progressStr, out int progress)) + { + progress = Math.Min(progress, 100); + downloadItem.Progress = progress; + downloadItem.Status = $"下载中({progress}%)"; + DownloadProgressChanged?.Invoke(downloadItem); + } + } + } + } + }; + + currentProcess.ErrorDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + { + Console.WriteLine($"错误: {e.Data}"); + downloadItem.Status = $"错误: {e.Data}"; + DownloadProgressChanged?.Invoke(downloadItem); + } + }; + + currentProcess.Exited += (sender, e) => + { + var process = currentProcess; + if (process == null) return; + + var result = GetProcessResult(process); + + if (result.ExitCode == 0) + { + // 最终状态强制更新 + downloadItem.Progress = 100; + downloadItem.Status = "下载完成 (100%)"; + + // 验证文件完整性 + string downloadPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "Downloads", + downloadItem.FileName); + + if (File.Exists(downloadPath)) + { + Console.WriteLine($"文件下载完成: {downloadPath}"); + } + else + { + Console.WriteLine("警告: 下载完成但文件不存在"); + } + + // 触发界面更新 + DownloadProgressChanged?.Invoke(downloadItem); + DownloadCompleted?.Invoke(downloadItem); + downloadItem.UpdateDisplay(); + + try + { + // 双重确保在主线程显示提示 + if (Application.OpenForms.Count > 0) + { + var mainForm = Application.OpenForms[0]; + mainForm.Invoke((MethodInvoker)delegate { + MessageBox.Show(mainForm, + $"文件 {downloadItem.FileName} 已成功下载到:\n{Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads", downloadItem.FileName)}", + "下载完成", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + }); + } + else + { + Console.WriteLine("下载完成提示:无法找到主窗体"); + } + } + catch (Exception ex) + { + Console.WriteLine($"显示下载完成提示时出错:{ex}"); + } + } + else + { + downloadItem.Status = $"下载失败 (代码: {result.ExitCode})"; + MessageBox.Show($"文件 {downloadItem.FileName} 下载失败", "下载失败", + MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + DownloadCompleted?.Invoke(downloadItem); + + try + { + process?.Dispose(); + } + finally + { + if (process != null) + { + currentProcess = null; + } + } + + // 强制更新显示 + downloadItem.UpdateDisplay(); + }; + + Console.WriteLine($"启动aria2c: {aria2cPath}"); + Console.WriteLine($"参数: {arguments}"); + + if (!currentProcess.Start()) + { + throw new Exception("进程启动失败"); + } + + currentProcess.BeginOutputReadLine(); + currentProcess.BeginErrorReadLine(); + progressTimer.Start(); + } + catch (Exception ex) + { + downloadItem.Status = $"下载错误: {ex.Message}"; + Console.WriteLine($"下载错误: {ex}"); + DownloadCompleted?.Invoke(downloadItem); + } + } + + public void CancelDownload(DownloadItem item) + { + try + { + var process = currentProcess; + if (process == null || process.HasExited || process.StartInfo == null) + { + item.Status = "已取消"; + DownloadProgressChanged?.Invoke(item); + return; + } + + process.Kill(); + process.Dispose(); + currentProcess = null; + + item.Status = "已取消"; + DownloadProgressChanged?.Invoke(item); + } + catch (Exception ex) + { + Console.WriteLine($"取消下载时出错: {ex}"); + item.Status = $"取消失败: {ex.Message}"; + DownloadProgressChanged?.Invoke(item); + } + } + } +} diff --git a/MainForm.cs b/MainForm.cs new file mode 100644 index 0000000..e7ef59a --- /dev/null +++ b/MainForm.cs @@ -0,0 +1,158 @@ +#nullable enable +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace AppStore +{ + public class MainForm : Form + { + private Button btnApps = null!; + private Button btnDownloads = null!; + private Panel contentPanel = null!; + + private void InitializeComponent() + { + // 窗体基本设置 + this.Text = "应用商店"; + this.Size = new Size(800, 600); + this.StartPosition = FormStartPosition.CenterScreen; + this.Icon = new Icon("img/ico/icon.ico"); + + // 顶部按钮面板 + Panel buttonPanel = new Panel(); + buttonPanel.Dock = DockStyle.Top; + buttonPanel.Height = 50; + buttonPanel.BackColor = Color.LightGray; + + // 软件下载按钮 + btnApps = new Button(); + btnApps.Text = "软件下载"; + btnApps.Size = new Size(100, 30); + btnApps.Location = new Point(20, 10); + btnApps.Font = new Font("Microsoft YaHei", 9); + btnApps.Click += (s, e) => ShowAppsView(); + buttonPanel.Controls.Add(btnApps); + + // 下载进度按钮 + btnDownloads = new Button(); + btnDownloads.Text = "下载进度"; + btnDownloads.Size = new Size(100, 30); + btnDownloads.Location = new Point(140, 10); + btnDownloads.Font = new Font("Microsoft YaHei", 9); + btnDownloads.Click += (s, e) => ShowDownloadsView(); + buttonPanel.Controls.Add(btnDownloads); + + this.Controls.Add(buttonPanel); + + // 内容区域 + contentPanel = new Panel(); + contentPanel.Dock = DockStyle.Fill; + contentPanel.Padding = new Padding(20); + this.Controls.Add(contentPanel); + + // 默认显示软件下载视图 + ShowAppsView(); + } + + private void ShowAppsView() + { + contentPanel.Controls.Clear(); + + // 使用FlowLayoutPanel来组织应用卡片 + // 使用FlowLayoutPanel实现自动流式布局 + FlowLayoutPanel flowPanel = new FlowLayoutPanel(); + flowPanel.Dock = DockStyle.Fill; + flowPanel.AutoScroll = true; + flowPanel.Padding = new Padding(10, 30, 10, 10); // 增加顶部间距 + flowPanel.WrapContents = true; + contentPanel.Controls.Add(flowPanel); + + // 创建WindowsCleaner应用卡片 + AppCard windowsCleanerCard = new AppCard(); + windowsCleanerCard.AppName = "WindowsCleaner"; + windowsCleanerCard.DownloadUrl = "https://ghproxy.net/https://github.com/darkmatter2048/WindowsCleaner/releases/download/v5.0.8/windowscleaner_v5.0.8_amd64_x64_setup.exe"; + + try + { + // 加载应用图标 + windowsCleanerCard.AppIcon = Image.FromFile("img/png/WindowsCleaner.png"); + } + catch + { + // 如果图标加载失败,使用默认图标 + windowsCleanerCard.AppIcon = SystemIcons.Application.ToBitmap(); + } + + windowsCleanerCard.UpdateDisplay(); + // 添加卡片到流式布局 + flowPanel.Controls.Add(windowsCleanerCard); + } + + private FlowLayoutPanel downloadsFlowPanel = new FlowLayoutPanel(); + private List downloadItems = new List(); + + public MainForm() + { + InitializeComponent(); + // 订阅下载管理器事件 + DownloadManager.Instance.DownloadAdded += OnDownloadAdded; + DownloadManager.Instance.DownloadProgressChanged += OnDownloadProgressChanged; + DownloadManager.Instance.DownloadCompleted += OnDownloadCompleted; + } + + private void ShowDownloadsView() + { + contentPanel.Controls.Clear(); + + // 使用FlowLayoutPanel组织下载项 + downloadsFlowPanel = new FlowLayoutPanel(); + downloadsFlowPanel.Dock = DockStyle.Fill; + downloadsFlowPanel.AutoScroll = true; + downloadsFlowPanel.Padding = new Padding(10, 30, 10, 10); // 增加顶部间距 + downloadsFlowPanel.FlowDirection = FlowDirection.TopDown; + downloadsFlowPanel.WrapContents = false; + contentPanel.Controls.Add(downloadsFlowPanel); + + // 显示所有下载项 + foreach (var item in DownloadManager.Instance.DownloadItems) + { + downloadsFlowPanel.Controls.Add(item); + } + } + + private void OnDownloadAdded(DownloadItem item) + { + if (InvokeRequired) + { + Invoke(new Action(OnDownloadAdded), item); + return; + } + + downloadItems.Add(item); + downloadsFlowPanel?.Controls.Add(item); + } + + private void OnDownloadProgressChanged(DownloadItem item) + { + if (InvokeRequired) + { + Invoke(new Action(OnDownloadProgressChanged), item); + return; + } + + item.UpdateDisplay(); + } + + private void OnDownloadCompleted(DownloadItem item) + { + if (InvokeRequired) + { + Invoke(new Action(OnDownloadCompleted), item); + return; + } + + item.UpdateDisplay(); + } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..9ce17df --- /dev/null +++ b/Program.cs @@ -0,0 +1,16 @@ +using System; +using System.Windows.Forms; + +namespace AppStore +{ + static class Program + { + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/img/ico/icon.ico b/img/ico/icon.ico new file mode 100644 index 0000000..d973473 Binary files /dev/null and b/img/ico/icon.ico differ diff --git a/img/png/WindowsCleaner.png b/img/png/WindowsCleaner.png new file mode 100644 index 0000000..5d9e8a5 Binary files /dev/null and b/img/png/WindowsCleaner.png differ diff --git a/img/png/kortapp-z.png b/img/png/kortapp-z.png new file mode 100644 index 0000000..f92a09a Binary files /dev/null and b/img/png/kortapp-z.png differ diff --git a/resource/aria2c.exe b/resource/aria2c.exe new file mode 100644 index 0000000..5004e10 Binary files /dev/null and b/resource/aria2c.exe differ