refactor(3): VideoDownloader.Browser library - tabs/find/search-engine/zoom/bookmark-history stores extracted from App (66 GREEN)

This commit is contained in:
Yun Chan 2026-08-15 15:02:23 +09:00
parent 6df9f81b4a
commit e8ec1350b3
20 changed files with 63 additions and 31 deletions

View file

@ -1,14 +1,7 @@
namespace VideoDownloader.App; namespace VideoDownloader.App;
/// <summary> /// <summary>앱 데이터 디렉터리(CorePaths 위임) — VD_DATA_DIR 오버라이드 지원.</summary>
/// 앱 데이터 디렉터리 결정. 기본 %AppData%/VideoDownloader.
/// 환경변수 VD_DATA_DIR 로 오버라이드 — E2E 테스트가 실사용자 프로필/설정/세션과
/// 격리된 임시 디렉터리에서 실행되도록 한다(결정적 테스트를 위한 공식 후크).
/// </summary>
public static class AppPaths public static class AppPaths
{ {
public static string DataDir => public static string DataDir => VideoDownloader.Core.Platform.CorePaths.DataDir;
Environment.GetEnvironmentVariable("VD_DATA_DIR") is { Length: > 0 } env
? env
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VideoDownloader");
} }

View file

@ -9,14 +9,14 @@ using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Core;
using VideoDownloader.App.Browser; using VideoDownloader.Browser;
using VideoDownloader.App.Browser.Tabs; using VideoDownloader.Browser.Tabs;
using VideoDownloader.App.Core; using VideoDownloader.App.Core;
using VideoDownloader.Core.Detection; using VideoDownloader.Core.Detection;
using VideoDownloader.App.ViewModels; using VideoDownloader.App.ViewModels;
using VideoDownloader.Core.Config; using VideoDownloader.Core.Config;
using Wv2 = Microsoft.Web.WebView2.Wpf; using Wv2 = Microsoft.Web.WebView2.Wpf;
using TabItem = VideoDownloader.App.Browser.Tabs.TabItem; using TabItem = VideoDownloader.Browser.Tabs.TabItem;
namespace VideoDownloader.App; namespace VideoDownloader.App;

View file

@ -4,7 +4,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using Microsoft.Win32; using Microsoft.Win32;
using VideoDownloader.App.Browser; using VideoDownloader.Browser;
using VideoDownloader.Core.Config; using VideoDownloader.Core.Config;
using VideoDownloader.Core.Social; using VideoDownloader.Core.Social;

View file

@ -21,6 +21,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\VideoDownloader.Core\VideoDownloader.Core.csproj" /> <ProjectReference Include="..\VideoDownloader.Core\VideoDownloader.Core.csproj" />
<ProjectReference Include="..\VideoDownloader.Browser\VideoDownloader.Browser.csproj" />
</ItemGroup> </ItemGroup>
<!-- .NET 10 SDK 의 net8.0-windows WPF implicit usings 보강 --> <!-- .NET 10 SDK 의 net8.0-windows WPF implicit usings 보강 -->

View file

@ -1,4 +1,4 @@
namespace VideoDownloader.App.Browser; namespace VideoDownloader.Browser;
public record Bookmark(string Title, string Url); public record Bookmark(string Title, string Url);

View file

@ -1,13 +1,13 @@
using System.Text.Json; using System.Text.Json;
namespace VideoDownloader.App.Browser; namespace VideoDownloader.Browser;
/// <summary>즐겨찾기/히스토리 JSON 영속화 (%AppData%/VideoDownloader). /// <summary>즐겨찾기/히스토리 JSON 영속화 (%AppData%/VideoDownloader).
/// #5 저장/로드 실패를 조용 삼킴하지 않고 browserstore.log 에 기록하며, /// #5 저장/로드 실패를 조용 삼킴하지 않고 browserstore.log 에 기록하며,
/// 손상 파일은 백업 후 빈 목록을 반환(다음 저장이 원본을 덮어쓰는 데이터 손실 방지).</summary> /// 손상 파일은 백업 후 빈 목록을 반환(다음 저장이 원본을 덮어쓰는 데이터 손실 방지).</summary>
public static class BrowserStore public static class BrowserStore
{ {
private static string Dir => AppPaths.DataDir; private static string Dir => VideoDownloader.Core.Platform.CorePaths.DataDir;
private static string BookmarksPath => Path.Combine(Dir, "bookmarks.json"); private static string BookmarksPath => Path.Combine(Dir, "bookmarks.json");
private static string HistoryPath => Path.Combine(Dir, "history.json"); private static string HistoryPath => Path.Combine(Dir, "history.json");
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true }; private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };

