跳到主要内容

github/tai

公开

Tai · Windows 软件与网站使用时长统计。上游 Planshit/Tai 的源码镜像,非灵栈原创;MIT 许可。来源:https://github.com/Planshit/Tai 。本次为手动同步,暂不提供安装包。

浏览文件

Main.cs

567 行 · 18.1 KB历史下载
1using Core.Enums;
2using Core.Event;
3using Core.Librarys;
4using Core.Librarys.Browser;
5using Core.Librarys.Browser.Favicon;
6using Core.Librarys.SQLite;
7using Core.Models;
8using Core.Models.Config;
9using Core.Models.Config.Link;
10using Core.Servicers.Interfaces;
11using System;
12using System.Collections.Generic;
13using System.Data.Entity;
14using System.Diagnostics;
15using System.Drawing;
16using System.IO;
17using System.Linq;
18using System.Security.Cryptography;
19using System.Text;
20using System.Text.RegularExpressions;
21using System.Threading;
22using System.Threading.Tasks;
23using System.Timers;
24using System.Windows.Threading;
25
26namespace Core.Servicers.Instances
27{
28 public class Main : IMain
29 {
30 private readonly IAppObserver appObserver;
31 private readonly IData data;
32 private readonly ISleepdiscover sleepdiscover;
33 private readonly IAppConfig appConfig;
34 private readonly IAppData appData;
35 private readonly ICategorys categories;
36 private readonly IWebFilter _webFilter;
37 private readonly IAppTimerServicer _appTimer;
38 private readonly IWebServer _webServer;
39 private readonly IWebData _webData;
40 // 忽略的进程
41 private readonly string[] DefaultIgnoreProcess = new string[] {
42 "Tai",
43 "SearchHost",
44 "Taskmgr",
45 "ApplicationFrameHost",
46 "StartMenuExperienceHost",
47 "ShellExperienceHost",
48 "OpenWith",
49 "Updater",
50 "LockApp",
51 "dwm",
52 "SystemSettingsAdminFlows"
53 };
54
55 /// <summary>
56 /// 睡眠状态
57 /// </summary>
58 private SleepStatus sleepStatus;
59
60 /// <summary>
61 /// app config
62 /// </summary>
63 private ConfigModel config;
64
65 public event EventHandler OnUpdateTime;
66 public event EventHandler OnStarted;
67
68 /// <summary>
69 /// 忽略进程缓存列表
70 /// </summary>
71 private List<string> IgnoreProcessCacheList;
72
73 /// <summary>
74 /// 配置正则忽略进程列表
75 /// </summary>
76 private List<string> ConfigIgnoreProcessRegxList;
77 /// <summary>
78 /// 配置忽略进程名称列表
79 /// </summary>
80 private List<string> ConfigIgnoreProcessList;
81 // 更新应用日期
82 private DateTime updadteAppDateTime_ = DateTime.Now.Date;
83 // 已经更新过的应用列表
84 private List<string> updatedAppList = new List<string>();
85 private List<string> _configProcessNameWhiteList, _configProcessRegexWhiteList;
86 public Main(
87 IAppObserver appObserver,
88 IData data,
89 ISleepdiscover sleepdiscover,
90 IAppConfig appConfig,
91 IDateTimeObserver dateTimeObserver,
92 IAppData appData, ICategorys categories,
93 IWebFilter webFilter_,
94 IAppTimerServicer appTimer_,
95 IWebServer webServer_,
96 IWebData webData_)
97 {
98 this.appObserver = appObserver;
99 this.data = data;
100 this.sleepdiscover = sleepdiscover;
101 this.appConfig = appConfig;
102 this.appData = appData;
103 this.categories = categories;
104 _webFilter = webFilter_;
105 _appTimer = appTimer_;
106 _webServer = webServer_;
107 _webData = webData_;
108
109 IgnoreProcessCacheList = new List<string>();
110 ConfigIgnoreProcessRegxList = new List<string>();
111 ConfigIgnoreProcessList = new List<string>();
112 _configProcessNameWhiteList = new List<string>();
113 _configProcessRegexWhiteList = new List<string>();
114
115 sleepdiscover.SleepStatusChanged += Sleepdiscover_SleepStatusChanged;
116 appConfig.ConfigChanged += AppConfig_ConfigChanged;
117 _appTimer.OnAppDurationUpdated += _appTimer_OnAppDurationUpdated;
118 WebSocketEvent.OnWebLog += WebSocketEvent_OnWebLog;
119 }
120
121 private void AppConfig_ConfigChanged(ConfigModel oldConfig, ConfigModel newConfig)
122 {
123 if (oldConfig != newConfig)
124 {
125 // 处理开机自启
126 SystemCommon.SetStartup(newConfig.General.IsStartatboot);
127
128 // 更新忽略规则
129 UpdateConfigIgnoreProcess();
130
131 // 更新白名单
132 UpdateConfigProcessWhiteList();
133
134 // 处理web记录功能启停
135 HandleWebServiceConfig();
136 }
137 }
138
139 public async void Run()
140 {
141 await Task.Run(() =>
142 {
143 CreateDirectory();
144
145 // 数据库自检
146 using (var db = new TaiDbContext())
147 {
148 db.SelfCheck();
149 }
150
151 // 加载app信息
152 appData.Load();
153
154 // 加载分类信息
155 categories.Load();
156
157 AppState.IsLoading = false;
158 });
159
160
161
162 // 加载应用配置(确保配置文件最先加载
163 appConfig.Load();
164 config = appConfig.GetConfig();
165 UpdateConfigIgnoreProcess();
166 UpdateConfigProcessWhiteList();
167
168 // 初始化过滤器
169 _webFilter.Init();
170
171 // 启动主服务
172 Start();
173
174 OnStarted?.Invoke(this, EventArgs.Empty);
175 }
176 public void Start()
177 {
178 // appTimer必须比Observer先启动*
179 _appTimer.Start();
180 appObserver.Start();
181 if (config.General.IsWebEnabled)
182 {
183 _webServer.Start();
184 }
185 if (config.Behavior.IsSleepWatch)
186 {
187 // 启动睡眠监测
188 sleepdiscover.Start();
189 }
190 }
191 public void Stop()
192 {
193 appObserver.Stop();
194 _appTimer.Stop();
195 _webServer.Stop();
196 }
197 public void Exit()
198 {
199 appObserver?.Stop();
200 }
201
202 /// <summary>
203 /// 创建程序目录
204 /// </summary>
205 private void CreateDirectory()
206 {
207 string dir = Path.Combine(FileHelper.GetRootDirectory(), "Data");
208 Directory.CreateDirectory(dir);
209 }
210
211 private void UpdateConfigIgnoreProcess()
212 {
213 if (config == null)
214 {
215 return;
216 }
217 ConfigIgnoreProcessList.Clear();
218 ConfigIgnoreProcessRegxList.Clear();
219 IgnoreProcessCacheList.Clear();
220
221 ConfigIgnoreProcessList = config.Behavior.IgnoreProcessList.Where(m => !IsRegex(m)).ToList();
222 ConfigIgnoreProcessRegxList = config.Behavior.IgnoreProcessList.Where(m => IsRegex(m)).ToList();
223 }
224
225 private void UpdateConfigProcessWhiteList()
226 {
227 if (config == null)
228 {
229 return;
230 }
231 _configProcessNameWhiteList.Clear();
232 _configProcessRegexWhiteList.Clear();
233
234 _configProcessNameWhiteList = config.Behavior.ProcessWhiteList.Where(m => !IsRegex(m)).ToList();
235 _configProcessRegexWhiteList = config.Behavior.ProcessWhiteList.Where(m => IsRegex(m)).ToList();
236 }
237
238 private bool IsRegex(string str)
239 {
240 return Regex.IsMatch(str, @"[\.|\*|\?|\{|\\|\[|\^|\|]");
241 }
242
243 private void Sleepdiscover_SleepStatusChanged(Enums.SleepStatus sleepStatus)
244 {
245 this.sleepStatus = sleepStatus;
246
247 Logger.Info($"[{sleepStatus}]");
248 if (sleepStatus == SleepStatus.Sleep)
249 {
250 // 进入睡眠状态
251 Debug.WriteLine("进入睡眠状态");
252
253 // 通知sokcet客户端
254 _webServer?.SendMsg("sleep");
255 // 停止服务
256 Stop();
257
258 // 更新时间
259 UpdateAppDuration();
260 }
261 else
262 {
263 // 从睡眠状态唤醒
264 Debug.WriteLine("从睡眠状态唤醒");
265
266 _webServer?.SendMsg("wake");
267
268 Start();
269 }
270 }
271
272
273 /// <summary>
274 /// 检查应用是否需要记录数据
275 /// </summary>
276 /// <param name="processName"></param>
277 /// <param name="description"></param>
278 /// <param name="file"></param>
279 private bool IsCheckApp(string processName, string description, string file)
280 {
281 if (string.IsNullOrEmpty(file) || string.IsNullOrEmpty(processName) || DefaultIgnoreProcess.Contains(processName) || IgnoreProcessCacheList.Contains(processName))
282 {
283 return false;
284 }
285
286 // 从名称判断
287 if (ConfigIgnoreProcessList.Contains(processName))
288 {
289 return false;
290 }
291
292 // 正则表达式
293 foreach (string reg in ConfigIgnoreProcessRegxList)
294 {
295 if (RegexHelper.IsMatch(processName, reg) || RegexHelper.IsMatch(file, reg))
296 {
297 IgnoreProcessCacheList.Add(processName);
298 return false;
299 }
300 }
301
302 // 应用白名单过滤
303 if (config.Behavior.IsWhiteList && config.Behavior.ProcessWhiteList.Count > 0)
304 {
305 bool isWhite = false;
306 // 通过进程名称判断
307 if (_configProcessNameWhiteList.Contains(processName))
308 {
309 isWhite = true;
310 }
311 else
312 {
313 // 进程名称中匹配不到时尝试正则表达式
314 foreach (string reg in _configProcessRegexWhiteList)
315 {
316 if (RegexHelper.IsMatch(processName, reg) || RegexHelper.IsMatch(file, reg))
317 {
318 isWhite = true;
319 break;
320 }
321 }
322 }
323 // 白名单中找不到时不统计
324 Debug.WriteLine("白名单过滤结果:" + processName + " -> " + isWhite);
325 if (!isWhite) return false;
326 }
327
328 AppModel app = appData.GetApp(processName);
329 if (app == null)
330 {
331 // 记录应用信息
332
333 // 提取icon
334 string iconFile = Iconer.ExtractFromFile(file, processName, description);
335
336 appData.AddApp(new AppModel()
337 {
338 Name = processName,
339 Description = description,
340 File = file,
341 CategoryID = 0,
342 IconFile = iconFile,
343 });
344 }
345 else
346 {
347 if (updadteAppDateTime_ != DateTime.Now.Date)
348 {
349 updadteAppDateTime_ = DateTime.Now.Date;
350 updatedAppList.Clear();
351 }
352
353 if (!updatedAppList.Contains(processName))
354 {
355 // 更新应用信息
356 app.IconFile = Iconer.ExtractFromFile(file, processName, description);
357
358 if (app.Description != description)
359 {
360 app.Description = description;
361 }
362 if (app.File != file)
363 {
364 app.File = file;
365 }
366 appData.UpdateApp(app);
367 updatedAppList.Add(processName);
368 }
369 }
370
371 return true;
372 }
373
374 private void HandleLinks(string processName, int seconds, DateTime time)
375 {
376 Task.Run(() =>
377 {
378 try
379 {
380 List<LinkModel> links = config.Links != null ? config.Links : new List<LinkModel>();
381 foreach (LinkModel link in links)
382 {
383 if (link.ProcessList != null
384 && link.ProcessList.Count >= 2
385 && link.ProcessList.Contains(processName))
386 {
387 // 属于关联进程
388 foreach (string linkProcess in link.ProcessList)
389 {
390 if (linkProcess != processName)
391 {
392 if (IsProcessRuning(linkProcess))
393 {
394 // 同步更新
395 data.UpdateAppDuration(linkProcess, seconds, time);
396 }
397
398 }
399 }
400 break;
401 }
402 }
403
404 }
405 catch (Exception ex)
406 {
407 Logger.Error(ex.Message + ",关联进程更新错误,Process Name: " + processName + ",Time: " + seconds);
408 }
409 });
410 }
411
412 #region 判断进程是否在运行中
413 /// <summary>
414 /// 判断进程是否在运行中
415 /// </summary>
416 /// <param name="processName"></param>
417 /// <returns></returns>
418 private bool IsProcessRuning(string processName)
419 {
420 Process[] process = Process.GetProcessesByName(processName);
421 return process != null && process.Length > 0;
422 }
423 #endregion
424
425 #region 处理网站数据记录配置项开关
426 /// <summary>
427 /// 处理网站数据记录配置项开关
428 /// </summary>
429 private void HandleWebServiceConfig()
430 {
431 if (config == null)
432 {
433 return;
434 }
435
436 if (config.General.IsWebEnabled)
437 {
438 _webServer.Start();
439 }
440 else
441 {
442 _webServer.Stop();
443 }
444 }
445 #endregion
446
447 private void _appTimer_OnAppDurationUpdated(object sender, Event.AppDurationUpdatedEventArgs e)
448 {
449 UpdateAppDuration(e);
450 }
451
452 private void UpdateAppDuration()
453 {
454 UpdateAppDuration(_appTimer.GetAppDuration());
455 }
456 private void UpdateAppDuration(AppDurationUpdatedEventArgs e)
457 {
458 if (e == null) return;
459
460 try
461 {
462 var app = e.App;
463 int duration = e.Duration;
464 DateTime startTime = e.ActiveTime;
465
466 bool isCheck = IsCheckApp(app.Process, app.Description, app.ExecutablePath);
467 if (isCheck)
468 {
469 // 更新统计时长
470 data.UpdateAppDuration(app.Process, duration, startTime);
471 // 关联进程更新
472 HandleLinks(app.Process, duration, startTime);
473 OnUpdateTime?.Invoke(this, null);
474 // 自动分类
475 DispatchCateogry(app.Process, app.ExecutablePath);
476 }
477 }
478 catch (Exception ex)
479 {
480 Logger.Error(ex.ToString());
481 }
482 }
483
484 #region 浏览器记录
485 private void WebSocketEvent_OnWebLog(Models.WebPage.NotifyWeb args)
486 {
487 try
488 {
489 if (_webFilter.IsIgnore(args.Url))
490 {
491 Debug.WriteLine($"URL已被过滤,{args.Url}");
492 return;
493 }
494
495 // 记录数据
496 var site = new Models.WebPage.Site()
497 {
498 Url = args.Url,
499 Title = args.Title
500 };
501
502 _webData.AddUrlBrowseTime(site, args.Duration, args.ActiveDateTime);
503
504 // 处理图标
505 Task.Run(async () =>
506 {
507 string saveName = UrlHelper.GetName(args.Url) + DateTime.Now.ToString("yyyyMM") + ".ico";
508 string path = await FaviconDownloader.DownloadAsync(args.Icon, saveName);
509 _webData.UpdateUrlFavicon(site, path);
510 });
511 }
512 catch (Exception ex)
513 {
514 Logger.Error(ex.ToString());
515 }
516 }
517 #endregion
518
519 #region 自动分类
520 /// <summary>
521 /// 自动分类
522 /// </summary>
523 /// <param name="processName_">进程名称</param>
524 private void DispatchCateogry(string processName_, string executablePath_)
525 {
526 try
527 {
528 AppModel app = appData.GetApp(processName_);
529 if (app != null)
530 {
531 var categoryList = categories.GetCategories().Where(c => c.IsDirectoryMath && c.DirectoryList.Count > 0).ToList();
532 CategoryModel mathCategory = null;
533 foreach (var category in categoryList)
534 {
535 if (mathCategory != null)
536 {
537 break;
538 }
539 foreach (var item in category.DirectoryList)
540 {
541 string path = item.Replace("\\", "\\\\");
542 if (Regex.IsMatch(executablePath_, @"^" + path))
543 {
544 mathCategory = category;
545 Debug.WriteLine("匹配成功:" + category.Name);
546 break;
547 }
548 }
549
550 }
551 if (mathCategory != null)
552 {
553 // 匹配成功
554 app.Category = mathCategory;
555 app.CategoryID = mathCategory.ID;
556 appData.UpdateApp(app);
557 }
558 }
559 }
560 catch (Exception ex)
561 {
562 Logger.Error(ex.ToString());
563 }
564 }
565 #endregion
566 }
567}