|
|
-
- using AppTime.Properties;
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data.SQLite;
- using System.Diagnostics;
- using System.Drawing;
- using System.Drawing.Imaging;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows.Forms;
-
- namespace AppTime
- {
- class Recorder
- {
- public const string ExName = "mkv";
-
- public Recorder()
- {
- BuildDataPath();
- }
-
- /// <summary>
- /// 统计周期。
- /// </summary>
- public int IntervalMs = 1000;
- public void Start()
- {
- new Thread(RecorderThreadProc) { IsBackground = true }.Start();
- }
-
- class App
- {
- public string WinText;
- public string AppProcess;
- public DateTime TimeStart;
- public long WinId;
- }
-
- public void BuildDataPath()
- {
- Directory.CreateDirectory(IconPath);
- Directory.CreateDirectory(ScreenPath);
- }
-
- Dictionary<int, Process> processes;
- public Process GetProcess(int processID)
- {
- if (processes != null && processes.TryGetValue(processID, out var p))
- {
- return p;
- }
-
- processes = Process.GetProcesses().ToDictionary(p => p.Id);
- return processes[processID];
- }
-
- class app
- {
- public long id;
- public string process;
- public Dictionary<string, win> wins = new Dictionary<string, win>();
- }
-
- class win
- {
- public long id;
- public string text;
- }
-
- long nextAppId = 0;
-
- Icon GetIcon(string fileName, bool largeIcon)
- {
- var shfi = new SHFILEINFO();
- WinApi.SHGetFileInfo(fileName, 0, ref shfi,
- (uint)Marshal.SizeOf(shfi),
- (uint)FileInfoFlags.SHGFI_ICON | (uint)FileInfoFlags.SHGFI_USEFILEATTRIBUTES
- | (uint)(largeIcon ? FileInfoFlags.SHGFI_LARGEICON : FileInfoFlags.SHGFI_SMALLICON)
- );
-
- return Icon.FromHandle(shfi.hIcon);
- }
-
- void SaveIcon(Icon icon, string filename)
- {
- using var img = icon.ToBitmap();
- img.Save(filename);
- }
-
- public string GetIconPath(long appId, bool large)
- {
- return Path.Combine(IconPath, $"{appId}{(large ? "l" : "s")}.png");
- }
-
- Dictionary<string, app> apps = new Dictionary<string, app>();
- app GetApp(Process process)
- {
- var name = process.ProcessName;
- if (apps.TryGetValue(name, out var app))
- {
- return app;
- }
-
- var data = db.ExecuteDynamic(
- @"select id from app where process = @process",
- new SQLiteParameter("process", name)
- ).FirstOrDefault();
-
- if (data == null)
- {
- if (nextAppId == 0)
- {
- nextAppId = (int)(long)db.ExecuteData("select ifnull(max(id),0) + 1 from app")[0][0];
- }
-
- var text = "";
- try
- {
- text = process.MainModule.FileVersionInfo.FileDescription;
-
- using var iconl = GetIcon(process.MainModule.FileName, true);
- SaveIcon(iconl, GetIconPath(nextAppId, true));
-
- using var icons = GetIcon(process.MainModule.FileName, false);
- SaveIcon(icons, GetIconPath(nextAppId, false));
- }
- catch (Win32Exception)
- {
- //ignore
- }
- catch (FileNotFoundException)
- {
- //ignore
- }
-
- if (string.IsNullOrWhiteSpace(text))
- {
- text = process.ProcessName;
- }
-
-
- db.Execute(
- "insert into app (id, process, text, tagId) values(@id, @process, @text, 0)",
- new SQLiteParameter("id", nextAppId),
- new SQLiteParameter("process", name),
- new SQLiteParameter("text", text)
- );
-
- app = new app { id = nextAppId, process = name };
- nextAppId++;
- }
- else
- {
- app = new app
- {
- id = data.id,
- process = name
- };
- }
-
- apps.Add(name, app);
-
- //fix icons
- var largeIconPath = GetIconPath(app.id, true);
- if (!File.Exists(largeIconPath))
- {
- try
- {
- using var iconl = GetIcon(process.MainModule.FileName, true);
- SaveIcon(iconl, largeIconPath);
-
- using var icons = GetIcon(process.MainModule.FileName, false);
- SaveIcon(icons, GetIconPath(app.id, false));
- }
- catch(Win32Exception)
- {
-
- }
- }
- return app;
-
- }
-
- long nextWinId = 0;
- win GetWin(Process process, string winText)
- {
- var app = GetApp(process);
- if (app.wins.TryGetValue(winText, out var win))
- {
- return win;
- }
-
- var data = db.ExecuteDynamic(
- "select id from win where appid=@appid and text=@winText",
- new SQLiteParameter("appid", app.id),
- new SQLiteParameter("winText", winText)
- ).FirstOrDefault();
-
- if (data == null)
- {
- if (nextWinId == 0)
- {
- nextWinId = (int)(long)db.ExecuteData("select ifnull(max(id),0) + 1 from win")[0][0];
- }
- db.Execute(
- "insert into win (id, appId, text) values(@id, @appId, @text)",
- new SQLiteParameter("id", nextWinId),
- new SQLiteParameter("appId", app.id),
- new SQLiteParameter("text", winText)
- );
- win = new win { id = nextWinId, text = winText };
- nextWinId++;
- }
- else
- {
- win = new win
- {
- id = data.id,
- text = winText
- };
- }
-
- app.wins.Add(winText, win);
- return win;
- }
-
- DB db = DB.Instance;
-
- public void RecorderThreadProc()
- {
- App lastApp = null;
- while (true)
- {
- var now = DateTime.Now;
- var hwnd = WinApi.GetForegroundWindow();
- var text = new StringBuilder(255);
- WinApi.GetWindowText(hwnd, text, 255);
- var winText = text.ToString();
- WinApi.GetWindowThreadProcessId(hwnd, out var processid);
- var process = GetProcess(processid);
- var appname = process.ProcessName;
-
- if (lastApp != null)
- {
- db.Execute(
- "update period set timeend = @v1 where timestart = @v0",
- lastApp.TimeStart,
- now.AddMilliseconds(-1)//必须减小,否则可能与下个周期开始时间重叠
- );
- }
-
- if (lastApp == null || lastApp.AppProcess != appname || lastApp.WinText != winText)
- {
-
- var win = GetWin(process, winText);
- lastApp = new App { WinId = win.id, AppProcess = appname, TimeStart = now, WinText = winText };
- db.Execute(
- "insert into [period](winid, timeStart, timeEnd) values(@v0, @v1, @v1)",
- win.id, now
- );
- }
-
- Screenshot(now);
-
- //等到下一个周期
- var nextTime = now.AddMilliseconds(IntervalMs);
- now = DateTime.Now;
- if(nextTime > now)
- {
- Thread.Sleep(nextTime - now);
- }
- }
- }
-
-
- string DataPath => string.IsNullOrWhiteSpace(Settings.Default.DataPath) ? Application.StartupPath : Settings.Default.DataPath;
- public string ScreenPath => Path.Combine(DataPath, "images");
- public string IconPath => Path.Combine(DataPath, "icons");
- ImageCodecInfo jpgcodec = ImageCodecInfo.GetImageDecoders().First(codec => codec.MimeType == "image/jpeg");
-
- ///// <summary>
- ///// 获取图片文件路径
- ///// </summary>
- ///// <param name="timeStart"></param>
- ///// <param name="timeImage"></param>
- ///// <returns></returns>
- //public string getImageFile(DateTime timeStart, DateTime timeImage)
- //{
- // var folder = Path.Combine(ScreenPath, timeImage.ToString("yyyyMMdd"));
- // var filename = $"{timeStart:HHmmss}+{Math.Round((timeImage - timeStart).TotalSeconds)}";
- // return Path.Combine(folder, $"{filename}.jpg");
- //}
-
- public string getFileName(DateTime time)
- {
- return Path.Combine(ScreenPath, $"{time:yyyyMMdd}", $"{time:HHmmss}." + Recorder.ExName);
- }
-
- DateTime lastCheck = DateTime.MinValue.Date;
-
- /// <summary>
- /// 截图
- /// </summary>
- /// <param name="now"></param>
- /// <param name="lastApp"></param>
- void Screenshot(DateTime now)
- {
- //检查记录天数限制
- if (lastCheck != now.Date)
- {
- var firstDate = now.Date.AddDays(-Settings.Default.RecordScreenDays);
- var dirs = Directory.EnumerateDirectories(ScreenPath, "????????");
- foreach (var i in dirs)
- {
- if (DateTime.TryParseExact(Path.GetFileName(i), "yyyyMMdd", CultureInfo.CurrentCulture, DateTimeStyles.None, out var date))
- {
- if (date < firstDate)
- {
- Directory.Delete(i);
- }
- }
- }
- lastCheck = now.Date;
- }
-
- if (Settings.Default.RecordScreenDays == 0)
- {
- return;
- }
-
- if (buffer == null)
- {
- buffer = new MemoryBuffer(now);
- }
-
- using var img = GetScreen();
- using var mem = new MemoryStream();
- img.Save(mem, ImageFormat.Jpeg);
- buffer.Frames.Add(new Frame(now - buffer.StartTime, mem.ToArray()));
- if ((now - buffer.StartTime).TotalSeconds >= 10 * 60 || now.Date != buffer.StartTime.Date) //固定为10mins,防止保存时间长,减少出问题时影响的时长。
- {
- FlushScreenBuffer();
- }
- }
-
-
- public void FlushScreenBuffer()
- {
- //切换到新buffer
- var newBuffer = new MemoryBuffer(DateTime.Now);
- var b = buffer;
- buffer = newBuffer;
-
- //加入flushing
- lock (flushing)
- {
- flushing.Add(b);
- }
- var path = getFileName(b.StartTime);
- var folder = Path.GetDirectoryName(path);
- if (!Directory.Exists(folder))
- {
- Directory.CreateDirectory(folder);
- }
- new Thread(() =>
- {
- Ffmpeg.Save(getFileName(b.StartTime), b.Frames.ToArray());
- lock (flushing)
- {
- flushing.Remove(b);
- }
- })
- {
- Priority = ThreadPriority.BelowNormal,
- IsBackground = false
- }.Start();
- }
-
-
- public class MemoryBuffer
- {
- public readonly DateTime StartTime;
- public readonly List<Frame> Frames = new List<Frame>();
- public MemoryBuffer(DateTime startTime)
- {
- StartTime = startTime;
- }
- }
-
- public MemoryBuffer buffer;
-
- public List<MemoryBuffer> flushing = new List<MemoryBuffer>();
-
- Bitmap GetScreen()
- {
- var result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
- using var g = Graphics.FromImage(result);
- retry:
- try
- {
- g.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size);
- }
- catch
- {
- goto retry;
- }
- return result;
- }
- }
- }
|