View file

@ -1,6 +1,6 @@
using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Core;
namespace VideoDownloader.App.Browser; namespace VideoDownloader.Browser;
/// <summary>네이티브 Find API 의 최소 추상화(단위 테스트용 fake 교체 가능).</summary> /// <summary>네이티브 Find API 의 최소 추상화(단위 테스트용 fake 교체 가능).</summary>
public interface IFindApi public interface IFindApi

View file

@ -1,4 +1,4 @@
namespace VideoDownloader.App.Browser; namespace VideoDownloader.Browser;
/// <summary>검색엔진 정의. QueryTemplate 의 {0} 이 URL 인코딩된 검색어로 치환된다.</summary> /// <summary>검색엔진 정의. QueryTemplate 의 {0} 이 URL 인코딩된 검색어로 치환된다.</summary>
public sealed record SearchEngine(string Name, string Keyword, string QueryTemplate); public sealed record SearchEngine(string Name, string Keyword, string QueryTemplate);

View file

@ -1,6 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace VideoDownloader.App.Browser; namespace VideoDownloader.Browser;
/// <summary> /// <summary>
/// 주소창 입력을 URL 또는 검색 의도로 판별한다(T1-4). /// 주소창 입력을 URL 또는 검색 의도로 판별한다(T1-4).

View file

@ -1,7 +1,7 @@
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
namespace VideoDownloader.App.Browser; namespace VideoDownloader.Browser;
/// <summary> /// <summary>
/// 사이트별 줌 배율 영속 저장소(T1-7). WebView2 는 사이트별 줌 영속을 보장하지 않아 /// 사이트별 줌 배율 영속 저장소(T1-7). WebView2 는 사이트별 줌 영속을 보장하지 않아

View file

@ -2,7 +2,7 @@ using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Microsoft.Web.WebView2.Wpf; using Microsoft.Web.WebView2.Wpf;
namespace VideoDownloader.App.Browser.Tabs; namespace VideoDownloader.Browser.Tabs;
/// <summary>탭 하나의 상태. View(WebView2 컨트롤)는 앱 레이어에서 주입한다(단위 테스트에선 null).</summary> /// <summary>탭 하나의 상태. View(WebView2 컨트롤)는 앱 레이어에서 주입한다(단위 테스트에선 null).</summary>
public sealed class TabItem : INotifyPropertyChanged public sealed class TabItem : INotifyPropertyChanged
@ -44,10 +44,13 @@ public sealed class TabItem : INotifyPropertyChanged
/// <summary>닫힌 시점의 인덱스(닫힌 탭 복원용). 열려 있는 동안은 무의미.</summary> /// <summary>닫힌 시점의 인덱스(닫힌 탭 복원용). 열려 있는 동안은 무의미.</summary>
internal int ClosedIndex { get; set; } = -1; internal int ClosedIndex { get; set; } = -1;
// ---- 탭별 일시 상태(앱 레이어 전용) ---- // ---- 탭별 일시 상태(호스트 앱이 사용) ----
internal string? PendingHistoryUrl; // SourceChanged → 타이틀 확정 사이의 기록 대기 URL /// <summary>SourceChanged → 타이틀 확정 사이의 히스토리 기록 대기 URL.</summary>
internal string? PendingSocialUrl; // 소셜 카드 제목 갱신 대기 URL public string? PendingHistoryUrl { get; set; }
internal FindSession? FindSession; // 탭별 페이지 내 검색 세션 (cv 바인딩) /// <summary>소셜 카드 제목 갱신 대기 URL.</summary>
public string? PendingSocialUrl { get; set; }
/// <summary>탭별 페이지 내 검색 세션 (CoreWebView2 바인딩).</summary>
public FindSession? FindSession { get; set; }
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;

