From ab7a315b04dc5d3375ef71aeae8f3e81f67086ab Mon Sep 17 00:00:00 2001 From: chiDT Date: Sat, 28 Jun 2025 22:14:18 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EC=BB=A4=EB=B0=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BokBonCheck.sln | 25 ++ BokBonCheck/App.config | 6 + BokBonCheck/BokBonCheck.csproj | 95 +++++ BokBonCheck/BokBonCheck.sln | 24 ++ BokBonCheck/BookSearchService.cs | 71 ++++ BokBonCheck/ChromeDriverManager.cs | 378 +++++++++++++++++ BokBonCheck/DownloadProgressForm.cs | 142 +++++++ BokBonCheck/Form1.Designer.cs | 47 +++ BokBonCheck/Form1.cs | 411 +++++++++++++++++++ BokBonCheck/Form1.resx | 120 ++++++ BokBonCheck/NamguLibrarySearcher.cs | 203 +++++++++ BokBonCheck/Program.cs | 22 + BokBonCheck/Properties/AssemblyInfo.cs | 33 ++ BokBonCheck/Properties/Resources.Designer.cs | 71 ++++ BokBonCheck/Properties/Resources.resx | 117 ++++++ BokBonCheck/Properties/Settings.Designer.cs | 30 ++ BokBonCheck/Properties/Settings.settings | 7 + BokBonCheck/README.md | 134 ++++++ 18 files changed, 1936 insertions(+) create mode 100644 BokBonCheck.sln create mode 100644 BokBonCheck/App.config create mode 100644 BokBonCheck/BokBonCheck.csproj create mode 100644 BokBonCheck/BokBonCheck.sln create mode 100644 BokBonCheck/BookSearchService.cs create mode 100644 BokBonCheck/ChromeDriverManager.cs create mode 100644 BokBonCheck/DownloadProgressForm.cs create mode 100644 BokBonCheck/Form1.Designer.cs create mode 100644 BokBonCheck/Form1.cs create mode 100644 BokBonCheck/Form1.resx create mode 100644 BokBonCheck/NamguLibrarySearcher.cs create mode 100644 BokBonCheck/Program.cs create mode 100644 BokBonCheck/Properties/AssemblyInfo.cs create mode 100644 BokBonCheck/Properties/Resources.Designer.cs create mode 100644 BokBonCheck/Properties/Resources.resx create mode 100644 BokBonCheck/Properties/Settings.Designer.cs create mode 100644 BokBonCheck/Properties/Settings.settings create mode 100644 BokBonCheck/README.md diff --git a/BokBonCheck.sln b/BokBonCheck.sln new file mode 100644 index 0000000..20313f8 --- /dev/null +++ b/BokBonCheck.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35919.96 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BokBonCheck", "BokBonCheck\BokBonCheck.csproj", "{BFFF0C63-1231-442F-B800-C21760132E4D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BFFF0C63-1231-442F-B800-C21760132E4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFFF0C63-1231-442F-B800-C21760132E4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFFF0C63-1231-442F-B800-C21760132E4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFFF0C63-1231-442F-B800-C21760132E4D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {319FC245-1B17-4BD0-B760-FD95BA4EA2C3} + EndGlobalSection +EndGlobal diff --git a/BokBonCheck/App.config b/BokBonCheck/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/BokBonCheck/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/BokBonCheck/BokBonCheck.csproj b/BokBonCheck/BokBonCheck.csproj new file mode 100644 index 0000000..8ae727b --- /dev/null +++ b/BokBonCheck/BokBonCheck.csproj @@ -0,0 +1,95 @@ + + + + + Debug + AnyCPU + {BFFF0C63-1231-442F-B800-C21760132E4D} + WinExe + BokBonCheck + BokBonCheck + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + + Form + + + Form1.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/BokBonCheck/BokBonCheck.sln b/BokBonCheck/BokBonCheck.sln new file mode 100644 index 0000000..e5a7a34 --- /dev/null +++ b/BokBonCheck/BokBonCheck.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BokBonCheck", "BokBonCheck.csproj", "{BEFC3363-0F54-A638-019B-6A76908217A4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BEFC3363-0F54-A638-019B-6A76908217A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEFC3363-0F54-A638-019B-6A76908217A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEFC3363-0F54-A638-019B-6A76908217A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEFC3363-0F54-A638-019B-6A76908217A4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7A76F894-89FF-4B58-8C94-36A77F1E2B44} + EndGlobalSection +EndGlobal diff --git a/BokBonCheck/BookSearchService.cs b/BokBonCheck/BookSearchService.cs new file mode 100644 index 0000000..ce6d99f --- /dev/null +++ b/BokBonCheck/BookSearchService.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; + +namespace BokBonCheck +{ + public class BookSearchResult + { + public string SiteName { get; set; } + public int BookCount { get; set; } + public string SearchTerm { get; set; } + public DateTime SearchTime { get; set; } + public string ErrorMessage { get; set; } + public bool IsSuccess { get; set; } + } + + public class BookSearchService + { + private readonly List _searchers; + + public BookSearchService() + { + _searchers = new List + { + new NamguLibrarySearcher() + // 나중에 다른 도서관 검색기를 여기에 추가할 수 있습니다 + }; + } + + public async Task> SearchBooksAsync(string searchTerm) + { + var results = new List(); + + foreach (var searcher in _searchers) + { + var result = await searcher.SearchAsync(searchTerm); + results.Add(result); + } + + return results; + } + + public void AddSearcher(ILibrarySearcher searcher) + { + if (!_searchers.Any(s => s.SiteName == searcher.SiteName)) + { + _searchers.Add(searcher); + } + } + + public void RemoveSearcher(string siteName) + { + var searcher = _searchers.FirstOrDefault(s => s.SiteName == siteName); + if (searcher != null) + { + _searchers.Remove(searcher); + } + } + + public List GetAvailableSites() + { + return _searchers.Select(s => s.SiteName).ToList(); + } + + public void ClearSearchers() + { + _searchers.Clear(); + } + } +} \ No newline at end of file diff --git a/BokBonCheck/ChromeDriverManager.cs b/BokBonCheck/ChromeDriverManager.cs new file mode 100644 index 0000000..a864b52 --- /dev/null +++ b/BokBonCheck/ChromeDriverManager.cs @@ -0,0 +1,378 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using WebDriverManager; +using WebDriverManager.DriverConfigs.Impl; +using System.Reflection; +using System.Net.Http; +using System.Threading; +using System.IO.Compression; + +namespace BokBonCheck +{ + public static class ChromeDriverManager + { + private static string _driverPath = null; + + public static async Task SetupChromeDriverAsync(DownloadProgressForm progressForm = null) + { + try + { + // Chrome 버전 확인 + var chromeVersion = GetChromeVersion(); + Console.WriteLine($"설치된 Chrome 버전: {chromeVersion}"); + + if (progressForm != null) + { + progressForm.UpdateProgress(10, $"Chrome 버전 확인 중... ({chromeVersion})"); + } + + // 기존 드라이버 경로 확인 + var existingDriverPath = GetExistingDriverPath(); + if (!string.IsNullOrEmpty(existingDriverPath) && File.Exists(existingDriverPath)) + { + Console.WriteLine($"기존 드라이버 발견: {existingDriverPath}"); + + // 기존 드라이버 테스트 + if (await TestExistingDriver(existingDriverPath)) + { + if (progressForm != null) + { + progressForm.UpdateProgress(100, "기존 드라이버 사용"); + progressForm.SetCompleted("기존 드라이버를 사용합니다."); + } + + _driverPath = existingDriverPath; + Environment.SetEnvironmentVariable("webdriver.chrome.driver", existingDriverPath); + return existingDriverPath; + } + else + { + Console.WriteLine("기존 드라이버 테스트 실패 - 새로 다운로드"); + // 기존 드라이버가 작동하지 않으면 삭제 + try + { + File.Delete(existingDriverPath); + Console.WriteLine("기존 드라이버 삭제됨"); + } + catch (Exception ex) + { + Console.WriteLine($"기존 드라이버 삭제 실패: {ex.Message}"); + } + } + } + + if (progressForm != null) + { + progressForm.UpdateProgress(30, "Chrome 버전에 맞는 드라이버 다운로드 중..."); + } + + // WebDriverManager를 사용하여 설치된 Chrome 버전에 맞는 드라이버 다운로드 + var driverManager = new DriverManager(); + var driverPath = driverManager.SetUpDriver(new ChromeConfig(), "MatchingBrowser"); + + // 다운로드된 드라이버 경로 저장 + _driverPath = driverPath; + + // 환경 변수 설정 + Environment.SetEnvironmentVariable("webdriver.chrome.driver", driverPath); + + if (progressForm != null) + { + progressForm.UpdateProgress(100, "드라이버 다운로드 완료"); + progressForm.SetCompleted("드라이버 다운로드 완료!"); + } + + Console.WriteLine($"ChromeDriver 경로: {driverPath}"); + Console.WriteLine($"환경 변수 설정: webdriver.chrome.driver = {driverPath}"); + + return driverPath; + } + catch (Exception ex) + { + Console.WriteLine($"ChromeDriver 설정 오류: {ex.Message}"); + if (progressForm != null) + { + progressForm.SetError($"설정 오류: {ex.Message}"); + } + throw; + } + } + + private static string GetExistingDriverPath() + { + try + { + // 환경 변수에서 확인 + var envPath = Environment.GetEnvironmentVariable("webdriver.chrome.driver"); + if (!string.IsNullOrEmpty(envPath) && File.Exists(envPath)) + { + return envPath; + } + + // WebDriverManager 캐시 폴더에서 확인 + var cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".cache", "selenium"); + if (Directory.Exists(cachePath)) + { + var chromeDriverFiles = Directory.GetFiles(cachePath, "chromedriver*", SearchOption.AllDirectories); + foreach (var file in chromeDriverFiles) + { + if (file.EndsWith(".exe") || !Path.HasExtension(file)) + { + return file; + } + } + } + + return null; + } + catch + { + return null; + } + } + + public static string GetDriverPath() + { + return _driverPath; + } + + private static string GetChromeVersion() + { + try + { + // Windows에서 Chrome 설치 경로 확인 + var chromePaths = new[] + { + @"C:\Program Files\Google\Chrome\Application\chrome.exe", + @"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Google\Chrome\Application\chrome.exe") + }; + + foreach (var path in chromePaths) + { + if (File.Exists(path)) + { + var versionInfo = FileVersionInfo.GetVersionInfo(path); + var version = versionInfo.FileVersion; + Console.WriteLine($"Chrome 버전 감지: {version} (경로: {path})"); + return version; + } + } + + return "알 수 없음"; + } + catch (Exception ex) + { + Console.WriteLine($"Chrome 버전 감지 실패: {ex.Message}"); + return "확인 실패"; + } + } + + public static bool IsChromeInstalled() + { + try + { + var chromePaths = new[] + { + @"C:\Program Files\Google\Chrome\Application\chrome.exe", + @"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Google\Chrome\Application\chrome.exe") + }; + + foreach (var path in chromePaths) + { + if (File.Exists(path)) + { + return true; + } + } + + return false; + } + catch + { + return false; + } + } + + public static async Task TestChromeDriverAsync(DownloadProgressForm progressForm = null) + { + try + { + var driverPath = await SetupChromeDriverAsync(progressForm); + + if (progressForm != null) + { + progressForm.UpdateProgress(50, "드라이버 테스트 중..."); + } + + // ChromeDriver 서비스 설정 + var service = OpenQA.Selenium.Chrome.ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath)); + service.HideCommandPromptWindow = true; + // 간단한 테스트 실행 + var options = new OpenQA.Selenium.Chrome.ChromeOptions(); + options.AddArgument("--headless"); + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--disable-gpu"); + options.AddArgument("--remote-debugging-port=0"); + + using (var driver = new OpenQA.Selenium.Chrome.ChromeDriver(service, options)) + { + driver.Navigate().GoToUrl("https://www.google.com"); + var title = driver.Title; + + Console.WriteLine($"드라이버 테스트 성공: {title}"); + + if (progressForm != null) + { + progressForm.UpdateProgress(100, "드라이버 테스트 성공!"); + progressForm.SetCompleted("드라이버 테스트 성공!"); + } + + return !string.IsNullOrEmpty(title); + } + } + catch (Exception ex) + { + Console.WriteLine($"ChromeDriver 테스트 실패: {ex.Message}"); + if (progressForm != null) + { + progressForm.SetError($"테스트 실패: {ex.Message}"); + } + return false; + } + } + + public static void ClearDriverCache() + { + try + { + Console.WriteLine("Chrome 드라이버 캐시 정리 시작..."); + + // WebDriverManager 캐시 폴더 정리 + var cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".cache", "selenium"); + if (Directory.Exists(cachePath)) + { + Directory.Delete(cachePath, true); + Console.WriteLine($"WebDriverManager 캐시 정리됨: {cachePath}"); + } + + // 환경 변수에서 설정된 드라이버 경로도 정리 + var envDriverPath = Environment.GetEnvironmentVariable("webdriver.chrome.driver"); + if (!string.IsNullOrEmpty(envDriverPath) && File.Exists(envDriverPath)) + { + try + { + File.Delete(envDriverPath); + Console.WriteLine($"환경 변수 드라이버 삭제됨: {envDriverPath}"); + } + catch (Exception ex) + { + Console.WriteLine($"환경 변수 드라이버 삭제 실패: {ex.Message}"); + } + } + + // 저장된 드라이버 경로도 정리 + if (!string.IsNullOrEmpty(_driverPath) && File.Exists(_driverPath)) + { + try + { + File.Delete(_driverPath); + Console.WriteLine($"저장된 드라이버 삭제됨: {_driverPath}"); + } + catch (Exception ex) + { + Console.WriteLine($"저장된 드라이버 삭제 실패: {ex.Message}"); + } + } + + _driverPath = null; + Environment.SetEnvironmentVariable("webdriver.chrome.driver", null); + + Console.WriteLine("Chrome 드라이버 캐시 정리 완료"); + } + catch (Exception ex) + { + Console.WriteLine($"캐시 정리 실패: {ex.Message}"); + } + } + + public static bool IsDriverReady() + { + try + { + var driverPath = GetExistingDriverPath(); + if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath)) + { + Console.WriteLine("기존 드라이버가 없습니다."); + return false; + } + + Console.WriteLine($"드라이버 준비 상태 확인: {driverPath}"); + + // 간단한 테스트 실행 + var service = OpenQA.Selenium.Chrome.ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath)); + service.HideCommandPromptWindow = true; + var options = new OpenQA.Selenium.Chrome.ChromeOptions(); + options.AddArgument("--headless"); + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--disable-gpu"); + options.AddArgument("--remote-debugging-port=0"); + + using (var driver = new OpenQA.Selenium.Chrome.ChromeDriver(service, options)) + { + driver.Navigate().GoToUrl("https://www.google.com"); + var result = !string.IsNullOrEmpty(driver.Title); + Console.WriteLine($"드라이버 준비 상태: {(result ? "준비됨" : "실패")}"); + return result; + } + } + catch (Exception ex) + { + Console.WriteLine($"드라이버 준비 상태 확인 실패: {ex.Message}"); + return false; + } + } + + private static async Task TestExistingDriver(string driverPath) + { + try + { + Console.WriteLine($"기존 드라이버 테스트: {driverPath}"); + + // ChromeDriver 서비스 설정 + var service = OpenQA.Selenium.Chrome.ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath)); + service.HideCommandPromptWindow = true; + + // 간단한 테스트 실행 + var options = new OpenQA.Selenium.Chrome.ChromeOptions(); + options.AddArgument("--headless"); + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--disable-gpu"); + options.AddArgument("--remote-debugging-port=0"); + + using (var driver = new OpenQA.Selenium.Chrome.ChromeDriver(service, options)) + { + driver.Navigate().GoToUrl("https://www.google.com"); + var title = driver.Title; + Console.WriteLine($"드라이버 테스트 성공: {title}"); + return !string.IsNullOrEmpty(title); + } + } + catch (Exception ex) + { + Console.WriteLine($"기존 드라이버 테스트 실패: {ex.Message}"); + return false; + } + } + } +} \ No newline at end of file diff --git a/BokBonCheck/DownloadProgressForm.cs b/BokBonCheck/DownloadProgressForm.cs new file mode 100644 index 0000000..0d54622 --- /dev/null +++ b/BokBonCheck/DownloadProgressForm.cs @@ -0,0 +1,142 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using System.Threading.Tasks; + +namespace BokBonCheck +{ + public partial class DownloadProgressForm : Form + { + private ProgressBar progressBar; + private Label lblStatus; + private Label lblProgress; + private Button btnCancel; + private bool isCancelled = false; + + public DownloadProgressForm() + { + InitializeComponent(); + this.StartPosition = FormStartPosition.CenterScreen; + } + + private void InitializeComponent() + { + this.Text = "Chrome 드라이버 다운로드"; + this.Size = new Size(400, 150); + this.StartPosition = FormStartPosition.CenterParent; + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.ControlBox = false; + this.TopMost = true; + + // 상태 라벨 + lblStatus = new Label + { + Text = "Chrome 드라이버를 확인하고 있습니다...", + Location = new Point(20, 20), + Size = new Size(360, 20), + Font = new Font("맑은 고딕", 9), + TextAlign = ContentAlignment.MiddleCenter + }; + + // 진행률 라벨 + lblProgress = new Label + { + Text = "0%", + Location = new Point(20, 50), + Size = new Size(360, 20), + Font = new Font("맑은 고딕", 10, FontStyle.Bold), + TextAlign = ContentAlignment.MiddleCenter, + ForeColor = Color.Blue + }; + + // 프로그레스 바 + progressBar = new ProgressBar + { + Location = new Point(20, 80), + Size = new Size(360, 25), + Style = ProgressBarStyle.Continuous, + Minimum = 0, + Maximum = 100, + Value = 0 + }; + + // 취소 버튼 + btnCancel = new Button + { + Text = "취소", + Location = new Point(150, 115), + Size = new Size(100, 30), + Font = new Font("맑은 고딕", 9), + BackColor = Color.LightCoral + }; + + btnCancel.Click += BtnCancel_Click; + + // 컨트롤 추가 + this.Controls.AddRange(new Control[] + { + lblStatus, lblProgress, progressBar, btnCancel + }); + } + + private void BtnCancel_Click(object sender, EventArgs e) + { + isCancelled = true; + btnCancel.Enabled = false; + lblStatus.Text = "취소 중..."; + } + + public bool IsCancelled => isCancelled; + + public void UpdateProgress(int percentage, string status = null) + { + if (this.InvokeRequired) + { + this.Invoke(new Action(UpdateProgress), percentage, status); + return; + } + + progressBar.Value = Math.Min(percentage, 100); + lblProgress.Text = $"{percentage}%"; + + if (!string.IsNullOrEmpty(status)) + { + lblStatus.Text = status; + } + } + + public void SetCompleted(string message = "다운로드 완료!") + { + if (this.InvokeRequired) + { + this.Invoke(new Action(SetCompleted), message); + return; + } + + progressBar.Value = 100; + lblProgress.Text = "100%"; + lblStatus.Text = message; + btnCancel.Text = "확인"; + btnCancel.BackColor = Color.LightGreen; + btnCancel.Enabled = true; + } + + public void SetError(string errorMessage) + { + if (this.InvokeRequired) + { + this.Invoke(new Action(SetError), errorMessage); + return; + } + + lblStatus.Text = "오류 발생"; + lblProgress.Text = "실패"; + lblProgress.ForeColor = Color.Red; + btnCancel.Text = "확인"; + btnCancel.BackColor = Color.LightCoral; + btnCancel.Enabled = true; + } + } +} \ No newline at end of file diff --git a/BokBonCheck/Form1.Designer.cs b/BokBonCheck/Form1.Designer.cs new file mode 100644 index 0000000..8221c59 --- /dev/null +++ b/BokBonCheck/Form1.Designer.cs @@ -0,0 +1,47 @@ +namespace BokBonCheck +{ + partial class Form1 + { + /// + /// 필수 디자이너 변수입니다. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 사용 중인 모든 리소스를 정리합니다. + /// + /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form 디자이너에서 생성한 코드 + + /// + /// 디자이너 지원에 필요한 메서드입니다. + /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Name = "Form1"; + this.Text = "Form1"; + this.ResumeLayout(false); + + } + + #endregion + } +} + diff --git a/BokBonCheck/Form1.cs b/BokBonCheck/Form1.cs new file mode 100644 index 0000000..1fdd45c --- /dev/null +++ b/BokBonCheck/Form1.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BokBonCheck +{ + public partial class Form1 : Form + { + private readonly BookSearchService _searchService; + private bool _isSearching = false; + private bool _isDriverReady = false; + + public Form1() + { + InitializeComponent(); + _searchService = new BookSearchService(); + InitializeUI(); + InitializeChromeDriver(); + } + + private async void InitializeChromeDriver() + { + var lblStatus = (Label)this.Controls["lblStatus"]; + lblStatus.Text = "Chrome 드라이버 확인 중..."; + lblStatus.ForeColor = Color.Blue; + + try + { + // Chrome 설치 확인 + if (!ChromeDriverManager.IsChromeInstalled()) + { + MessageBox.Show("Google Chrome이 설치되어 있지 않습니다. Chrome을 설치한 후 프로그램을 다시 실행해주세요.", + "Chrome 필요", MessageBoxButtons.OK, MessageBoxIcon.Warning); + lblStatus.Text = "Chrome이 설치되지 않음"; + lblStatus.ForeColor = Color.Red; + return; + } + + // 기존 드라이버가 준비되어 있는지 확인 + if (ChromeDriverManager.IsDriverReady()) + { + lblStatus.Text = "Chrome 드라이버 준비 완료 - 검색할 준비가 되었습니다."; + lblStatus.ForeColor = Color.Green; + + var btnSearch = (Button)this.Controls["btnSearch"]; + btnSearch.Enabled = true; + _isDriverReady = true; + return; + } + + // 드라이버가 없거나 작동하지 않으면 다운로드 진행 창 표시 + using (var progressForm = new DownloadProgressForm()) + { + progressForm.Show(); + + try + { + // ChromeDriver 설정 + await ChromeDriverManager.SetupChromeDriverAsync(progressForm); + + // 드라이버 테스트 + var isWorking = await ChromeDriverManager.TestChromeDriverAsync(progressForm); + + if (isWorking) + { + _isDriverReady = true; + lblStatus.Text = "Chrome 드라이버 준비 완료 - 검색할 준비가 되었습니다."; + lblStatus.ForeColor = Color.Green; + + var btnSearch = (Button)this.Controls["btnSearch"]; + btnSearch.Enabled = true; + } + else + { + lblStatus.Text = "Chrome 드라이버 테스트 실패"; + lblStatus.ForeColor = Color.Red; + MessageBox.Show("Chrome 드라이버 테스트에 실패했습니다. 인터넷 연결을 확인하고 프로그램을 다시 실행해주세요.", + "드라이버 오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + catch (OperationCanceledException) + { + lblStatus.Text = "드라이버 다운로드가 취소되었습니다."; + lblStatus.ForeColor = Color.Orange; + return; + } + catch (Exception ex) + { + lblStatus.Text = "Chrome 드라이버 설정 실패"; + lblStatus.ForeColor = Color.Red; + MessageBox.Show($"Chrome 드라이버 설정 중 오류가 발생했습니다: {ex.Message}", + "설정 오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + } + } + catch (Exception ex) + { + lblStatus.Text = "Chrome 드라이버 설정 실패"; + lblStatus.ForeColor = Color.Red; + MessageBox.Show($"Chrome 드라이버 설정 중 오류가 발생했습니다: {ex.Message}", + "설정 오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void InitializeUI() + { + // 폼 설정 + this.Text = "도서 검색 프로그램"; + this.Size = new Size(820, 600); + this.StartPosition = FormStartPosition.CenterScreen; + + // 검색 입력 영역 + var lblSearch = new Label + { + Text = "도서명을 입력하세요:", + Location = new Point(20, 20), + Size = new Size(150, 25), + Font = new Font("맑은 고딕", 10) + }; + + var txtSearch = new TextBox + { + Name = "txtSearch", + Location = new Point(20, 50), + Size = new Size(300, 25), + Font = new Font("맑은 고딕", 10) + }; + + var btnSearch = new Button + { + Name = "btnSearch", + Text = "검색", + Location = new Point(340, 50), + Size = new Size(80, 25), + Font = new Font("맑은 고딕", 10), + BackColor = Color.LightBlue, + Enabled = false // 초기에는 비활성화 + }; + + var btnClear = new Button + { + Name = "btnClear", + Text = "초기화", + Location = new Point(430, 50), + Size = new Size(80, 25), + Font = new Font("맑은 고딕", 10), + BackColor = Color.LightGray + }; + + var btnResetDriver = new Button + { + Name = "btnResetDriver", + Text = "드라이버 재설정", + Location = new Point(520, 50), + Size = new Size(100, 25), + Font = new Font("맑은 고딕", 9), + BackColor = Color.LightYellow + }; + + // 진행 상황 표시 + var progressBar = new ProgressBar + { + Name = "progressBar", + Location = new Point(20, 90), + Size = new Size(600, 20), + Visible = false + }; + + var lblStatus = new Label + { + Name = "lblStatus", + Text = "Chrome 드라이버 설정 중...", + Location = new Point(20, 120), + Size = new Size(490, 25), + Font = new Font("맑은 고딕", 9), + ForeColor = Color.Blue + }; + + // 결과 표시 영역 + var lblResults = new Label + { + Text = "검색 결과:", + Location = new Point(20, 160), + Size = new Size(150, 25), + Font = new Font("맑은 고딕", 10, FontStyle.Bold) + }; + + var dataGridView = new DataGridView + { + Name = "dataGridView", + Location = new Point(20, 190), + Size = new Size(760, 350), + AllowUserToAddRows = false, + AllowUserToDeleteRows = false, + ReadOnly = true, + SelectionMode = DataGridViewSelectionMode.FullRowSelect, + AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill, + BackgroundColor = Color.White + }; + + // 컨트롤 추가 + this.Controls.AddRange(new Control[] + { + lblSearch, txtSearch, btnSearch, btnClear, btnResetDriver, + progressBar, lblStatus, lblResults, dataGridView + }); + + // 이벤트 핸들러 연결 + btnSearch.Click += BtnSearch_Click; + btnClear.Click += BtnClear_Click; + btnResetDriver.Click += BtnResetDriver_Click; + txtSearch.KeyPress += TxtSearch_KeyPress; + + // DataGridView 설정 + SetupDataGridView(); + } + + private void SetupDataGridView() + { + var dataGridView = (DataGridView)this.Controls["dataGridView"]; + + dataGridView.Columns.Clear(); + dataGridView.Columns.Add("SiteName", "도서관"); + dataGridView.Columns.Add("BookCount", "도서 수"); + dataGridView.Columns.Add("SearchTime", "검색 시간"); + dataGridView.Columns.Add("Status", "상태"); + + // 컬럼 너비 설정 + dataGridView.Columns["SiteName"].Width = 150; + dataGridView.Columns["BookCount"].Width = 100; + dataGridView.Columns["SearchTime"].Width = 150; + dataGridView.Columns["Status"].Width = 100; + } + + private async void BtnSearch_Click(object sender, EventArgs e) + { + if (_isSearching || !_isDriverReady) return; + + var txtSearch = (TextBox)this.Controls["txtSearch"]; + var btnSearch = (Button)this.Controls["btnSearch"]; + var progressBar = (ProgressBar)this.Controls["progressBar"]; + var lblStatus = (Label)this.Controls["lblStatus"]; + var dataGridView = (DataGridView)this.Controls["dataGridView"]; + + if (string.IsNullOrWhiteSpace(txtSearch.Text)) + { + MessageBox.Show("도서명을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + _isSearching = true; + btnSearch.Enabled = false; + progressBar.Visible = true; + progressBar.Style = ProgressBarStyle.Marquee; + lblStatus.Text = "검색 중..."; + lblStatus.ForeColor = Color.Blue; + + try + { + // 기존 결과 초기화 + dataGridView.Rows.Clear(); + + // 검색 실행 + var results = await _searchService.SearchBooksAsync(txtSearch.Text.Trim()); + + // 결과 표시 + foreach (var result in results) + { + var row = new object[] + { + result.SiteName, + result.IsSuccess ? result.BookCount.ToString() : "오류", + result.SearchTime.ToString("yyyy-MM-dd HH:mm:ss"), + result.IsSuccess ? "성공" : "실패" + }; + + dataGridView.Rows.Add(row); + + // 실패한 경우 오류 메시지 표시 + if (!result.IsSuccess) + { + MessageBox.Show($"{result.SiteName}: {result.ErrorMessage}", "검색 오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + lblStatus.Text = $"검색 완료 - {results.Count}개 사이트 검색됨"; + lblStatus.ForeColor = Color.Green; + } + catch (Exception ex) + { + lblStatus.Text = "검색 중 오류가 발생했습니다."; + lblStatus.ForeColor = Color.Red; + MessageBox.Show($"검색 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + _isSearching = false; + btnSearch.Enabled = _isDriverReady; + progressBar.Visible = false; + } + } + + private void BtnClear_Click(object sender, EventArgs e) + { + var txtSearch = (TextBox)this.Controls["txtSearch"]; + var dataGridView = (DataGridView)this.Controls["dataGridView"]; + var lblStatus = (Label)this.Controls["lblStatus"]; + + txtSearch.Clear(); + dataGridView.Rows.Clear(); + + if (_isDriverReady) + { + lblStatus.Text = "검색할 준비가 되었습니다."; + lblStatus.ForeColor = Color.Green; + } + else + { + lblStatus.Text = "Chrome 드라이버 설정 실패"; + lblStatus.ForeColor = Color.Red; + } + } + + private async void BtnResetDriver_Click(object sender, EventArgs e) + { + var btnResetDriver = (Button)sender; + var lblStatus = (Label)this.Controls["lblStatus"]; + + btnResetDriver.Enabled = false; + lblStatus.Text = "드라이버 재설정 중..."; + lblStatus.ForeColor = Color.Blue; + + try + { + // 강제로 캐시 정리 (재설정이므로) + ChromeDriverManager.ClearDriverCache(); + + // 다운로드 진행 창 표시 + using (var progressForm = new DownloadProgressForm()) + { + progressForm.Text = "Chrome 드라이버 재설정"; + progressForm.Show(); + + try + { + // 드라이버 재설정 + await ChromeDriverManager.SetupChromeDriverAsync(progressForm); + + // 테스트 + var isWorking = await ChromeDriverManager.TestChromeDriverAsync(progressForm); + + if (isWorking) + { + _isDriverReady = true; + lblStatus.Text = "드라이버 재설정 완료 - 검색할 준비가 되었습니다."; + lblStatus.ForeColor = Color.Green; + + var btnSearch = (Button)this.Controls["btnSearch"]; + btnSearch.Enabled = true; + } + else + { + lblStatus.Text = "드라이버 재설정 실패"; + lblStatus.ForeColor = Color.Red; + MessageBox.Show("드라이버 재설정에 실패했습니다.", "재설정 실패", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + catch (OperationCanceledException) + { + lblStatus.Text = "드라이버 재설정이 취소되었습니다."; + lblStatus.ForeColor = Color.Orange; + return; + } + catch (Exception ex) + { + lblStatus.Text = "드라이버 재설정 중 오류 발생"; + lblStatus.ForeColor = Color.Red; + MessageBox.Show($"드라이버 재설정 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + } + } + catch (Exception ex) + { + lblStatus.Text = "드라이버 재설정 중 오류 발생"; + lblStatus.ForeColor = Color.Red; + MessageBox.Show($"드라이버 재설정 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + btnResetDriver.Enabled = true; + } + } + + private void TxtSearch_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == (char)Keys.Enter) + { + e.Handled = true; + BtnSearch_Click(sender, e); + } + } + } +} diff --git a/BokBonCheck/Form1.resx b/BokBonCheck/Form1.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/BokBonCheck/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BokBonCheck/NamguLibrarySearcher.cs b/BokBonCheck/NamguLibrarySearcher.cs new file mode 100644 index 0000000..33e9cef --- /dev/null +++ b/BokBonCheck/NamguLibrarySearcher.cs @@ -0,0 +1,203 @@ +using System; +using System.Threading.Tasks; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Support.UI; +using System.Text.RegularExpressions; +using WebDriverManager; +using WebDriverManager.DriverConfigs.Impl; +using System.IO; + +namespace BokBonCheck +{ + public class NamguLibrarySearcher : ILibrarySearcher + { + public string SiteName => "남구통합도서관"; + public string SiteUrl => "https://lib.namgu.gwangju.kr/main/bookSearch"; + + public async Task SearchAsync(string searchTerm) + { + var result = new BookSearchResult + { + SiteName = SiteName, + SearchTerm = searchTerm, + SearchTime = DateTime.Now + }; + + IWebDriver driver = null; + ChromeDriverService service = null; + + try + { + // ChromeDriverManager에서 설정한 드라이버 경로 사용 + var driverPath = ChromeDriverManager.GetDriverPath(); + if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath)) + { + // 드라이버가 없으면 다시 설정 + driverPath = await ChromeDriverManager.SetupChromeDriverAsync(); + } + + // ChromeDriver 서비스 설정 + service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath)); + service.HideCommandPromptWindow = true; + + var options = new ChromeOptions(); + options.AddArgument("--headless"); + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--disable-gpu"); + options.AddArgument("--window-size=1920,1080"); + options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); + options.AddArgument("--disable-blink-features=AutomationControlled"); + options.AddExcludedArgument("enable-automation"); + options.AddAdditionalOption("useAutomationExtension", false); + + // 명시적으로 서비스와 옵션을 사용하여 드라이버 생성 + driver = new ChromeDriver(service, options); + + // 자동화 감지 방지 + ((IJavaScriptExecutor)driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"); + + driver.Navigate().GoToUrl(SiteUrl); + + // 페이지 로딩 대기 + var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15)); + + // 검색창 찾기 (남구통합도서관 사이트의 특정 선택자 사용) + IWebElement searchBox = null; + try + { + // 여러 가능한 선택자 시도 + var selectors = new[] + { + "input[name='query']", + "input[id='query']", + "input[type='text']", + }; + + foreach (var selector in selectors) + { + try + { + searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); + break; + } + catch + { + continue; + } + } + + if (searchBox == null) + { + throw new Exception("검색창을 찾을 수 없습니다."); + } + } + catch (Exception ex) + { + throw new Exception($"검색창 찾기 실패: {ex.Message}"); + } + + // 검색어 입력 + searchBox.Clear(); + searchBox.SendKeys(searchTerm); + + // 검색 버튼 클릭 + IWebElement searchButton = null; + try + { + var buttonSelectors = new[] + { + "button[type='submit']", + "input[type='submit']", + ".search-btn", + ".btn-search", + "button:contains('검색')", + "input[value*='검색']", + "button[class*='search']", + "input[class*='search']" + }; + + foreach (var selector in buttonSelectors) + { + try + { + searchButton = driver.FindElement(By.CssSelector(selector)); + break; + } + catch + { + continue; + } + } + + if (searchButton == null) + { + // Enter 키로 검색 시도 + searchBox.SendKeys(Keys.Enter); + } + else + { + searchButton.Click(); + } + } + catch (Exception ex) + { + // Enter 키로 검색 시도 + searchBox.SendKeys(Keys.Enter); + } + + // 검색 결과 로딩 대기 + await Task.Delay(3000); + + // 검색 결과 수 추출 + var resultCount = ExtractBookCount(driver); + + result.BookCount = resultCount; + result.IsSuccess = true; + } + catch (Exception ex) + { + result.IsSuccess = false; + result.ErrorMessage = ex.Message; + result.BookCount = 0; + } + finally + { + driver?.Quit(); + driver?.Dispose(); + service?.Dispose(); + } + + return result; + } + + private int ExtractBookCount(IWebDriver driver) + { + try + { + // div.search-result 내부의 span에서 '전체 N' 텍스트 추출 + var resultDiv = driver.FindElement(By.CssSelector("div.search-result")); + var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]")); + string text = span.Text; // 예: "전체 5 " + var match = System.Text.RegularExpressions.Regex.Match(text, @"전체\s*(\d+)"); + if (match.Success) + { + return int.Parse(match.Groups[1].Value); + } + return 0; + } + catch + { + return 0; + } + } + } + + public interface ILibrarySearcher + { + string SiteName { get; } + string SiteUrl { get; } + Task SearchAsync(string searchTerm); + } +} \ No newline at end of file diff --git a/BokBonCheck/Program.cs b/BokBonCheck/Program.cs new file mode 100644 index 0000000..f69ad18 --- /dev/null +++ b/BokBonCheck/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BokBonCheck +{ + internal static class Program + { + /// + /// 해당 애플리케이션의 주 진입점입니다. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/BokBonCheck/Properties/AssemblyInfo.cs b/BokBonCheck/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7432889 --- /dev/null +++ b/BokBonCheck/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 +// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 +// 이러한 특성 값을 변경하세요. +[assembly: AssemblyTitle("BokBonCheck")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BokBonCheck")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 +// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 +// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. +[assembly: ComVisible(false)] + +// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. +[assembly: Guid("bfff0c63-1231-442f-b800-c21760132e4d")] + +// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. +// +// 주 버전 +// 부 버전 +// 빌드 번호 +// 수정 버전 +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BokBonCheck/Properties/Resources.Designer.cs b/BokBonCheck/Properties/Resources.Designer.cs new file mode 100644 index 0000000..e7f2df7 --- /dev/null +++ b/BokBonCheck/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 이 코드는 도구를 사용하여 생성되었습니다. +// 런타임 버전:4.0.30319.42000 +// +// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 +// 이러한 변경 내용이 손실됩니다. +// +//------------------------------------------------------------------------------ + +namespace BokBonCheck.Properties +{ + + + /// + /// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다. + /// + // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder + // 클래스에서 자동으로 생성되었습니다. + // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 + // ResGen을 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BokBonCheck.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을 + /// 재정의합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/BokBonCheck/Properties/Resources.resx b/BokBonCheck/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/BokBonCheck/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BokBonCheck/Properties/Settings.Designer.cs b/BokBonCheck/Properties/Settings.Designer.cs new file mode 100644 index 0000000..bb8d50f --- /dev/null +++ b/BokBonCheck/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BokBonCheck.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/BokBonCheck/Properties/Settings.settings b/BokBonCheck/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/BokBonCheck/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/BokBonCheck/README.md b/BokBonCheck/README.md new file mode 100644 index 0000000..e29d30c --- /dev/null +++ b/BokBonCheck/README.md @@ -0,0 +1,134 @@ +# 도서 검색 프로그램 (BokBonCheck) + +## 개요 +이 프로그램은 여러 도서관 웹사이트에서 도서명을 검색하여 각 사이트별로 검색된 도서 수를 확인할 수 있는 C# WinForms 애플리케이션입니다. + +## 주요 기능 +- **다중 사이트 검색**: 여러 도서관 사이트에서 동시에 도서 검색 +- **실시간 결과 표시**: 검색 결과를 테이블 형태로 실시간 표시 +- **확장 가능한 구조**: 새로운 도서관 사이트를 쉽게 추가 가능 +- **비동기 처리**: 검색 중에도 UI가 응답 가능 +- **자동 Chrome 드라이버 관리**: Chrome 버전에 맞는 드라이버 자동 다운로드 및 관리 + +## 현재 지원하는 도서관 +- 남구통합도서관 (https://lib.namgu.gwangju.kr/main/bookSearch) + +## 기술 스택 +- **.NET Framework 4.8** +- **C# WinForms** +- **Selenium WebDriver** +- **WebDriverManager** (Chrome 드라이버 자동 관리) + +## 설치 및 실행 + +### 필수 요구사항 +1. .NET Framework 4.8 이상 +2. Google Chrome 브라우저 (최신 버전 권장) +3. 인터넷 연결 (Chrome 드라이버 다운로드용) +4. Visual Studio 2019 이상 (개발용) + +### 빌드 및 실행 +1. 프로젝트를 Visual Studio에서 열기 +2. NuGet 패키지 복원 (자동으로 실행됨) +3. 프로젝트 빌드 (Ctrl+Shift+B) +4. 실행 (F5) + +### 배포 +1. Release 모드로 빌드 +2. `bin/Release` 폴더의 모든 파일을 배포 대상 폴더에 복사 +3. BokBonCheck.exe 실행 + +## 사용법 + +### 프로그램 시작 +1. 프로그램 실행 시 Chrome 드라이버 자동 설정 +2. Chrome 설치 확인 및 적절한 드라이버 다운로드 +3. 드라이버 테스트 완료 후 검색 가능 + +### 기본 검색 +1. 검색창에 도서명 입력 +2. "검색" 버튼 클릭 또는 Enter 키 입력 +3. 검색 결과 확인 + +### 결과 해석 +- **도서관**: 검색한 도서관 이름 +- **도서 수**: 해당 도서관에서 검색된 도서의 총 개수 +- **검색 시간**: 검색이 완료된 시간 +- **상태**: 검색 성공/실패 여부 + +## 프로젝트 구조 + +``` +BokBonCheck/ +├── Form1.cs # 메인 UI 폼 +├── BookSearchService.cs # 검색 서비스 관리 +├── NamguLibrarySearcher.cs # 남구통합도서관 전용 검색기 +├── ChromeDriverManager.cs # Chrome 드라이버 자동 관리 +├── ILibrarySearcher.cs # 검색기 인터페이스 +└── Program.cs # 프로그램 진입점 +``` + +## Chrome 드라이버 자동 관리 + +### WebDriverManager 기능 +- **자동 버전 감지**: 설치된 Chrome 버전 자동 감지 +- **드라이버 자동 다운로드**: Chrome 버전에 맞는 드라이버 자동 다운로드 +- **캐시 관리**: 다운로드된 드라이버 캐시 관리 +- **업데이트 지원**: Chrome 업데이트 시 드라이버 자동 업데이트 + +### 동작 과정 +1. 프로그램 시작 시 Chrome 설치 확인 +2. Chrome 버전 정보 수집 +3. WebDriverManager를 통한 적절한 드라이버 다운로드 +4. 드라이버 테스트 실행 +5. 검색 기능 활성화 + +## 새로운 도서관 추가하기 + +새로운 도서관을 추가하려면 `ILibrarySearcher` 인터페이스를 구현하는 클래스를 생성하고 `BookSearchService`에 등록하면 됩니다. + +### 예시: +```csharp +public class NewLibrarySearcher : ILibrarySearcher +{ + public string SiteName => "새로운도서관"; + public string SiteUrl => "https://newlibrary.com/search"; + + public async Task SearchAsync(string searchTerm) + { + // 검색 로직 구현 + } +} +``` + +그리고 `BookSearchService` 생성자에서: +```csharp +_searchers.Add(new NewLibrarySearcher()); +``` + +## 문제 해결 + +### Chrome 관련 오류 +- **Chrome 미설치**: Google Chrome을 설치하고 프로그램 재실행 +- **드라이버 다운로드 실패**: 인터넷 연결 확인 후 프로그램 재실행 +- **드라이버 테스트 실패**: 방화벽이나 보안 프로그램 설정 확인 + +### 검색 실패 +- 인터넷 연결 상태 확인 +- 해당 도서관 사이트가 정상 작동하는지 확인 +- 사이트 구조가 변경되었을 수 있으므로 검색 로직 업데이트 필요 + +### 성능 문제 +- 검색 중에는 다른 검색을 시작하지 마세요 +- 네트워크 상태에 따라 검색 시간이 달라질 수 있습니다 +- 첫 실행 시 드라이버 다운로드로 인한 지연이 있을 수 있습니다 + +## 업데이트 내역 +- v1.1.0: Chrome 드라이버 자동 관리 기능 추가 (WebDriverManager 사용) +- v1.0.0: 초기 버전 - 남구통합도서관 검색 기능 구현 + +## 라이선스 +이 프로젝트는 개인 및 교육 목적으로 사용할 수 있습니다. + +## 기여 +버그 리포트나 기능 제안은 이슈로 등록해 주세요. \ No newline at end of file