借助网络配置,控制Modbus工具的日志显示,避免卡死界面
智能大石头 authored at 2021-12-11 22:09:19
8.93 KiB
XCoder
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using NewLife.Log;

namespace ALib
{
    /// <summary>
    /// 嵌入 ZIP 资源释放器。
    /// 原生库多为未压缩的 PE 文件,ZIP 压缩率通常可达 3~8 倍,
    /// 因此把成组文件(或单个大 DLL)打包为 zip 嵌入,可显著减小程序集体积;
    /// 运行首次使用时再解压还原到磁盘。
    /// 仅依赖 .NET 内置的 <see cref="System.IO.Compression"/>,无需第三方库。
    /// </summary>
    public static class ZipResourceExtractor
    {
        /// <summary>是否已释放过,避免重复解压</summary>
        private static readonly HashSet<String> _done = new HashSet<String>(StringComparer.OrdinalIgnoreCase);

        /// <summary>释放过程锁</summary>
        private static readonly Object _lock = new Object();

        /// <summary>
        /// 把嵌入的 zip 资源解压到指定目录(保留 zip 内的相对结构)。
        /// </summary>
        /// <param name="resourceName">嵌入资源完整名,如 ALib.ZLG_CAN.kerneldlls.zip</param>
        /// <param name="destDir">目标目录</param>
        /// <param name="checkFile">
        /// 完整性校验用的相对文件名(如 dll_cfg.ini)。
        /// 该文件存在即认为目录已释放完整,直接跳过解压;
        /// 传 null 则每次都按缓存标记判断。
        /// </param>
        /// <returns>是否成功</returns>
        public static Boolean ExtractToDirectory(String resourceName, String destDir, String checkFile = null)
        {
            if (String.IsNullOrEmpty(resourceName) || String.IsNullOrEmpty(destDir)) return false;

            lock (_lock)
            {
                if (_done.Contains(resourceName)) return true;

                // 已释放且关键文件在,直接复用,避免每次启动都解压
                if (!String.IsNullOrEmpty(checkFile) && File.Exists(Path.Combine(destDir, checkFile)))
                {
                    _done.Add(resourceName);
                    return true;
                }

                try
                {
                    var asm = Assembly.GetExecutingAssembly();
                    using var stream = asm.GetManifestResourceStream(resourceName);
                    if (stream == null)
                    {
                        XTrace.WriteLine($"找不到嵌入资源: {resourceName}");
                        return false;
                    }

                    using var zip = new ZipArchive(stream, ZipArchiveMode.Read);

                    Directory.CreateDirectory(destDir);

                    var count = 0;
                    foreach (var entry in zip.Entries)
                    {
                        // 目录项(以 / 结尾)本身不写文件,但其下的文件会隐式建目录
                        if (String.IsNullOrEmpty(entry.Name)) continue;

                        var rel = entry.FullName.Replace('/', Path.DirectorySeparatorChar);

                        // 防目录穿越:只允许解压到 destDir 之内
                        var dest = Path.GetFullPath(Path.Combine(destDir, rel));
                        var rootFull = Path.GetFullPath(destDir);
                        if (!dest.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
                        {
                            XTrace.WriteLine($"跳过越界条目: {entry.FullName}");
                            continue;
                        }

                        var dir = Path.GetDirectoryName(dest);
                        if (!String.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);

                        // 大小一致则跳过写入:驱动已被加载时无法覆盖,跳过也能保证可用
                        var fi = new FileInfo(dest);
                        if (fi.Exists && fi.Length == entry.Length) continue;

                        WriteEntry(entry, dest);
                        count++;
                    }

                    var ok = String.IsNullOrEmpty(checkFile) || File.Exists(Path.Combine(destDir, checkFile));
                    if (ok)
                    {
                        _done.Add(resourceName);
                        XTrace.WriteLine($"ZIP 资源已释放: {resourceName} -> {destDir}(解压 {count} 个文件)");
                    }
                    else
                    {
                        XTrace.WriteLine($"ZIP 释放后缺少校验文件 {checkFile},目录可能不完整: {destDir}");
                    }
                    return ok;
                }
                catch (Exception ex)
                {
                    XTrace.WriteLine($"释放 ZIP 资源失败 {resourceName}: {ex.Message}");
                    return false;
                }
            }
        }

        /// <summary>
        /// 把嵌入 zip 中的某个条目解压为单个文件(zip 内可只放这一个文件)。
        /// </summary>
        /// <param name="resourceName">嵌入资源完整名,如 ALib.ZLG_CAN.zlgcan.zip</param>
        /// <param name="destPath">目标文件路径</param>
        /// <returns>是否成功</returns>
        public static Boolean ExtractFile(String resourceName, String destPath)
        {
            if (String.IsNullOrEmpty(resourceName) || String.IsNullOrEmpty(destPath)) return false;

            lock (_lock)
            {
                var key = resourceName + "|" + destPath;
                if (_done.Contains(key)) return true;

                // 已存在且非空,直接复用
                var fi = new FileInfo(destPath);
                if (fi.Exists && fi.Length > 0)
                {
                    _done.Add(key);
                    return true;
                }

                try
                {
                    var asm = Assembly.GetExecutingAssembly();
                    using var stream = asm.GetManifestResourceStream(resourceName);
                    if (stream == null)
                    {
                        XTrace.WriteLine($"找不到嵌入资源: {resourceName}");
                        return false;
                    }

                    using var zip = new ZipArchive(stream, ZipArchiveMode.Read);

                    // 单文件模式:取第一个非目录条目
                    ZipArchiveEntry target = null;
                    foreach (var e in zip.Entries)
                    {
                        if (!String.IsNullOrEmpty(e.Name)) { target = e; break; }
                    }
                    if (target == null)
                    {
                        XTrace.WriteLine($"ZIP 中无可解压文件: {resourceName}");
                        return false;
                    }

                    var dir = Path.GetDirectoryName(destPath);
                    if (!String.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);

                    WriteEntry(target, destPath);

                    _done.Add(key);
                    return true;
                }
                catch (Exception ex)
                {
                    XTrace.WriteLine($"释放 ZIP 文件失败 {resourceName}: {ex.Message}");
                    return false;
                }
            }
        }

        /// <summary>查找以指定后缀结尾的嵌入 zip 资源名,找不到返回 null</summary>
        /// <param name="suffix">资源名后缀,如 ".ZLG_CAN.zlgcan.zip"</param>
        /// <returns>完整资源名</returns>
        public static String FindResource(String suffix)
        {
            if (String.IsNullOrEmpty(suffix)) return null;

            try
            {
                var asm = Assembly.GetExecutingAssembly();
                foreach (var name in asm.GetManifestResourceNames())
                {
                    if (name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) return name;
                }
            }
            catch (Exception ex)
            {
                XTrace.WriteLine($"枚举嵌入资源失败: {ex.Message}");
            }

            return null;
        }

        /// <summary>把 zip 条目写入磁盘(先写临时文件再原子替换)</summary>
        /// <param name="entry">zip 条目</param>
        /// <param name="destPath">目标路径</param>
        private static void WriteEntry(ZipArchiveEntry entry, String destPath)
        {
            var tmp = destPath + ".tmp";

            using (var src = entry.Open())
            using (var fs = new FileStream(tmp, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                src.CopyTo(fs);
                fs.Flush();
            }

            try
            {
                File.Move(tmp, destPath, true);
            }
            catch
            {
                // 目标被占用(如驱动已加载)时保留旧文件;旧文件不存在才视为失败
                try { File.Delete(tmp); } catch { }
                if (!File.Exists(destPath)) throw;
            }
        }
    }
}