View file

@ -1,6 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
namespace VideoDownloader.App.Browser.Tabs; namespace VideoDownloader.Browser.Tabs;
/// <summary> /// <summary>
/// 탭 관리 순수 로직(Phase A). WebView2 생성은 주입된 팩토리/어태처가 담당해 /// 탭 관리 순수 로직(Phase A). WebView2 생성은 주입된 팩토리/어태처가 담당해

View file

@ -1,7 +1,7 @@
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
namespace VideoDownloader.App.Browser.Tabs; namespace VideoDownloader.Browser.Tabs;
/// <summary>세션 JSON 저장소(T1-10). 종료 시 탭 URL 저장, 시작 시 복원.</summary> /// <summary>세션 JSON 저장소(T1-10). 종료 시 탭 URL 저장, 시작 시 복원.</summary>
public static class TabSessionStore public static class TabSessionStore

View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<RootNamespace>VideoDownloader.Browser</RootNamespace>
</PropertyGroup>
<!-- net8.0-windows WPF 빌드 시 SDK implicit usings 보강 -->
<ItemGroup>
<Using Include="System.IO" />
<Using Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.4078.44" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VideoDownloader.Core\VideoDownloader.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,13 @@
namespace VideoDownloader.Core.Platform;
/// <summary>
/// 앱 데이터 디렉터리 결정(Core 레벨). 기본 %AppData%/VideoDownloader.
/// VD_DATA_DIR 환경변수로 오버라이드 — E2E 테스트 격리 공식 후크.
/// </summary>
public static class CorePaths
{
public static string DataDir =>
Environment.GetEnvironmentVariable("VD_DATA_DIR") is { Length: > 0 } env
? env
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VideoDownloader");
}

View file

@ -1,4 +1,4 @@
using VideoDownloader.App.Browser; using VideoDownloader.Browser;
namespace VideoDownloader.Tests.Browser; namespace VideoDownloader.Tests.Browser;

View file

@ -1,4 +1,4 @@
using VideoDownloader.App.Browser; using VideoDownloader.Browser;
namespace VideoDownloader.Tests.Browser; namespace VideoDownloader.Tests.Browser;

View file

@ -1,4 +1,4 @@
using VideoDownloader.App.Browser; using VideoDownloader.Browser;
namespace VideoDownloader.Tests.Browser; namespace VideoDownloader.Tests.Browser;

View file

@ -1,4 +1,4 @@
using VideoDownloader.App.Browser.Tabs; using VideoDownloader.Browser.Tabs;
namespace VideoDownloader.Tests.Browser; namespace VideoDownloader.Tests.Browser;

View file

@ -1,6 +1,7 @@
<Solution> <Solution>
<Project Path="VideoDownloader.App/VideoDownloader.App.csproj" /> <Project Path="VideoDownloader.App/VideoDownloader.App.csproj" />
<Project Path="VideoDownloader.Avalonia/VideoDownloader.Avalonia.csproj" /> <Project Path="VideoDownloader.Avalonia/VideoDownloader.Avalonia.csproj" />
<Project Path="VideoDownloader.Browser/VideoDownloader.Browser.csproj" />
<Project Path="VideoDownloader.Cli/VideoDownloader.Cli.csproj" /> <Project Path="VideoDownloader.Cli/VideoDownloader.Cli.csproj" />
<Project Path="VideoDownloader.Core/VideoDownloader.Core.csproj" /> <Project Path="VideoDownloader.Core/VideoDownloader.Core.csproj" />
<Project Path="VideoDownloader.Tests/VideoDownloader.Tests.csproj" /> <Project Path="VideoDownloader.Tests/VideoDownloader.Tests.csproj" />