diff --git a/TextLocator/App.xaml.cs b/TextLocator/App.xaml.cs index e248e7bcf207b1fd2d7ca0be8f572a64ba6e4a87..664a990e355e052c466ca69c6131e54b4dd75b2a 100644 --- a/TextLocator/App.xaml.cs +++ b/TextLocator/App.xaml.cs @@ -1,6 +1,9 @@ using Hardcodet.Wpf.TaskbarNotification; using log4net; using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -10,6 +13,7 @@ using TextLocator.Core; using TextLocator.Enums; using TextLocator.Factory; using TextLocator.Service; +using TextLocator.SingleInstance; using TextLocator.Util; namespace TextLocator @@ -17,20 +21,50 @@ namespace TextLocator /// /// App.xaml 的交互逻辑 /// - public partial class App : Application + public partial class App : Application, ISingleInstanceApp { private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + /// + /// 入口函数 + /// + [STAThread] + public static void Main() + { + Assembly assembly = Assembly.GetExecutingAssembly(); + string uniqueName = string.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", assembly.GetType().GUID, assembly.GetName().Name); + if (SingleInstance.InitializeAsFirstInstance(uniqueName)) { + var app = new App(); + app.InitializeComponent(); + app.Run(); + + SingleInstance.Cleanup(); + } + } + + /// + /// 信号外部命令行参数 + /// + /// + /// + public bool SignalExternalCommandLineArgs(IList args) + { + if (this.MainWindow.WindowState == WindowState.Minimized) + { + this.MainWindow.WindowState = WindowState.Normal; + } + + this.MainWindow.Activate(); + + return true; + } + // 托盘图标 private static TaskbarIcon _taskbar; public static TaskbarIcon Taskbar { get => _taskbar; set => _taskbar = value; } - // 单实例 - private Mutex _mutex; - public App() { - // 初始化线程池大小 AppCore.SetThreadPoolSize(); @@ -39,6 +73,9 @@ namespace TextLocator // 初始化文件服务引擎 InitFileInfoServiceEngine(); + + // 初始化窗口状态尺寸 + CacheUtil.Put("WindowState", WindowState.Normal); } /// @@ -47,25 +84,6 @@ namespace TextLocator /// protected override void OnStartup(StartupEventArgs e) { - // 互斥 - _mutex = new Mutex(true, "TextLocator", out bool isNewInstance); - // 是否启动新实例 - if (!isNewInstance) - { - // 找到已经在运行的实例句柄(给出你的窗体标题名 “XXX影院”) - IntPtr hWndPtr = FindWindow(null, "文本搜索定位器"); - - // 还原窗口 - _ = IsIconic(hWndPtr) ? ShowWindow(hWndPtr, SW_RESTORE) : ShowWindow(hWndPtr, SW_SHOW); - - // 激活窗口 - SetForegroundWindow(hWndPtr); - - // 退出当前实例 - AppCore.Shutdown(); - return; - } - // 托盘图标 _taskbar = (TaskbarIcon)FindResource("Taskbar"); @@ -93,6 +111,9 @@ namespace TextLocator // 文件读取超时时间 AppUtil.WriteValue("AppConfig", "FileReadTimeout", AppConst.FILE_READ_TIMEOUT + ""); + + // 文件内容摘要切割长度 + AppUtil.WriteValue("AppConfig", "FileContentBreviaryCutLength", AppConst.FILE_CONTENT_BREVIARY_CUT_LENGTH + ""); } #endregion @@ -186,46 +207,5 @@ namespace TextLocator } } #endregion - - #region Windows API - //ShowWindow 参数 - private const int SW_SHOW = 5; - private const int SW_RESTORE = 9; - - /// - /// 是标志性的 - /// - /// 窗口句柄 - /// - [DllImport("USER32.DLL", SetLastError = true, CharSet = CharSet.Auto)] - private static extern bool IsIconic(IntPtr hWnd); - /// - /// 在桌面窗口列表中寻找与指定条件相符的第一个窗口。 - /// - /// 指向指定窗口的类名。如果 lpClassName 是 NULL,所有类名匹配。 - /// 指向指定窗口名称(窗口的标题)。如果 lpWindowName 是 NULL,所有windows命名匹配。 - /// 返回指定窗口句柄 - [DllImport("USER32.DLL", SetLastError = true, CharSet = CharSet.Auto)] - public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); - - /// - /// 将窗口还原,可从最小化还原 - /// - /// - /// - /// - [DllImport("USER32.DLL")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); - - /// - /// 激活指定窗口 - /// - /// 指定窗口句柄 - /// - [DllImport("USER32.DLL")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetForegroundWindow(IntPtr hWnd); - #endregion } } diff --git a/TextLocator/Core/AppConst.cs b/TextLocator/Core/AppConst.cs index 14e97eceb7b1128d074d01b529051b7bc107b8e8..ee402f43df06f65b3e085cc7af8d850e6bf23fb4 100644 --- a/TextLocator/Core/AppConst.cs +++ b/TextLocator/Core/AppConst.cs @@ -14,10 +14,6 @@ namespace TextLocator.Core /// public class AppConst { - /// - /// 结果列表分页条数 - /// - public static int MRESULT_LIST_PAGE_SIZE = int.Parse(AppUtil.ReadValue("AppConfig", "ResultListPageSize", "100")); /// /// 文件读取超时时间,单位:秒 /// @@ -27,17 +23,30 @@ namespace TextLocator.Core /// public static int FILE_SIZE_LIMIT = int.Parse(AppUtil.ReadValue("AppConfig", "FileSizeLimit", "200000000")); /// + /// 文件内容摘要切割长度 + /// + public static int FILE_CONTENT_BREVIARY_CUT_LENGTH = int.Parse(AppUtil.ReadValue("AppConfig", "FileContentBreviaryCutLength", "120")); + /// + /// 结果列表分页条数 + /// + public static int MRESULT_LIST_PAGE_SIZE = int.Parse(AppUtil.ReadValue("AppConfig", "ResultListPageSize", "100")); + /// /// 缓存池容量 /// public static int CACHE_POOL_CAPACITY = int.Parse(AppUtil.ReadValue("AppConfig", "CachePoolCapacity", "100000")); + /// + /// 索引更新任务间隔时间,单位:分 + /// + public static int INDEX_UPDATE_TASK_INTERVAL = int.Parse(AppUtil.ReadValue("AppConfig", "IndexUpdateTaskInterval", "10")); + /// /// 启用索引更新任务,默认启用 /// public static bool ENABLE_INDEX_UPDATE_TASK = bool.Parse(AppUtil.ReadValue("AppConfig", "EnableIndexUpdateTask", "True")); /// - /// 索引更新任务间隔时间,单位:分 + /// 启用预览摘要 /// - public static int INDEX_UPDATE_TASK_INTERVAL = int.Parse(AppUtil.ReadValue("AppConfig", "IndexUpdateTaskInterval", "10")); + public static bool ENABLE_PREVIEW_SUMMARY = bool.Parse(AppUtil.ReadValue("AppConfig", "EnablePreviewSummary", "False")); /// /// 最小工作线程(CPU线程数 * 2) @@ -93,7 +102,7 @@ namespace TextLocator.Core /// /// 匹配空白和换行 /// - public static readonly Regex REGEX_LINE_BREAKS_WHITESPACE = new Regex(@" |\r\r|\n\n|┄|\s"); + public static readonly Regex REGEX_LINE_BREAKS_WHITESPACE = new Regex(@" |\r\r|\n\n|\s\s|┄"); /// /// 匹配HTML和XML标签 /// diff --git a/TextLocator/Entity/FileInfo.cs b/TextLocator/Entity/FileInfo.cs index c1be0b1767314d3c34f95428669a1f8b79b07286..747d8ada48b486b744492e6b232bfb69ae0139f2 100644 --- a/TextLocator/Entity/FileInfo.cs +++ b/TextLocator/Entity/FileInfo.cs @@ -8,6 +8,13 @@ namespace TextLocator.Entity /// public class FileInfo { + // -------- 列表索引 -------- + /// + /// 列表序号 + /// + public int Index { get; set; } + + // -------- 文件信息 -------- /// /// 文件类型 /// @@ -43,7 +50,6 @@ namespace TextLocator.Entity /// private List keywords = new List(); public List Keywords { get => keywords; set => keywords = value; } - /// /// 搜索域 /// diff --git a/TextLocator/Factory/FileInfoServiceFactory.cs b/TextLocator/Factory/FileInfoServiceFactory.cs index 4b64b9ba56a0e17df7c434c39d73675e7671081b..48a4be80b2249786ed899c9cdeae195970c1bc75 100644 --- a/TextLocator/Factory/FileInfoServiceFactory.cs +++ b/TextLocator/Factory/FileInfoServiceFactory.cs @@ -32,52 +32,27 @@ namespace TextLocator.Factory /// public static string GetFileContent(string filePath) { - FileInfo fileInfo = new FileInfo(filePath); + // 读取文件内容 + string content = String.Empty; try { - // 如果文件存在 - if (fileInfo == null && !fileInfo.Exists) - { - throw new FileNotFoundException("文件未找到,请确认"); - } - // 文件太大 - if (fileInfo.Length > AppConst.FILE_SIZE_LIMIT) - { - throw new FileBigSizeException("不支持大于 " + FileUtil.GetFileSizeFriendly(AppConst.FILE_SIZE_LIMIT) + " 的文件解析"); - } - } - catch (Exception ex) - { - log.Error(filePath + "->" + ex.Message, ex); - - return null; - } + // 检查文件信息 + CheckFileInfo(filePath); - // 获取文件服务对象 - IFileInfoService fileInfoService = GetFileInfoService(FileTypeUtil.GetFileType(filePath)); + // 获取文件服务对象 + IFileInfoService fileInfoService = GetFileInfoService(FileTypeUtil.GetFileType(filePath)); - // 内容 - string content = ""; - // 缓存Key - string cacheKey = MD5Util.GetMD5Hash(filePath); + // 获取文件内容 + content = WaitTimeout(fileInfoService.GetFileContent, filePath); - if (CacheUtil.Exists(cacheKey)) - { - // 从缓存中读取 - content = CacheUtil.Get(cacheKey); -#if DEBUG - log.Debug(filePath + ",缓存生效。"); -#endif + // 特殊字符替换 + content = AppConst.REGEX_LINE_BREAKS_WHITESPACE.Replace(content, " "); } - else + catch (Exception ex) { - // 读取文件内容 - content = WaitTimeout(fileInfoService.GetFileContent, filePath); - - // 写入缓存 - CacheUtil.Put(cacheKey, content); + log.Error(filePath + " -> 文件读取错误:" + ex.Message, ex); } - + // 返回 return content; } @@ -109,6 +84,25 @@ namespace TextLocator.Factory throw new NotFoundFileServiceException("暂无[" + fileType.ToString() + "]服务实例, 返回默认其他类型文件服务实例"); } } + + /// + /// 检查文件信息 + /// + /// 文件路径 + private static void CheckFileInfo(string filePath) + { + FileInfo fileInfo = new FileInfo(filePath); + // 如果文件存在 + if (fileInfo == null && !fileInfo.Exists) + { + throw new FileNotFoundException("文件未找到,请确认"); + } + // 文件太大 + if (fileInfo.Length > AppConst.FILE_SIZE_LIMIT) + { + throw new FileBigSizeException(string.Format("不支持大于 {0} 的文件解析", FileUtil.GetFileSizeFriendly(AppConst.FILE_SIZE_LIMIT))); + } + } #endregion #region 超时函数 @@ -130,7 +124,11 @@ namespace TextLocator.Factory { string obj = null; AutoResetEvent are = new AutoResetEvent(false); - Thread t = new Thread(delegate () { obj = method(filePath); are.Set(); }); + Thread t = new Thread(() => + { + obj = method(filePath); + are.Set(); + }); t.Start(); Wait(t, are); return obj; diff --git a/TextLocator/FileInfoItem.xaml b/TextLocator/FileInfoItem.xaml index c34611bdebdb3490a93e69f6020fc548d16d91c7..3c7ad70dda7b3024b15a193987805df301ffe675 100644 --- a/TextLocator/FileInfoItem.xaml +++ b/TextLocator/FileInfoItem.xaml @@ -9,7 +9,7 @@ - + diff --git a/TextLocator/FileInfoItem.xaml.cs b/TextLocator/FileInfoItem.xaml.cs index 986e3031aade9eab2138c711ca63a0d00852eb8f..d9e4721702a7482e00c5af6c981cb4b41c1508b1 100644 --- a/TextLocator/FileInfoItem.xaml.cs +++ b/TextLocator/FileInfoItem.xaml.cs @@ -3,6 +3,8 @@ using System; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Media; +using System.Windows.Threading; +using TextLocator.Core; using TextLocator.Index; using TextLocator.Util; @@ -19,8 +21,7 @@ namespace TextLocator /// 文件信息显示条目 /// /// 文件信息 - /// 搜索域 - public FileInfoItem(Entity.FileInfo fileInfo, Enums.SearchRegion searchRegion) + public FileInfoItem(Entity.FileInfo fileInfo) { InitializeComponent(); @@ -28,14 +29,14 @@ namespace TextLocator try { - Refresh(fileInfo, searchRegion); + Refresh(fileInfo); } catch { Dispatcher.InvokeAsync(() => { try { - Refresh(fileInfo, searchRegion); + Refresh(fileInfo); } catch (Exception ex) { @@ -49,8 +50,7 @@ namespace TextLocator /// 刷新数据 /// /// 文件信息 - /// 搜索域 - public void Refresh(Entity.FileInfo fileInfo, Enums.SearchRegion searchRegion) + public void Refresh(Entity.FileInfo fileInfo) { // 根据文件类型显示图标 this.FileTypeIcon.Source = FileUtil.GetFileIcon(fileInfo.FileType); @@ -62,7 +62,7 @@ namespace TextLocator string fileName = fileInfo.FileName; // 显示文件名称 FileContentUtil.FillFlowDocument(this.FileName, fileName.Length > 55 ? fileName.Substring(0, 55) + "..." : fileName, (Brush)new BrushConverter().ConvertFromString("#1A0DAB"), true); - if (searchRegion == Enums.SearchRegion.文件名和内容 || searchRegion == Enums.SearchRegion.仅文件名) + if (fileInfo.SearchRegion == Enums.SearchRegion.文件名和内容 || fileInfo.SearchRegion == Enums.SearchRegion.仅文件名) { FileContentUtil.FlowDocumentHighlight(this.FileName, Colors.Red, fileInfo.Keywords); } @@ -73,29 +73,64 @@ namespace TextLocator // 获取摘要 FileContentUtil.EmptyRichTextDocument(this.ContentBreviary); - Task.Factory.StartNew(() => { + Task.Factory.StartNew(async () => { + await Task.Delay(AppConst.MRESULT_LIST_PAGE_SIZE / 2 * fileInfo.Index); string breviary = IndexCore.GetContentBreviary(fileInfo); - Dispatcher.InvokeAsync(() => + await Dispatcher.InvokeAsync(() => { FileContentUtil.FillFlowDocument(this.ContentBreviary, breviary, (Brush)new BrushConverter().ConvertFromString("#545454")); - if (searchRegion == Enums.SearchRegion.文件名和内容 || searchRegion == Enums.SearchRegion.仅文件内容) + if (fileInfo.SearchRegion == Enums.SearchRegion.文件名和内容 || fileInfo.SearchRegion == Enums.SearchRegion.仅文件内容) { FileContentUtil.FlowDocumentHighlight(this.ContentBreviary, Colors.Red, fileInfo.Keywords); } }); }); - // 词频统计 - Task.Factory.StartNew(() => { - string matchCountDetails = IndexCore.GetMatchCountDetails(fileInfo); - Dispatcher.InvokeAsync(() => { - if (!string.IsNullOrWhiteSpace(matchCountDetails)) - { - // 关键词匹配次数 - this.FileTypeIcon.ToolTip = matchCountDetails; - } - }); + // 词频统计明细 + Task.Factory.StartNew(async () => { + await Task.Delay(AppConst.MRESULT_LIST_PAGE_SIZE * fileInfo.Index); + LoadMatchCountDetails(fileInfo); }); } + + /// + /// 光标在文件类型图标边界移入事件(词频统计详情放在这里加载,主要是为了节省列表加载事件) + /// + /// + /// + private void FileTypeIcon_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) + { + if (this.FileTypeIcon.ToolTip == null) + { + LoadMatchCountDetails(this.Tag as Entity.FileInfo); + } + } + + /// + /// 加载词频统计详情放在这里 + /// + private void LoadMatchCountDetails(Entity.FileInfo fileInfo) + { + string matchCountDetails = IndexCore.GetMatchCountDetails(fileInfo); + if (!string.IsNullOrWhiteSpace(matchCountDetails)) + { + void Load() + { + this.FileTypeIcon.ToolTip = matchCountDetails; + ToolTipService.SetShowDuration(this.FileTypeIcon, 600000); + } + try + { + Load(); + } + catch + { + Dispatcher.InvokeAsync(() => + { + Load(); + }); + } + } + } } } diff --git a/TextLocator/HotkeyWindow.xaml b/TextLocator/HotkeyWindow.xaml index ff58c20c5be9d6ddac2e265c1aee15db90a0faed..9e462da476ea0fb5a15ab01d596a7a36ee6a3460 100644 --- a/TextLocator/HotkeyWindow.xaml +++ b/TextLocator/HotkeyWindow.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:TextLocator" mc:Ignorable="d" x:Name="hotkey" - Title="热键设置" Height="380" Width="520" WindowStartupLocation="CenterScreen" WindowStyle="ToolWindow" ResizeMode="CanMinimize" Icon="/Resource/App.ico" Loaded="Window_Loaded" Closed="Window_Closed" > + Title="热键设置" Height="380" Width="520" WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow" ResizeMode="CanMinimize" Icon="/Resource/App.ico" Loaded="Window_Loaded" Closed="Window_Closed" > diff --git a/TextLocator/Index/IndexCore.cs b/TextLocator/Index/IndexCore.cs index 3cc655b6429ccbce4a9fff81cf855664eed0ef6c..a14ccb153e41757da02f1140ef82f7b7eb13c3b1 100644 --- a/TextLocator/Index/IndexCore.cs +++ b/TextLocator/Index/IndexCore.cs @@ -888,7 +888,7 @@ namespace TextLocator.Index // 获取内容 string content = AppConst.REGEX_CONTENT_PAGE.Replace(fileInfo.Preview, ""); // 缩略信息 - string breviary = AppConst.REGEX_LINE_BREAKS_WHITESPACE.Replace(content, ""); + string breviary = AppConst.REGEX_LINE_BREAKS_WHITESPACE.Replace(content, " "); int min = 0; int max = breviary.Length; diff --git a/TextLocator/MainWindow.xaml b/TextLocator/MainWindow.xaml index d8437acdf66feed8dab8ac365c85e495949daba8..2d63d5733936c19298607e4f61f7209e163d3577 100644 --- a/TextLocator/MainWindow.xaml +++ b/TextLocator/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:rubyer="clr-namespace:Rubyer;assembly=Rubyer" mc:Ignorable="d" Title="文本搜索定位器" Width="1600" Height="900" Icon="Resource/App.ico" - WindowStartupLocation="CenterScreen" Loaded="Window_Loaded" Closing="Window_Closing" Activated="Window_Activated"> + WindowStartupLocation="CenterScreen" Loaded="Window_Loaded" Closing="Window_Closing" Activated="Window_Activated" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged"> @@ -97,6 +97,8 @@ + +