|
|
-
- using System;
- using System.Collections.Generic;
- using System.Data;
- 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 static AppTime.Recorder;
-
- namespace AppTime
- {
- class Controller
- {
- public WebServer Server =>Program.server;
- public Recorder Recorder=>Program.recorder;
-
-
- #region basic
-
- int getColor(string str)
- {
- return str.GetHashCode();
- }
-
- static Dictionary<long, int> appColors = new Dictionary<long, int>();
-
- int getAppColor(long appId)
- {
- if(!appColors.TryGetValue(appId, out var color))
- {
- var text = db.ExecuteValue<string>($"select text from app where id={appId}");
- lock (appColors)
- {
- color = appColors[appId] = getColor(text);
- }
- }
- return color;
- }
-
- static Dictionary<long, int> tagColors = new Dictionary<long, int>();
- int getTagColor(long tagId)
- {
- if (!tagColors.TryGetValue(tagId, out var color))
- {
- if (tagId == -1)
- {
- color = tagColors[tagId] = BitConverter.ToInt32(new byte[] { 0xFF, 0x99, 0x99, 0x99 }, 0);
- }
- else
- {
- var text = db.ExecuteValue<string>($"select text from tag where id={tagId}");
- color = tagColors[tagId] = getColor(text);
- }
- }
- return color;
- }
-
-
- public unsafe byte[] getPeriodBar(DateTime timefrom, DateTime timeto, string view, int width)
- {
- if (width <= 0 || width > 8000)
- {
- return null;
- }
-
-
- var totalsecs = (timeto - timefrom).TotalSeconds;
- IEnumerable<dynamic> data;
- if (view == "app")
- {
- data = db.ExecuteDynamic(@"
- select
- app.id appId,
- p.timeStart,
- p.timeEnd
- from app
- join win on win.appid = app.id
- join period p on p.winid = win.id
- where
- timeStart between @v0 and @v1
- or timeEnd between @v0 and @v1
- or @v0 between timeStart and timeEnd
- order by p.timeStart
- ",
- timefrom, timeto
- );
- }
- else
- {
- data = db.ExecuteDynamic(@"
- select
- ifnull(tag.id,-1) tagId,
- p.timeStart,
- p.timeEnd
- from app
- join win on win.appid = app.id
- join period p on p.winid = win.id
- left join tag on tag.id = win.tagId or (win.tagId = 0 and tag.id = app.tagId)
- where
- timeStart between @v0 and @v1
- or timeEnd between @v0 and @v1
- or @v0 between timeStart and timeEnd
- order by p.timeStart
- ",
- timefrom, timeto
- );
- }
-
- //绘制PeriodBar,直接写内存比gdi+快
- var imgdata = new int[width];
- foreach (var period in data)
- {
- var from = Math.Max(0, (int)Math.Round((period.timeStart - timefrom).TotalSeconds / totalsecs * width));
- var to = Math.Min(width - 1, (int)Math.Round((period.timeEnd - timefrom).TotalSeconds / totalsecs * width));
-
- for (var x = from; x <= Math.Min(width - 1, to); x++)
- {
- imgdata[x] = view == "app" ? (int)getAppColor(period.appId) : (int)getTagColor(period.tagId);
- }
- }
-
- fixed (int* p = &imgdata[0])
- {
- var ptr = new IntPtr(p);
- using var bmp = new Bitmap(width, 1, width * 4, PixelFormat.Format32bppArgb, ptr);
- using var mem = new MemoryStream();
- bmp.Save(mem, ImageFormat.Png);
- return mem.ToArray();
- }
-
- }
-
- public object getTree(DateTime timefrom, DateTime timeto, string view, long parentKey)
- {
- var result = new List<object>();
- var totalSeconds = (timeto - timefrom).TotalSeconds;
- IEnumerable<dynamic> data;
-
- if (view == "app")
- {
- if (parentKey == 0)
- {
- data = db.ExecuteDynamic(@"
- select
- app.id appId,
- app.text appText,
- tag.text tagText,
- sum(julianday(case when timeEnd > @v1 then @v1 else timeEnd end) -
- julianday(case when timeStart < @v0 then @v0 else timeStart end)) days
- from app
- join win on win.appid = app.id
- join period p on p.winid = win.id
- left join tag on tag.id = app.tagId
- where
- timeStart between @v0 and @v1
- or timeEnd between @v0 and @v1
- or @v0 between timeStart and timeEnd
- group by app.id
- order by days desc
- ",
- timefrom, timeto
- );
-
- foreach (var i in data)
- {
- var time = new TimeSpan((long)(i.days * TimeSpan.TicksPerDay));
- result.Add(
- new
- {
- i.appId,
- i.tagText,
- text = i.appText,
- time = time.ToString(@"hh\:mm\:ss"),
- percent = Math.Round(time.TotalSeconds * 100 / totalSeconds, 2) + "%",
- children = new object[0]
- }
- );
- }
-
- return result;
- }
-
- data = db.ExecuteDynamic(@"
- select
- win.id winId,
- win.text winText,
- tag.text tagText,
- sum(julianday(case when timeEnd > @v1 then @v1 else timeEnd end) -
- julianday(case when timeStart < @v0 then @v0 else timeStart end)) days
- from win
- join period p on p.winid = win.id
- left join tag on tag.id = win.tagId
- where
- win.appId = @v2
- and (
- timeStart between @v0 and @v1
- or timeEnd between @v0 and @v1
- or @v0 between timeStart and timeEnd
- )
- group by win.id
- order by days desc
- ",
- timefrom, timeto, parentKey
- );
-
- foreach (var i in data)
- {
- var time = new TimeSpan((long)(i.days * TimeSpan.TicksPerDay));
- result.Add(
- new
- {
- i.winId,
- i.tagText,
- text = string.IsNullOrWhiteSpace(i.winText) ? "(无标题)" : i.winText,
- time = time.ToString(@"hh\:mm\:ss"),
- percent = Math.Round(time.TotalSeconds * 100 / totalSeconds, 2) + "%"
- }
- );
- }
-
- return result;
- }
-
-
- if (parentKey == 0)
- {
-
- data = db.ExecuteDynamic(@"
- select
- ifnull(tag.id,-1) tagId,
- ifnull(tag.text, '(无标签)') tagText,
- sum(julianday(case when timeEnd > @v1 then @v1 else timeEnd end) -
- julianday(case when timeStart < @v0 then @v0 else timeStart end)) days
- from win
- join app on app.id = win.appid
- join period p on p.winid = win.id
- left join tag on tag.id = win.tagId or (win.tagId = 0 and tag.id = app.tagId)
- where
- timeStart between @v0 and @v1
- or timeEnd between @v0 and @v1
- or @v0 between timeStart and timeEnd
- group by tag.id
- order by days desc
- ",
- timefrom, timeto
- );
-
- foreach (var i in data)
- {
- var time = new TimeSpan((long)(i.days * TimeSpan.TicksPerDay));
- result.Add(
- new
- {
- i.tagId,
- i.tagText,
- time = time.ToString(@"hh\:mm\:ss"),
- percent = Math.Round(time.TotalSeconds * 100 / totalSeconds, 2) + "%"
- }
- );
- }
- return result;
-
- }
-
- data = db.ExecuteDynamic(@"
- select
- win.id winId,
- win.text winText,
- sum(julianday(case when timeEnd > @v1 then @v1 else timeEnd end) -
- julianday(case when timeStart < @v0 then @v0 else timeStart end)) days
- from win
- join period p on p.winid = win.id
- join app on app.id = win.appid
- left join tag on tag.id = win.tagId or (win.tagId = 0 and tag.id = app.tagId)
- where
- ifnull(tag.id, -1) = @v2
- and (
- timeStart between @v0 and @v1
- or timeEnd between @v0 and @v1
- or @v0 between timeStart and timeEnd
- )
- group by win.id
- order by days desc
- ",
- timefrom, timeto, parentKey
- );
-
- foreach (var i in data)
- {
- var time = new TimeSpan((long)(i.days * TimeSpan.TicksPerDay));
- result.Add(
- new
- {
- i.winId,
- text = string.IsNullOrWhiteSpace(i.winText) ? "(无标题)" : i.winText,
- time = time.ToString(@"hh\:mm\:ss"),
- percent = Math.Round(time.TotalSeconds * 100 / totalSeconds, 2) + "%"
- }
- );
- }
-
- return result;
- }
-
- /// <summary>
- /// 时间
- /// </summary>
- public class TimeInfo
- {
- /// <summary>
- /// 原始时间
- /// </summary>
- public DateTime timeSrc;
- /// <summary>
- /// 切换到应用的时间
- /// </summary>
- public DateTime timeStart;
- /// <summary>
- /// 应用名
- /// </summary>
- public string app;
- /// <summary>
- /// 应用id
- /// </summary>
- public long appId;
- /// <summary>
- /// 窗口标题
- /// </summary>
- public string title;
- }
-
- /// <summary>
- /// 获取指定时间的记录信息
- /// </summary>
- /// <param name="time"></param>
- /// <returns></returns>
- public TimeInfo getTimeInfo(DateTime time)
- {
-
- var data = db.ExecuteDynamic(@"
- SELECT timeStart, app.text appText, win.text winText, app.id appId
- from period
- join win on win.id = period.winid
- join app on app.id = win.appid
- where @v0 between timeStart and timeEnd
- limit 1",
- time
- ).FirstOrDefault();
-
- if (data == null)
- {
- return null;
- }
-
- return new TimeInfo
- {
- timeSrc = time,
- timeStart = data.timeStart,
- app = data.appText,
- title = data.winText,
- appId = data.appId
- };
- }
-
- static byte[] defaultIcon;
-
- public byte[] getIcon(int appId, bool large)
- {
- var path = Recorder.GetIconPath(appId, large);
- if (File.Exists(path))
- {
- return File.ReadAllBytes(path);
- }
- if (defaultIcon == null)
- {
- defaultIcon = File.ReadAllBytes("./webui/img/icon.png");
- }
- return defaultIcon;
- }
-
- static byte[] imageNone = null;
-
- TimeSpan getTime(string file)
- {
- return TimeSpan.ParseExact(Path.GetFileNameWithoutExtension(file), "hhmmss", CultureInfo.InvariantCulture);
- }
-
-
- /// <summary>
- /// 查找不满足条件的最后一个元素
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="items"></param>
- /// <param name="largerThenTarget"></param>
- /// <returns></returns>
- T find<T>(IList<T> items, Func<T, bool> largerThenTarget)
- {
- if (items.Count == 0)
- {
- return default;
- }
-
- var match = items[0];
- if (largerThenTarget(match))
- {
- return default;
- }
-
- for (var i = 1; i < items.Count; i++)
- {
- var item = items[i];
- if (largerThenTarget(item))
- {
- break;
- }
- match = item;
- }
- return match;
- }
-
-
- static Thread lastThread;
- static readonly object threadLock = new object();
- /// <summary>
- /// 获取指定时间的截图
- /// </summary>
- /// <param name="info"></param>
- /// <returns></returns>
- public byte[] getImage(TimeInfo info)
- {
-
- if (imageNone == null)
- {
- imageNone = File.ReadAllBytes(Path.Combine(Server.WebRootPath, "img", "none.png"));
- }
-
- //只响应最后一个请求,避免运行多个ffmpeg占用资源。
- lock (threadLock)
- {
- Ffmpeg.KillLastFfmpeg();
- if (lastThread != null && lastThread.IsAlive)
- {
- lastThread.Abort();
- }
- lastThread = Thread.CurrentThread;
- }
-
- try
- {
- //先从buffer中找
- {
- var buffers = new List<MemoryBuffer>(Recorder.flushing);
- if (Recorder.buffer != null)
- {
- buffers.Add(Recorder.buffer);
- }
-
- var match = find(buffers, i => i.StartTime > info.timeSrc);
- if (match != null)
- {
- var time = info.timeSrc - match.StartTime;
- var frame = find(match.Frames, f => (match.StartTime + f.Time) > info.timeSrc);
- if (frame != null)
- {
- return frame.Data;
- }
- }
- }
-
-
- //从文件系统找
- {
- var path = Recorder.getFileName(info.timeSrc);
- var needtime = info.timeSrc.TimeOfDay;
- var needtimetext = needtime.ToString("hhmmss");
- var match = (from f in Directory.GetFiles(Path.GetDirectoryName(path), "????????." + Recorder.ExName)
- where Path.GetFileNameWithoutExtension(f).CompareTo(needtimetext) < 0
- orderby f
- select f).LastOrDefault();
- if (match != null)
- {
- var time = needtime - getTime(match);
- var data = Ffmpeg.Snapshot(match, time);
- if (data != null && data.Length > 0)
- {
- return data;
- }
- }
-
- return imageNone;
- }
- }
- catch (ThreadAbortException)
- {
- return imageNone;
- }
- }
-
- #endregion
-
- #region tag
-
- private long nextTagId = 0;
- private long NextTagId()
- {
- if (nextTagId == 0)
- {
- nextTagId = db.ExecuteValue<long>("select ifnull(max(id), 0) + 1 from tag");
- }
- return nextTagId++;
- }
-
- public bool existsTag(string text)
- {
- return db.ExecuteValue< bool>(
- "select exists(select * from tag where text = @text)",
- new SQLiteParameter("text", text)
- );
- }
-
- public bool addTag(string text)
- {
- if(existsTag(text))
- {
- return false;
- }
-
- db.Execute(
- "insert into tag (id, text) values(@id, @text)",
- new SQLiteParameter("id", NextTagId()),
- new SQLiteParameter("text", text)
- );
- return true;
- }
-
- public void removeTag(int tagId)
- {
-
- db.Execute(
- "delete from tag where id = @id",
- new SQLiteParameter("id", tagId)
- );
-
- db.Execute($"update app set tagId=0 where tagId={tagId}");
- db.Execute($"update win set tagId=0 where tagId={tagId}");
-
- }
-
- public void clearAppTag(long appId)
- {
- db.Execute("update app set tagid = 0 where id = @v0", appId);
- }
-
- public void clearWinTag(long winId)
- {
- db.Execute("update win set tagid = 0 where id = @v0", winId);
- }
-
- public DataTable getTags()
- {
- return db.ExecuteTable("select id, text from tag order by id");
- }
-
-
- public bool isTagUsed(int tagId)
- {
- return db.ExecuteValue<bool>(
- @"select exists(
- select * from app where tagId = @tagId
- union all
- select * from win where tagId = @tagId
- )",
- new SQLiteParameter("tagId", tagId)
- );
- }
-
- DB db = DB.Instance;
-
- public bool renameTag(long tagId, string newName)
- {
- if(existsTag(newName))
- {
- return false;
- }
-
- db.Execute("update tag set text=@newName where id=@tagId",
- new SQLiteParameter("newName", newName),
- new SQLiteParameter("tagId", tagId)
- );
-
-
- return true;
- }
-
- public void tagApp(long appId, long tagId)
- {
- db.Execute(
- "update app set tagid = @tagId where id=@appId",
- new SQLiteParameter("appId", appId),
- new SQLiteParameter("tagId", tagId)
- );
- }
-
- public void tagWin(long winId, long tagId)
- {
- db.Execute(
- "update win set tagid = @tagId where id=@winId",
- new SQLiteParameter("winId", winId),
- new SQLiteParameter("tagId", tagId)
- );
- }
-
- #endregion
- }
- }
|