基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
// ============================================================
|
||||
// UmiOcrHost:Umi-OCR 本体(网盘 zip)下载/安装/启停/检测 管理器
|
||||
// · 本体 zip 直链(用户网盘,~179MB,zip 根直接是 Umi-OCR.exe 无顶层目录)
|
||||
// · 安装 = 下载 zip 到临时文件 → 解压到 exe 旁 Umi-OCR\ → 找到主程序验证 → 删 zip
|
||||
// · 启动 = Process.Start(主程序,Umi-OCR 启动即带 HTTP 服务 1224 端口) → 轮询端口就绪
|
||||
// · 停止 = 只停由本程序启动的实例(CloseMainWindow→超时 Kill),不碰用户自己开的
|
||||
// ============================================================
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace pdfbt
|
||||
{
|
||||
internal static class UmiOcrHost
|
||||
{
|
||||
/// <summary>本体 zip 直链(用户网盘直连,GET 才有内容,HEAD 会 404,不支持断点续传)。</summary>
|
||||
public const string DownloadUrl = "http://s333.odn.cc/s/845f64a239b665ec";
|
||||
|
||||
private const string InstallDirName = "Umi-OCR";
|
||||
private const int HttpPort = 1224;
|
||||
|
||||
/// <summary>由本程序启动的实例(停止时只关它,不碰用户自己开的)。</summary>
|
||||
private static Process _startedProc;
|
||||
|
||||
/// <summary>本体安装目录(exe 旁 Umi-OCR\)。</summary>
|
||||
public static string InstallDir
|
||||
{
|
||||
get { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, InstallDirName); }
|
||||
}
|
||||
|
||||
/// <summary>找安装目录里的主程序(Umi-OCR.exe,大小写不敏感;找不到返回 null)。</summary>
|
||||
public static string FindExe()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(InstallDir)) return null;
|
||||
foreach (var f in Directory.GetFiles(InstallDir, "*.exe", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
string name = Path.GetFileName(f);
|
||||
if (name.Equals("Umi-OCR.exe", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("Umi-OCR.exe", StringComparison.OrdinalIgnoreCase))
|
||||
return f;
|
||||
}
|
||||
// 目录名不完全一致时兜底:取第一个 exe
|
||||
string[] all = Directory.GetFiles(InstallDir, "*.exe", SearchOption.TopDirectoryOnly);
|
||||
return all.Length > 0 ? all[0] : null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
/// <summary>本体是否已安装(目录里能找到主程序)。</summary>
|
||||
public static bool IsInstalled
|
||||
{
|
||||
get { return FindExe() != null; }
|
||||
}
|
||||
|
||||
/// <summary>HTTP 服务(1224 端口)是否在监听。</summary>
|
||||
public static bool IsHttpUp()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var c = new System.Net.Sockets.TcpClient())
|
||||
{
|
||||
var ar = c.BeginConnect("127.0.0.1", HttpPort, null, null);
|
||||
if (ar.AsyncWaitHandle.WaitOne(800))
|
||||
{
|
||||
c.EndConnect(ar);
|
||||
return true;
|
||||
}
|
||||
c.Close();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>三态:0=未安装,1=已安装未运行,2=运行中(HTTP 在线)。</summary>
|
||||
public static int Detect()
|
||||
{
|
||||
if (!IsInstalled) return 0;
|
||||
return IsHttpUp() ? 2 : 1;
|
||||
}
|
||||
|
||||
/// <summary>状态文字(设置窗口显示用)。</summary>
|
||||
public static string DetectText()
|
||||
{
|
||||
int st = Detect();
|
||||
if (st == 0) return "未检测到 OCR 本体(点\"下载 OCR 本体\"自动下载安装)";
|
||||
if (st == 1) return "已检测到 OCR 本体,服务未运行(识别时会自动启动)";
|
||||
return "OCR 本体运行中(HTTP " + HttpPort + " 端口在线)";
|
||||
}
|
||||
|
||||
/// <summary>下载 zip → 解压到 Umi-OCR\ → 验证主程序 → 删 zip。
|
||||
/// progress: 0~100 下载百分比;解压与收尾阶段回调 100。失败抛异常(内部已清理残留)。
|
||||
/// ★ 必须用 DownloadFileAsync:同步版 DownloadFile 不触发 DownloadProgressChanged
|
||||
/// (进度事件只在异步路径发,同步下载会一直停在 0%)。</summary>
|
||||
public static Task DownloadAndInstallAsync(Action<int> progress)
|
||||
{
|
||||
return Task.Factory.StartNew(delegate
|
||||
{
|
||||
string tmpZip = Path.Combine(Path.GetTempPath(), "Umi-OCR_" + Guid.NewGuid().ToString("N") + ".zip");
|
||||
try
|
||||
{
|
||||
if (progress != null) progress(0);
|
||||
// 异步下载包成同步等待(TaskCompletionSource),进度事件才会触发
|
||||
var dlTcs = new TaskCompletionSource<bool>();
|
||||
using (var wc = new WebClient())
|
||||
{
|
||||
wc.Proxy = null;
|
||||
wc.DownloadProgressChanged += (s, e) =>
|
||||
{
|
||||
if (progress != null) progress(e.ProgressPercentage);
|
||||
};
|
||||
wc.DownloadFileCompleted += (s, e) =>
|
||||
{
|
||||
if (e.Error != null) dlTcs.TrySetException(e.Error);
|
||||
else if (e.Cancelled) dlTcs.TrySetException(new Exception("下载已取消"));
|
||||
else dlTcs.TrySetResult(true);
|
||||
};
|
||||
wc.DownloadFileAsync(new Uri(DownloadUrl), tmpZip);
|
||||
}
|
||||
dlTcs.Task.GetAwaiter().GetResult(); // 等下载结束(异常原样抛出)
|
||||
if (progress != null) progress(100);
|
||||
|
||||
// 校验是 zip(PK 头)而不是错误页/JSON
|
||||
using (var fs = File.OpenRead(tmpZip))
|
||||
{
|
||||
int b0 = fs.ReadByte(), b1 = fs.ReadByte();
|
||||
if (b0 != 'P' || b1 != 'K')
|
||||
throw new Exception("下载的内容不是有效的压缩包(可能是网盘临时故障),请稍后重试");
|
||||
}
|
||||
|
||||
// 解压(覆盖);zip 根直接是 Umi-OCR.exe,目标目录即安装目录
|
||||
string dst = InstallDir;
|
||||
if (!Directory.Exists(dst)) Directory.CreateDirectory(dst);
|
||||
using (var archive = ZipFile.OpenRead(tmpZip))
|
||||
{
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
string target = Path.GetFullPath(Path.Combine(dst, entry.FullName));
|
||||
if (!target.StartsWith(Path.GetFullPath(dst), StringComparison.OrdinalIgnoreCase))
|
||||
continue; // 防 zip 路径穿越
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
{
|
||||
if (!Directory.Exists(target)) Directory.CreateDirectory(target);
|
||||
continue;
|
||||
}
|
||||
string tdir = Path.GetDirectoryName(target);
|
||||
if (!string.IsNullOrEmpty(tdir) && !Directory.Exists(tdir)) Directory.CreateDirectory(tdir);
|
||||
if (File.Exists(target))
|
||||
{
|
||||
try { File.Delete(target); } catch { } // 被占用则跳过(旧文件同名多半一致)
|
||||
}
|
||||
entry.ExtractToFile(target, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (FindExe() == null)
|
||||
throw new Exception("解压完成但未找到 Umi-OCR 主程序,压缩包内容可能不完整");
|
||||
|
||||
try { File.Delete(tmpZip); } catch { } // 验证通过后删 zip
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { if (File.Exists(tmpZip)) File.Delete(tmpZip); } catch { }
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>确保本体已安装且 HTTP 服务在线:未安装抛错引导下载;未运行则静默启动(--hide 主窗口进托盘)并等端口就绪(≤15秒)。
|
||||
/// 返回 false=已安装但启动超时(调用方提示)。</summary>
|
||||
public static bool EnsureRunning()
|
||||
{
|
||||
if (!IsInstalled)
|
||||
throw new Exception("尚未安装 Umi-OCR 本体。\r\n请到 OCR 设置窗口选择 Umi-OCR 引擎,点\"下载 OCR 本体\"。");
|
||||
if (IsHttpUp()) return true;
|
||||
|
||||
string exe = FindExe();
|
||||
// --hide:官方参数,启动后主窗口隐藏到托盘,后台提供 HTTP 服务(用户看到零弹窗)
|
||||
_startedProc = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = exe,
|
||||
Arguments = "--hide",
|
||||
WorkingDirectory = Path.GetDirectoryName(exe),
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
});
|
||||
for (int i = 0; i < 30; i++) // 30 × 500ms = 最多 15 秒
|
||||
{
|
||||
if (IsHttpUp()) return true;
|
||||
System.Threading.Thread.Sleep(500);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>停止由本程序启动的实例(PdfViewerWindow 关闭时调用;用户自己开的不动)。
|
||||
/// --hide 启动的窗口在托盘,CloseMainWindow 不可靠:先发官方 --quit 指令优雅退出,超时再 Kill。</summary>
|
||||
public static void StopIfStartedByUs()
|
||||
{
|
||||
var p = _startedProc;
|
||||
_startedProc = null;
|
||||
if (p == null) return;
|
||||
try
|
||||
{
|
||||
if (!p.HasExited)
|
||||
{
|
||||
// 官方退出指令:向已运行实例发 --quit(依赖其 HTTP 通道)
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = FindExe(),
|
||||
Arguments = "--quit",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
if (!p.WaitForExit(3000)) p.Kill();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { try { p.Dispose(); } catch { } }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user