Files
ctbjrj/pdfbt/UmiOcrHost.cs
T

233 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 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 { } }
}
}
}