Эх сурвалжийг харах

Add test app and refactor some code.

Justin 1 жил өмнө
parent
commit
a7a3b98491

+ 7 - 1
StationProbe.sln

@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.7.34221.43
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StationProbe", "StationProbe\StationProbe.csproj", "{F2BEB2F3-17A5-4DE0-8FD5-FF5A576CC621}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StationProbe", "StationProbe\StationProbe.csproj", "{F2BEB2F3-17A5-4DE0-8FD5-FF5A576CC621}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestViewer", "TestViewer\TestViewer.csproj", "{DEC90293-46D0-47B4-9EEB-4F6A09B5BE75}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
 		{F2BEB2F3-17A5-4DE0-8FD5-FF5A576CC621}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F2BEB2F3-17A5-4DE0-8FD5-FF5A576CC621}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F2BEB2F3-17A5-4DE0-8FD5-FF5A576CC621}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DEC90293-46D0-47B4-9EEB-4F6A09B5BE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DEC90293-46D0-47B4-9EEB-4F6A09B5BE75}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DEC90293-46D0-47B4-9EEB-4F6A09B5BE75}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DEC90293-46D0-47B4-9EEB-4F6A09B5BE75}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 12 - 2
StationProbe/Database.cs

@@ -1,4 +1,6 @@
-using SQLite;
+using System;
+using System.Linq;
+using SQLite;
 
 namespace StationProbe
 {
@@ -6,9 +8,17 @@ namespace StationProbe
     {
         private readonly SQLiteConnection _db;
 
+        public Database(string file)
+        {
+            _db = new SQLiteConnection(file);
+            _db.CreateTable<Image>();
+            _db.CreateTable<Exam>();
+            _db.CreateTable<BatchTask>();
+        }
+
         public Database()
         {
-            var drives = DriveInfo.GetDrives();
+            var drives = System.IO.DriveInfo.GetDrives();
             var largestDriver = drives.OrderByDescending(x=>x.TotalFreeSpace).First();
             _db = new SQLiteConnection($"{largestDriver.Name}database.db");
             _db.CreateTable<Image>();

+ 0 - 6
StationProbe/DingTalk.cs

@@ -27,12 +27,6 @@ namespace StationProbe
 
             [JsonPropertyName("errmsg")]
             public string ErrorMsg { get; set; } = string.Empty;
-
-            [JsonPropertyName("task_id")]
-            public string TaskId { get; set; } = string.Empty;
-
-            [JsonPropertyName("request_id")]
-            public string RequestId { get; set; } = string.Empty;
         }
 
         private readonly static HttpClient _client;

+ 20 - 9
StationProbe/Entities.cs

@@ -1,4 +1,5 @@
-using SQLite;
+using System;
+using SQLite;
 
 namespace StationProbe
 {
@@ -6,15 +7,15 @@ namespace StationProbe
     public class Image
     {
         [PrimaryKey]
-        public string Id { get; set; }
+        public string Id { get; set; } = string.Empty;
 
         [Indexed]
-        public string ExamId { get; set; }
+        public string ExamId { get; set; } = string.Empty;
 
         [Indexed]
         public int ImageIndex { get; set; }
 
-        public byte[] Data { get; set; }
+        public byte[] Data { get; set; } = new byte[0];
 
         public DateTime CreateTime { get; set; }
     }
@@ -25,7 +26,7 @@ namespace StationProbe
         [PrimaryKey, AutoIncrement]
         public int Id { get; set; }
 
-        public string Name { get; set; }
+        public string Name { get; set; } = string.Empty;
 
         public int PageCount { get; set; }
 
@@ -36,6 +37,11 @@ namespace StationProbe
         public DateTime End { get; set; }
 
         public DateTime CreateTime { get; set; }
+
+        public override string ToString()
+        {
+            return Name;
+        }
     }
 
 
@@ -43,17 +49,17 @@ namespace StationProbe
     internal class Exam
     {
         [PrimaryKey]
-        public string Id { get; set; }
+        public string Id { get; set; } = string.Empty;
 
-        public string PatientName { get; set; }
+        public string PatientName { get; set; } = string.Empty;
 
         public int PatientAge { get; set; }
 
-        public string PatientSex { get; set; }
+        public string PatientSex { get; set; } = string.Empty;
 
         public DateTime ExamDate { get; set; }
 
-        public string Report { get; set; }
+        public string Report { get; set; } = string.Empty;
 
 
         [Indexed]
@@ -69,5 +75,10 @@ namespace StationProbe
         public int ExamIndexInPage { get; set; }
 
         public DateTime CreateTime { get; set; }
+
+        public override string ToString()
+        {
+            return $"[{CreateTime}]#{PatientName}";
+        }
     }
 }

+ 273 - 198
StationProbe/ProbeTask.cs

@@ -1,5 +1,4 @@
 using PuppeteerSharp;
-using static System.Net.Mime.MediaTypeNames;
 
 namespace StationProbe
 {
@@ -12,7 +11,7 @@ namespace StationProbe
 
     internal static class ElementConverter
     {
-        public static T? As<T>(this object obj) where T: class
+        public static T? As<T>(this object obj) where T : class
         {
             return obj as T;
         }
@@ -30,18 +29,26 @@ namespace StationProbe
         }
     }
 
-    internal abstract class ProbeTask
+    internal interface IExamStore:IDisposable
     {
-        private static string _debugFolder = string.Empty;
-        protected readonly Database _db;
+        Exam? GetLastExam(int batchTaskId);
+        void AddExam(Exam exam);
+        void AddImage(Image image);
+        void UpdateBatchTask(BatchTask batchTask);
+    }
 
-        protected ScreenshotOptions _snapshotOpts = new ScreenshotOptions() {Type = ScreenshotType.Png };
+    internal abstract class ProbeTask
+    {
         public static bool DebugMode { get; set; }
 
+        private static string _debugFolder = string.Empty;
+
+        protected ScreenshotOptions _snapshotOpts = new ScreenshotOptions() { Type = ScreenshotType.Png };
 
-        public ProbeTask(Database db)
+        protected readonly IExamStore _examStore;
+        public ProbeTask(IExamStore examStore)
         {
-            _db = db;
+            _examStore = examStore;
         }
 
         public static void InitDebugFolder()
@@ -74,11 +81,11 @@ namespace StationProbe
         public abstract Task<ElementCollection> ExecuteAsync(ElementCollection inputs);
     }
 
-    internal class LoginTask: ProbeTask
+    internal class LoginTask : ProbeTask
     {
         private readonly string _loginUrl;
 
-        public LoginTask(Database db, string loginUrl) :base(db)
+        public LoginTask(IExamStore examStore, string loginUrl) : base(examStore)
         {
             _loginUrl = loginUrl;
         }
@@ -90,7 +97,7 @@ namespace StationProbe
             {
                 await page.GoToAsync(_loginUrl);
 
-                if(DebugMode)
+                if (DebugMode)
                 {
                     var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
                     var fileName = "100000000.jpg";
@@ -98,9 +105,11 @@ namespace StationProbe
                 }
 
                 //username
-                var userElement = await page.WaitForSelectorAsync("input[id=\"userName\"]");
-                await userElement.FocusAsync();
-                await page.Keyboard.TypeAsync("justin.xing");
+                await using (var userElement = await page.WaitForSelectorAsync("input[id=\"userName\"]"))
+                {
+                    await userElement.FocusAsync();
+                    await page.Keyboard.TypeAsync("justin.xing");
+                }
 
                 if (DebugMode)
                 {
@@ -110,9 +119,11 @@ namespace StationProbe
                 }
 
                 //password
-                var passwordElement = await page.WaitForSelectorAsync("input[id=\"password\"]");
-                await passwordElement.FocusAsync();
-                await page.Keyboard.TypeAsync("vinno@123");
+                await using (var passwordElement = await page.WaitForSelectorAsync("input[id=\"password\"]"))
+                {
+                    await passwordElement.FocusAsync();
+                    await page.Keyboard.TypeAsync("vinno@123");
+                }
 
                 if (DebugMode)
                 {
@@ -122,9 +133,11 @@ namespace StationProbe
                 }
 
                 //login
-                var loginButtonElement = await page.WaitForSelectorAsync("button[b-kvd270uhz9]");
-                await loginButtonElement.ClickAsync();
-                await page.WaitForNetworkIdleAsync();
+                await using (var loginButtonElement = await page.WaitForSelectorAsync("button[b-kvd270uhz9]"))
+                {
+                    await loginButtonElement.ClickAsync();
+                    await page.WaitForNetworkIdleAsync();
+                }
 
                 if (DebugMode)
                 {
@@ -137,9 +150,9 @@ namespace StationProbe
         }
     }
 
-    internal class MenuTask:ProbeTask
+    internal class MenuTask : ProbeTask
     {
-        public MenuTask(Database db) : base(db)
+        public MenuTask(IExamStore examStore) : base(examStore)
         {
         }
 
@@ -149,18 +162,17 @@ namespace StationProbe
             var page = inputs[ElementType.Page].As<IPage>();
             if (page != null)
             {
-                //var frame = await page.WaitForFrameAsync("");
-                //inputs.AddObject(ElementType.Frame, frame);
-
-                var menuElement = await page.QuerySelectorAsync("a[href=\"feedback/list\"]");
-                await menuElement.ClickAsync();
-                await page.WaitForNetworkIdleAsync();
-                if (DebugMode)
+                await using (var menuElement = await page.WaitForSelectorAsync("a[href=\"feedback/list\"]"))
                 {
-                    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "500000000.jpg";
-                    SaveDebugSnapshot(fileName, imageData);
+                    await menuElement.ClickAsync();
+                    if (DebugMode)
+                    {
+                        var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
+                        var fileName = "500000000.jpg";
+                        SaveDebugSnapshot(fileName, imageData);
+                    }
                 }
+
             }
             return inputs;
         }
@@ -175,7 +187,7 @@ namespace StationProbe
 
         public int ExamCount { get; private set; }
 
-        public FilterTask(Database db, DateTime start, DateTime end):base(db)
+        public FilterTask(IExamStore examStore, DateTime start, DateTime end) : base(examStore)
         {
             _start = start;
             _end = end;
@@ -185,87 +197,96 @@ namespace StationProbe
         {
             var page = inputs[ElementType.Page].As<IPage>();
             //var frame = inputs[ElementType.Frame].As<IFrame>();
-            if(page!= null) //&& frame != null) 
+            if (page != null) //&& frame != null) 
             {
-                var filterElements = await page.QuerySelectorAllAsync("div[class=\"me-3 my-1\"]");
-                var filterElement1 = filterElements[3];
-                var filterElement2 = filterElements[4];
-                
-                //show filter dialog
-                if (DebugMode)
-                {
-                    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "600000000.jpg";
-                    SaveDebugSnapshot(fileName, imageData);
-                }
+                //var filterElements = await page.QuerySelectorAllAsync("div[class=\"me-3 my-1\"]");
+                //var filterElement1 = filterElements[3];
+                //var filterElement2 = filterElements[4];
 
-                //input start
-                var openElement1 = await filterElement1.WaitForSelectorAsync("div[class=\"multi-select\"]");
-                await openElement1.ClickAsync();
-                var optionListElement1 = await filterElement1.WaitForSelectorAsync("div[class=\"dropdown-menu shadow\"]");
-                var optionElements = await optionListElement1.QuerySelectorAllAsync("div[class=\"dropdown-item\"]");
-                var targetElement = optionElements[0];
-                await targetElement.ClickAsync();
+                ////show filter dialog
+                //if (DebugMode)
+                //{
+                //    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
+                //    var fileName = "600000000.jpg";
+                //    SaveDebugSnapshot(fileName, imageData);
+                //}
 
-                if (DebugMode)
-                {
-                    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "700000000.jpg";
-                    SaveDebugSnapshot(fileName, imageData);
-                }
+                ////input start
+                //var openElement1 = await filterElement1.WaitForSelectorAsync("div[class=\"multi-select\"]");
+                //await openElement1.ClickAsync();
+                //var optionListElement1 = await filterElement1.WaitForSelectorAsync("div[class=\"dropdown-menu shadow\"]");
+                //var optionElements = await optionListElement1.QuerySelectorAllAsync("div[class=\"dropdown-item\"]");
+                //var targetElement = optionElements[0];
+                //await targetElement.ClickAsync();
 
-                var openlement2 = await filterElement2.WaitForSelectorAsync("div[class=\"multi-select\"]");
-                await openlement2.ClickAsync();
-                var optionListElement2 = await filterElement2.WaitForSelectorAsync("div[class=\"dropdown-menu shadow\"]");
-                optionElements = await optionListElement2.QuerySelectorAllAsync("div[class=\"dropdown-item\"]");
-                targetElement = optionElements[0];
-                await targetElement.ClickAsync();
+                //if (DebugMode)
+                //{
+                //    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
+                //    var fileName = "700000000.jpg";
+                //    SaveDebugSnapshot(fileName, imageData);
+                //}
 
-                //input end
-                if (DebugMode)
-                {
-                    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "800000000.jpg";
-                    SaveDebugSnapshot(fileName, imageData);
-                }
+                //var openlement2 = await filterElement2.WaitForSelectorAsync("div[class=\"multi-select\"]");
+                //await openlement2.ClickAsync();
+                //var optionListElement2 = await filterElement2.WaitForSelectorAsync("div[class=\"dropdown-menu shadow\"]");
+                //optionElements = await optionListElement2.QuerySelectorAllAsync("div[class=\"dropdown-item\"]");
+                //targetElement = optionElements[0];
+                //await targetElement.ClickAsync();
 
-                //await frame.WaitForSelectorAsync("input[_bl_13]");
-                //await frame.FocusAsync("input[_bl_13]");
-                //await page.Keyboard.TypeAsync("justin.xing");
+                ////input end
+                //if (DebugMode)
+                //{
+                //    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
+                //    var fileName = "800000000.jpg";
+                //    SaveDebugSnapshot(fileName, imageData);
+                //}
 
-                var searchTableElement = await page.QuerySelectorAsync("div[class=\"table-search\"]");
-                var searchButtonElement = await searchTableElement.QuerySelectorAsync("button[class=\"btn btn-primary btn-xs ms-2\"]");
-                await searchButtonElement.ClickAsync();
-                await page.WaitForNetworkIdleAsync();
+                //var searchTableElement = await page.QuerySelectorAsync("div[class=\"table-search\"]");
+                //var searchButtonElement = await searchTableElement.QuerySelectorAsync("button[class=\"btn btn-primary btn-xs ms-2\"]");
+                //await searchButtonElement.ClickAsync();
+                //await page.WaitForNetworkIdleAsync();
 
-                if (DebugMode)
+                //if (DebugMode)
+                //{
+                //    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
+                //    var fileName = "900000000.jpg";
+                //    SaveDebugSnapshot(fileName, imageData);
+                //}
+
+                await using (var pageinationElement = await page.QuerySelectorAsync("div[class=\"table-pagination\"]"))
                 {
-                    var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "900000000.jpg";
-                    SaveDebugSnapshot(fileName, imageData);
+                    await using (var examCountElement = await pageinationElement.QuerySelectorAsync("div[class=\"d-none d-sm-inline-block\"]"))
+                    {
+                        var examCountStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", examCountElement);
+                        var words = examCountStr.Split(' ');
+                        ExamCount = int.Parse(words[1]);
+                    }
+
                 }
 
-                var pageinationElement = await page.QuerySelectorAsync("div[class=\"table-pagination\"]");
-                var examCountElement = await pageinationElement.QuerySelectorAsync("div[class=\"d-none d-sm-inline-block\"]");
-                var examCountStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", examCountElement);
-                var words = examCountStr.Split(' ');
-                ExamCount = int.Parse(words[1]);
-
-                var pagesElement = await page.QuerySelectorAsync("ul[class=\"pagination\"]");
-                var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
-                var lastPageElement = pageItemElements[pageItemElements.Length - 2];
-                var textElement = await lastPageElement.QuerySelectorAsync("a");
-                var lastPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
-                PageCount = int.Parse(lastPageStr);
+                await using (var pagesElement = await page.QuerySelectorAsync("ul[class=\"pagination\"]"))
+                {
+                    var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
+                    var lastPageElement = pageItemElements[pageItemElements.Length - 2];
+                    await using (var textElement = await lastPageElement.QuerySelectorAsync("a"))
+                    {
+                        var lastPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
+                        PageCount = int.Parse(lastPageStr);
+                    }
+                    foreach (var pageItemElement in pageItemElements)
+                    {
+                        await pageItemElement.DisposeAsync();
+                    }
+                }
             }
             return inputs;
         }
     }
 
-    internal class PageTask:ProbeTask
+    internal class PageTask : ProbeTask
     {
         private readonly int _page;
-        public PageTask(Database db, int page):base(db)
+        public PageTask(IExamStore examStore, int page) : base(examStore)
         {
             _page = page;
         }
@@ -274,24 +295,38 @@ namespace StationProbe
         {
             while (true)
             {
-                var pagesElement = await page.QuerySelectorAsync("ul[class=\"pagination\"]");
-                var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
-                var currentPageElemment = await pagesElement.QuerySelectorAsync("li[class=\"page-item active\"]");
-                var textElement = await currentPageElemment.QuerySelectorAsync("a");
-                var currentPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
-                var currentPage = int.Parse(currentPageStr);
-                if(currentPage != _page+1)
-                {
-                    var nextPageElement = pageItemElements[pageItemElements.Length - 1];
-                    await nextPageElement.ClickAsync();
-                    await page.WaitForNetworkIdleAsync();
-                }
-                else
+                await using (var pagesElement = await page.WaitForSelectorAsync("ul[class=\"pagination\"]"))
                 {
-                    break;
+                    var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
+                    await using (var currentPageElemment = await pagesElement.QuerySelectorAsync("li[class=\"page-item active\"]"))
+                    {
+                        await using (var textElement = await currentPageElemment.QuerySelectorAsync("a"))
+                        {
+                            var currentPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
+                            var currentPage = int.Parse(currentPageStr);
+                            if (currentPage != _page + 1)
+                            {
+                                var nextPageElement = pageItemElements[pageItemElements.Length - 1];
+                                await nextPageElement.ClickAsync();
+                                foreach(var pageItemElement in pageItemElements)
+                                {
+                                   await pageItemElement.DisposeAsync();
+                                }
+                            }
+                            else
+                            {
+                                foreach (var pageItemElement in pageItemElements)
+                                {
+                                    await pageItemElement.DisposeAsync();
+                                }
+                                break;
+                            }
+                        }
+                    }
                 }
+                await Task.Delay(100);
             }
-            
+
         }
 
         public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
@@ -304,7 +339,7 @@ namespace StationProbe
                 if (DebugMode)
                 {
                     var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "9" + _page.ToString("D5") +"000.jpg";
+                    var fileName = "9" + _page.ToString("D5") + "000.jpg";
                     SaveDebugSnapshot(fileName, imageData);
                 }
             }
@@ -312,14 +347,14 @@ namespace StationProbe
         }
     }
 
-    internal class ExamTask:ProbeTask
+    internal class ExamTask : ProbeTask
     {
         private readonly int _batchTaskId;
         private readonly int _pageIndex;
         private readonly int _examIndex;
         private readonly int _examIndexInPage;
 
-        public ExamTask(Database db, int batchTaskId, int pageIndex, int examIndex, int examIndexInPage): base(db)
+        public ExamTask(IExamStore examStore, int batchTaskId, int pageIndex, int examIndex, int examIndexInPage) : base(examStore)
         {
             _batchTaskId = batchTaskId;
             _pageIndex = pageIndex;
@@ -331,24 +366,87 @@ namespace StationProbe
         {
             while (true)
             {
-                var pagesElement = await page.QuerySelectorAsync("ul[class=\"pagination\"]");
-                var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
-                var currentPageElemment = await pagesElement.QuerySelectorAsync("li[class=\"page-item active\"]");
-                var textElement = await currentPageElemment.QuerySelectorAsync("a");
-                var currentPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
-                var currentPage = int.Parse(currentPageStr);
-                if (currentPage != _pageIndex + 1)
+                await using (var pagesElement = await page.WaitForSelectorAsync("ul[class=\"pagination\"]"))
                 {
-                    var nextPageElement = pageItemElements[pageItemElements.Length - 1];
-                    await nextPageElement.ClickAsync();
-                    await page.WaitForNetworkIdleAsync();
+                    var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
+                    await using (var currentPageElemment = await pagesElement.QuerySelectorAsync("li[class=\"page-item active\"]"))
+                    {
+                        await using (var textElement = await currentPageElemment.QuerySelectorAsync("a"))
+                        {
+                            var currentPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
+                            var currentPage = int.Parse(currentPageStr);
+                            if (currentPage != _pageIndex + 1)
+                            {
+                                var nextPageElement = pageItemElements[pageItemElements.Length - 1];
+                                await nextPageElement.ClickAsync();
+                                foreach (var pageItemElement in pageItemElements)
+                                {
+                                    await pageItemElement.DisposeAsync();
+                                }
+                            }
+                            else
+                            {
+                                foreach (var pageItemElement in pageItemElements)
+                                {
+                                    await pageItemElement.DisposeAsync();
+                                }
+                                break;
+                            }
+                        }
+                    }
                 }
-                else
+                await Task.Delay(100);
+            }
+
+        }
+
+        private async Task EnterExamAsync(IPage page)
+        {
+            await JumpToPageAsync(page);
+            await using (var tableElement = await page.QuerySelectorAsync("table[class=\"table table-bordered table-striped table-hover\"]"))
+            {
+                var examLinkElements = await tableElement.QuerySelectorAllAsync("div[style=\"cursor:pointer;\"]");
+                var eaxmLinkElement = examLinkElements[_examIndexInPage];
+                await eaxmLinkElement.ClickAsync();
+                foreach(var examLinkElement in examLinkElements)
                 {
-                    break;
+                    await examLinkElement.DisposeAsync();
                 }
             }
+        }
 
+        private async Task ExitExamAsync(IPage page)
+        {
+            //Wait for the back button.
+            await using (var backButtonElement = await page.WaitForSelectorAsync("button[class=\"btn fix-item feedback-common-button\"]"))
+            {
+                await page.EvaluateFunctionAsync("b => b.click()", backButtonElement);
+            }
+        }
+
+        private async void DoSaveExamImageAsync(IPage page, string examId, int imageIndex, string imageUrl)
+        {
+            var response = await page.WaitForResponseAsync(imageUrl);
+            var imageData = await response.BufferAsync();
+            var image = new Image()
+            {
+                Id = Guid.NewGuid().ToString("N"),
+                ExamId = examId,
+                ImageIndex = imageIndex,
+                Data = imageData,
+                CreateTime = DateTime.Now,
+            };
+            _examStore.AddImage(image);
+            Logger.WriteLine($"Image:{imageUrl} stored.");
+        }
+
+        private async Task SaveExamImageAsync(IPage page, string examId, int imageIndex, string imageUrl)
+        {
+            await using (var imagePage = await page.Browser.NewPageAsync())
+            {
+                DoSaveExamImageAsync(imagePage, examId, imageIndex, imageUrl);
+                await imagePage.GoToAsync(imageUrl);
+            }
         }
 
         public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
@@ -357,27 +455,46 @@ namespace StationProbe
             //var frame = inputs[ElementType.Frame].As<IFrame>();
             if (page != null) //&& frame != null)
             {
-                await JumpToPageAsync(page);
-                var tableElement = await page.QuerySelectorAsync("table[class=\"table table-bordered table-striped table-hover\"]");
-                var examLinkElements = await tableElement.QuerySelectorAllAsync("div[style=\"cursor:pointer;\"]");
-                var eaxmLinkElement = examLinkElements[_examIndexInPage];
-                var patientName = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", eaxmLinkElement);
-                //var examDateStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", examLinkElements[6]);
-                var examDate = DateTime.Now; //Parse(examDateStr);
-                //Enter exam
-                await eaxmLinkElement.ClickAsync();
-                //await page.WaitForNavigationAsync();
-                await page.WaitForNetworkIdleAsync();
+                var examId = Guid.NewGuid().ToString("N");
+                //First time enter exam.
+                await EnterExamAsync(page);
+                //wait for page loaded.
+                await Task.Delay(1000);
+                //Get all image urls.
+                var imageUrls = new List<string>();
+                await using (var backButtonElement = await page.WaitForSelectorAsync("button[class=\"btn fix-item feedback-common-button\"]"))
+                {
+                    var imageDivElements = await page.QuerySelectorAllAsync("div[class=\"bb-img is-preview\"]");
+                    if (imageDivElements != null)
+                    {
+                        foreach (var imageDivElement in imageDivElements)
+                        {
+                            await using (var imageElement = await imageDivElement.QuerySelectorAsync("img"))
+                            {
+                                await using (var imageUrl = await imageElement.GetPropertyAsync("src"))
+                                {
+                                    var url = await imageUrl.JsonValueAsync<string>();
+                                    imageUrls.Add(url);
+                                }
+                            }
+                            await imageDivElement.DisposeAsync();
+                        }
+                    }
+                }
+                Logger.WriteLine($"Get image url count:{imageUrls.Count}");
+
+                for (var i = 0; i < imageUrls.Count; i++)
+                {
+                    await SaveExamImageAsync(page, examId, i, imageUrls[i]);
+                }
 
                 if (DebugMode)
                 {
                     var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
-                    var fileName = "9" + _pageIndex.ToString("D5") + _examIndexInPage.ToString("D3") +".jpg";
+                    var fileName = "9" + _pageIndex.ToString("D5") + _examIndexInPage.ToString("D3") + ".jpg";
                     SaveDebugSnapshot(fileName, imageData);
                 }
 
-                var exmaId = Guid.NewGuid().ToString("N");
-                
                 //Get patient info
                 //var patientName = "";
                 var patientSex = "";
@@ -386,72 +503,28 @@ namespace StationProbe
 
                 var report = "";
                 var reportElements = await page.QuerySelectorAllAsync("div[class=\"col\"]");
-                foreach ( var reportElement in reportElements)
+                foreach (var reportElement in reportElements)
                 {
-                    var strong = await reportElement.QuerySelectorAsync("strong");
-                    if(strong != null)
+                    await using (var strong = await reportElement.QuerySelectorAsync("strong"))
                     {
-                        var content = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", strong);
-                        report += " " + content;
-                    }
-                }
-
-                //Get report
-                //var report = "";
-
-                //Save images.
-                var imageUrls = new List<string>();
-                var imageDivElements = await page.QuerySelectorAllAsync("div[class=\"bb-img is-preview\"]");
-                if(imageDivElements != null)
-                {
-                    for(var i=0;i<imageDivElements.Length;i++)
-                    {
-                        var imageElement = await imageDivElements[i].WaitForSelectorAsync("img");
-                        var imageUrl = await imageElement.GetPropertyAsync("src");
-                        var url = await imageUrl.JsonValueAsync<string>();
-                        imageUrls.Add(url);
-                    }
-                    await page.ReloadAsync();
-                    Parallel.For(0, imageUrls.Count - 1, async(i) =>
-                    {
-                        var url = imageUrls[i];
-                        var response = await page.WaitForResponseAsync(response => (response.Request.Url == url && response.Ok));
-                        var buffer = await response.BufferAsync();
-                        var image = new Image()
+                        if (strong != null)
                         {
-                            Id = Guid.NewGuid().ToString("N"),
-                            ExamId = exmaId,
-                            ImageIndex = i,
-                            Data = buffer,
-                            CreateTime = DateTime.Now,
-                        };
-                        _db.AddImage(image);
-                    });
+                            var content = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", strong);
+                            report += " " + content;
+                        }
+                    }
+                    await reportElement.DisposeAsync();
                 }
-                //var imageUrls = new List<string>();
-                //for (var i = 0; i < imageUrls.Count; i++)
-                //{
-                //    var response = await page.WaitForResponseAsync(imageUrls[i]);
-                //    var buffer = await response.BufferAsync();
-                //    var image = new Image()
-                //    {
-                //        Id = Guid.NewGuid().ToString("N"),
-                //        ExamId = exmaId,
-                //        ImageIndex = i,
-                //        Data = buffer,
-                //        CreateTime = DateTime.Now,
-                //    };
-                //    _db.AddImage(image);
-                //}
+                Logger.WriteLine($"Get report:{report}");
 
                 //Save exam.
                 var exam = new Exam()
                 {
-                    Id = exmaId,
-                    PatientName = patientName,
+                    Id = examId,
+                    PatientName = "",
                     PatientSex = patientSex,
                     PatientAge = patientAge,
-                    ExamDate = examDate,
+                    ExamDate = DateTime.Now,
                     Report = report,
                     BatchTaskId = _batchTaskId,
                     ExamIndex = _examIndex,
@@ -459,9 +532,11 @@ namespace StationProbe
                     ExamIndexInPage = _examIndexInPage,
                     CreateTime = DateTime.Now
                 };
-                _db.AddExam(exam);
+                _examStore.AddExam(exam);
+
                 //Back to page.
-                await page.GoBackAsync();
+                await ExitExamAsync(page);
+
                 if (DebugMode)
                 {
                     var imageData = await page.ScreenshotDataAsync(_snapshotOpts);

+ 3 - 6
StationProbe/Program.cs

@@ -30,7 +30,6 @@ else
         }
         else
         {
-            ProbeTask.DebugMode = true;
             Console.WriteLine("Downloading headless browser...");
             await new BrowserFetcher().DownloadAsync();
 
@@ -42,9 +41,7 @@ else
                     ProbeTask.InitDebugFolder();
                     await using var browser = (Browser)await Puppeteer.LaunchAsync(new LaunchOptions
                     {
-                        Headless = true,
-                        //Devtools = true
-
+                        Headless = false,
                     });
                     await using var page = await browser.NewPageAsync();
                     await page.SetViewportAsync(new ViewPortOptions
@@ -52,7 +49,7 @@ else
                         Width = 1920,
                         Height = 1080
                     });
-                    var superImageTask = new SuperImageTask(batchTask, 15);
+                    var superImageTask = new SuperImageTask(batchTask, 10);
                     await superImageTask.RunAsync(page);
                     error = false;
                 }
@@ -62,7 +59,7 @@ else
                     error = true;
                     try
                     {
-                        await DingTalk.SendMessageAsync($"Run super image task error, will restart in 10s.");
+                        await DingTalk.SendMessageAsync($"Run super image task error:{ex.Message}, will restart in 10s.");
                     }
                     catch(Exception dx)
                     {

+ 56 - 24
StationProbe/SuperImageTask.cs

@@ -2,9 +2,39 @@
 
 namespace StationProbe
 {
+    internal class DefaultExamStore : IExamStore
+    {
+        private readonly Database _db = new Database();
+
+        public void AddExam(Exam exam)
+        {
+            _db.AddExam(exam);
+        }
+
+        public void AddImage(Image image)
+        {
+            _db.AddImage(image);
+        }
+
+        public void Dispose()
+        {
+            _db.Dispose();
+        }
+
+        public Exam? GetLastExam(int batchTaskId)
+        {
+            return _db.GetLastExam(batchTaskId);
+        }
+
+        public void UpdateBatchTask(BatchTask batchTask)
+        {
+            _db.UpdateBatchTask(batchTask);
+        }
+    }
+
     internal class SuperImageTask : IDisposable
     {
-        private readonly Database _db;
+        private readonly IExamStore _examStore;
         private readonly BatchTask _batchTask;
         private readonly int _examCountInPage;
         private int _pageIndex = 0;
@@ -15,7 +45,7 @@ namespace StationProbe
         {
             _batchTask = batchTask;
             _examCountInPage = examCountInPage;
-            _db = new Database();
+            _examStore = new DefaultExamStore();
         }
 
         public async Task RunAsync(IPage page)
@@ -26,7 +56,7 @@ namespace StationProbe
             Logger.WriteLine($"Start login task (url:{url})...");
             try
             {
-                outputs = await new LoginTask(_db,url).ExecuteAsync(outputs);
+                outputs = await new LoginTask(_examStore, url).ExecuteAsync(outputs);
             }
             catch (Exception ex)
             {
@@ -37,7 +67,7 @@ namespace StationProbe
             Logger.WriteLine("Start menu task...");
             try
             {
-                outputs = await new MenuTask(_db).ExecuteAsync(outputs);
+                outputs = await new MenuTask(_examStore).ExecuteAsync(outputs);
             }
             catch (Exception ex)
             {
@@ -48,14 +78,14 @@ namespace StationProbe
             Logger.WriteLine($"Start filter task(start:{_batchTask.Start:yyyy-MM-dd}, end:{_batchTask.End:yyyy-MM-dd})...");
             try
             {
-                var filterTask = new FilterTask(_db,_batchTask.Start, _batchTask.End);
+                var filterTask = new FilterTask(_examStore, _batchTask.Start, _batchTask.End);
                 outputs = await filterTask.ExecuteAsync(outputs);
                 if (filterTask.PageCount != _batchTask.PageCount || filterTask.ExamCount != _batchTask.ExamCount)
                 {
                     //Update the batchTask
                     _batchTask.ExamCount = filterTask.ExamCount;
                     _batchTask.PageCount = filterTask.PageCount;
-                    _db.UpdateBatchTask(_batchTask);
+                    _examStore.UpdateBatchTask(_batchTask);
                 }
                 Logger.WriteLine($"Get page count:{filterTask.PageCount}, exam count:{filterTask.ExamCount}");
             }
@@ -65,7 +95,7 @@ namespace StationProbe
                 throw;
             }
             await Task.Delay(1000);
-            var lastExam = _db.GetLastExam(_batchTask.Id);
+            var lastExam = _examStore.GetLastExam(_batchTask.Id);
             if (lastExam != null)
             {
                 _pageIndex = lastExam.PageIndex;
@@ -84,7 +114,7 @@ namespace StationProbe
                 Logger.WriteLine($"Start page task (page:{_pageIndex})");
                 try
                 {
-                    var pageTask = new PageTask(_db, _pageIndex);
+                    var pageTask = new PageTask(_examStore, _pageIndex);
                     outputs = await pageTask.ExecuteAsync(outputs);
                     var currentExamCountinPage = _pageIndex < (_batchTask.PageCount - 1) ? _examCountInPage : (_batchTask.ExamCount - _pageIndex * _examCountInPage);
                     while (_examIndexInPage < currentExamCountinPage)
@@ -92,27 +122,29 @@ namespace StationProbe
                         Logger.WriteLine($"Start exam task (exam index: {_examIndexInPage})...");
                         try
                         {
-                            outputs = await new ExamTask(_db,_batchTask.Id, _pageIndex, _examIndex ,_examIndexInPage).ExecuteAsync(outputs);
-                            processedExams++;
-                            if(processedExams % 150 == 0)
-                            {
-                                //Notify per 10 pages.
-                                try
-                                {
-                                    await DingTalk.SendMessageAsync($"{processedExams} exams processed.");
-                                }
-                                catch (Exception dx)
-                                {
-                                    Logger.WriteLine($"Send dingtalk message error:{dx.Message}");
-                                }
-                            }
-                            await Task.Delay(1000);
+                            outputs = await new ExamTask(_examStore, _batchTask.Id, _pageIndex, _examIndex ,_examIndexInPage).ExecuteAsync(outputs);
                         }
                         catch (Exception ex)
                         {
                             Logger.WriteLine($"Run ExamTask failed:{ex}");
                             throw;
                         }
+                        _examIndex++;
+                        _examIndexInPage++;
+                        processedExams++;
+                        if (processedExams % 150 == 0)
+                        {
+                            //Notify per 10 pages.
+                            try
+                            {
+                                await DingTalk.SendMessageAsync($"{processedExams} exams processed.");
+                            }
+                            catch (Exception dx)
+                            {
+                                Logger.WriteLine($"Send dingtalk message error:{dx.Message}");
+                            }
+                        }
+                        await Task.Delay(1000);
                     }
                     _examIndexInPage = 0;
                 }
@@ -127,7 +159,7 @@ namespace StationProbe
 
         public void Dispose()
         {
-            _db.Dispose();
+            _examStore.Dispose();
         }
     }
 }

+ 9 - 0
TestViewer/App.xaml

@@ -0,0 +1,9 @@
+<Application x:Class="TestViewer.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:TestViewer"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+         
+    </Application.Resources>
+</Application>

+ 17 - 0
TestViewer/App.xaml.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace TestViewer
+{
+    /// <summary>
+    /// Interaction logic for App.xaml
+    /// </summary>
+    public partial class App : Application
+    {
+    }
+}

+ 10 - 0
TestViewer/AssemblyInfo.cs

@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+                                     //(used if a resource is not found in the page,
+                                     // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+                                              //(used if a resource is not found in the page,
+                                              // app, or any theme specific resource dictionaries)
+)]

+ 32 - 0
TestViewer/MainWindow.xaml

@@ -0,0 +1,32 @@
+<Window x:Class="TestViewer.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="Exam Database TestViewer" Height="450" Width="800" WindowStartupLocation="CenterScreen" WindowState="Maximized">
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="240"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+        <Grid Grid.Column="0">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="32"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="32"/>
+            </Grid.RowDefinitions>
+            <ComboBox Grid.Row="0" x:Name="BatchTaskList"/>
+            <ListBox Grid.Row="1" x:Name="ExamList"/>
+            <UniformGrid Grid.Row="2" Columns="3">
+                <Button Margin="2" Content="&lt;" Click="OnPreviousPageClick"/>
+                <TextBlock x:Name="PageIndex" Text="1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
+                <Button Margin="2" Content=">" Click="OnNextPageClick"/>
+            </UniformGrid>
+        </Grid>
+        <ScrollViewer Grid.Column="1" >
+            <StackPanel x:Name="ExamContent"/>
+        </ScrollViewer>
+        
+    </Grid>
+</Window>

+ 133 - 0
TestViewer/MainWindow.xaml.cs

@@ -0,0 +1,133 @@
+using Microsoft.Win32;
+using StationProbe;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+
+namespace TestViewer
+{
+    /// <summary>
+    /// Interaction logic for MainWindow.xaml
+    /// </summary>
+    public partial class MainWindow : Window
+    {
+        private readonly Database _db;
+        private int _pageIndex = -1;
+        private int _pageCount = 0;
+
+        public MainWindow()
+        {
+            InitializeComponent();
+            var fileDialog = new OpenFileDialog();
+            fileDialog.Title = "Open database file";
+            fileDialog.Filter = "*.db|*.db";
+            if(fileDialog.ShowDialog() == true)
+            {
+                _db = new Database(fileDialog.FileName);
+                PageIndex.Text = $"{_pageIndex + 1}/{_pageCount}";
+                var batchTasks = _db.GetBatchTasks();
+                foreach (var task in batchTasks)
+                {
+                    BatchTaskList.Items.Add(task);
+                }
+                BatchTaskList.SelectionChanged += OnBatchTaskSelectionChanged;
+                ExamList.SelectionChanged += OnExamSelectionChanged;
+            }
+            else
+            {
+                Application.Current.Shutdown(0);
+            }           
+        }
+
+        private void OnBatchTaskSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            var batchTask = BatchTaskList.SelectedValue as BatchTask;
+            if (batchTask != null)
+            {
+                var lastExam = _db.GetLastExam(batchTask.Id);
+                if (lastExam != null)
+                {
+                    _pageIndex = 0;
+                    _pageCount = lastExam.PageIndex + 1;
+                    var exams = _db.GetExams(batchTask.Id, _pageIndex);
+                    foreach (var exam in exams)
+                    {
+                        ExamList.Items.Add(exam);
+                    }
+                    PageIndex.Text = $"{_pageIndex + 1}/{_pageCount}";
+                }
+            }
+        }
+
+        private void OnExamSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            ExamContent.Children.Clear();
+            if (ExamList.SelectedValue != null)
+            {
+                var exam = ExamList.SelectedValue as Exam;
+                if (exam != null)
+                {
+                    ExamContent.Children.Add(new TextBlock() 
+                    {
+                        Margin = new Thickness(16),
+                        Text = exam.Report, 
+                        FontSize = 20, 
+                        TextWrapping = TextWrapping.Wrap 
+                    });
+                    var images = _db.GetImages(exam.Id);
+                    foreach (var image in images)
+                    {
+                        var img = new BitmapImage();
+                        img.BeginInit();
+                        img.StreamSource = new MemoryStream(image.Data);
+                        img.EndInit();
+                        ExamContent.Children.Add(new System.Windows.Controls.Image() 
+                        { 
+                            Margin = new Thickness(4),
+                            Source = img 
+                        });
+                    }
+                }
+            }
+        }
+
+        private void OnNextPageClick(object sender, RoutedEventArgs e)
+        {
+            if (_pageIndex < _pageCount - 1)
+            {
+                var batchTask = BatchTaskList.SelectedValue as BatchTask;
+                if (batchTask != null)
+                {
+                    ExamList.Items.Clear();
+                    _pageIndex++;
+                    var exams = _db.GetExams(batchTask.Id, _pageIndex);
+                    foreach (var exam in exams)
+                    {
+                        ExamList.Items.Add(exam);
+                    }
+                    PageIndex.Text = $"{_pageIndex + 1}/{_pageCount}";
+                }
+            }
+        }
+
+        private void OnPreviousPageClick(object sender, RoutedEventArgs e)
+        {
+            if (_pageIndex > 0)
+            {
+                var batchTask = BatchTaskList.SelectedValue as BatchTask;
+                if (batchTask != null)
+                {
+                    ExamList.Items.Clear();
+                    _pageIndex--;
+                    var exams = _db.GetExams(batchTask.Id, _pageIndex);
+                    foreach (var exam in exams)
+                    {
+                        ExamList.Items.Add(exam);
+                    }
+                    PageIndex.Text = $"{_pageIndex + 1}/{_pageCount}";
+                }
+            }
+        }
+    }
+}

+ 19 - 0
TestViewer/TestViewer.csproj

@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net6.0-windows</TargetFramework>
+    <Nullable>enable</Nullable>
+    <UseWPF>true</UseWPF>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\StationProbe\Database.cs" Link="Database.cs" />
+    <Compile Include="..\StationProbe\Entities.cs" Link="Entities.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
+  </ItemGroup>
+
+</Project>