Skip to content

Commit 4a22b61

Browse files
committed
Improve app robustness, UX, and code quality
- Move config files to %AppData%\LibVideo\ with auto-migration - Replace fragile regex JSON parsing with JavaScriptSerializer - Unify media file extension lists across codebase - Add pinyin initial search for Chinese filenames - Enable DataGrid column sorting - Display TMDB rating in metadata panel - Persist search history across sessions - Use LiteDB shared connection mode to prevent lock conflicts - Fix async void in LoadMetadata to prevent unhandled exceptions
1 parent b8db3a4 commit 4a22b61

11 files changed

Lines changed: 320 additions & 101 deletions

File tree

LibVideo/App.xaml.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Windows;
44
using System.Threading.Tasks;
5+
using LibVideo.Helpers;
56

67
namespace LibVideo
78
{
@@ -11,9 +12,9 @@ protected override void OnStartup(StartupEventArgs e)
1112
{
1213
base.OnStartup(e);
1314

14-
if (File.Exists("language.txt"))
15+
if (File.Exists(AppPaths.LanguageFile))
1516
{
16-
string lang = File.ReadAllText("language.txt").Trim();
17+
string lang = File.ReadAllText(AppPaths.LanguageFile).Trim();
1718
ChangeLanguage(lang);
1819
}
1920
else
@@ -22,11 +23,11 @@ protected override void OnStartup(StartupEventArgs e)
2223
if (!System.Threading.Thread.CurrentThread.CurrentUICulture.Name.StartsWith("zh", StringComparison.OrdinalIgnoreCase))
2324
{
2425
ChangeLanguage("en");
25-
File.WriteAllText("language.txt", "en");
26+
File.WriteAllText(AppPaths.LanguageFile, "en");
2627
}
2728
else
2829
{
29-
File.WriteAllText("language.txt", "zh");
30+
File.WriteAllText(AppPaths.LanguageFile, "zh");
3031
}
3132
}
3233

LibVideo/Data/DatabaseManager.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22
using System.Linq;
33
using LiteDB;
44
using LibVideo.Models;
5+
using LibVideo.Helpers;
56

67
namespace LibVideo.Data
78
{
89
public class DatabaseManager
910
{
1011
private readonly string dbPath;
1112

12-
public DatabaseManager(string path = "libvideo_db.db")
13+
public DatabaseManager()
1314
{
14-
dbPath = path;
15+
dbPath = AppPaths.DatabaseFile;
1516
}
1617

1718
public void SyncDiskItems(IEnumerable<VideoItem> currentFilesList, HashSet<string> scannedRoots)
1819
{
19-
using (var db = new LiteDatabase(dbPath))
20+
using (var db = new LiteDatabase($"Filename={dbPath};Connection=Shared"))
2021
{
2122
var col = db.GetCollection<VideoItem>("videos");
2223
col.EnsureIndex(x => x.FullName, true);
@@ -88,6 +89,7 @@ public void UpdateItemMetadata(VideoItem memItem)
8889
dbItem.MetaPlot = memItem.MetaPlot;
8990
dbItem.MetaGenre = memItem.MetaGenre;
9091
dbItem.MetaPosterPath = memItem.MetaPosterPath;
92+
dbItem.MetaRating = memItem.MetaRating;
9193
dbItem.HasScraped = memItem.HasScraped;
9294
col.Update(dbItem);
9395
}

LibVideo/Helpers/AppPaths.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace LibVideo.Helpers
5+
{
6+
public static class AppPaths
7+
{
8+
private static readonly string _baseDir;
9+
10+
static AppPaths()
11+
{
12+
_baseDir = Path.Combine(
13+
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
14+
"LibVideo");
15+
16+
if (!Directory.Exists(_baseDir))
17+
Directory.CreateDirectory(_baseDir);
18+
19+
MigrateOldFiles();
20+
}
21+
22+
public static string LanguageFile => Path.Combine(_baseDir, "language.txt");
23+
public static string DirectoriesFile => Path.Combine(_baseDir, "directories.txt");
24+
public static string PlayerFile => Path.Combine(_baseDir, "player.txt");
25+
public static string SearchHistoryFile => Path.Combine(_baseDir, "search_history.txt");
26+
public static string DatabaseFile => Path.Combine(_baseDir, "libvideo_db.db");
27+
28+
/// <summary>
29+
/// One-time migration: copies config files from the exe directory to %AppData%\LibVideo\
30+
/// so existing users don't lose their settings.
31+
/// </summary>
32+
private static void MigrateOldFiles()
33+
{
34+
string exeDir = AppDomain.CurrentDomain.BaseDirectory;
35+
MigrateFile(Path.Combine(exeDir, "language.txt"), LanguageFile);
36+
MigrateFile(Path.Combine(exeDir, "directories.txt"), DirectoriesFile);
37+
MigrateFile(Path.Combine(exeDir, "player.txt"), PlayerFile);
38+
MigrateFile(Path.Combine(exeDir, "libvideo_db.db"), DatabaseFile);
39+
}
40+
41+
private static void MigrateFile(string oldPath, string newPath)
42+
{
43+
if (File.Exists(oldPath) && !File.Exists(newPath))
44+
{
45+
try { File.Copy(oldPath, newPath); } catch { }
46+
}
47+
}
48+
}
49+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace LibVideo.Helpers
5+
{
6+
/// <summary>
7+
/// Single source of truth for all supported media file extensions.
8+
/// Eliminates duplication between MainViewModel and MetadataService.
9+
/// </summary>
10+
public static class MediaExtensions
11+
{
12+
public static readonly string[] VideoExtensions =
13+
{
14+
".mp4", ".mkv", ".avi", ".rmvb", ".wmv", ".flv",
15+
".mov", ".ts", ".iso", ".m2ts"
16+
};
17+
18+
public static readonly string[] AudioExtensions =
19+
{
20+
".mp3", ".flac", ".wav", ".ape", ".m4a",
21+
".aac", ".wma", ".ogg", ".mid"
22+
};
23+
24+
public static readonly string[] AllMediaExtensions;
25+
26+
static MediaExtensions()
27+
{
28+
AllMediaExtensions = VideoExtensions.Concat(AudioExtensions).ToArray();
29+
}
30+
31+
public static bool IsMediaFile(string extension)
32+
{
33+
return AllMediaExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
34+
}
35+
36+
public static bool IsAudioFile(string extension)
37+
{
38+
return AudioExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
39+
}
40+
41+
public static bool IsVideoFile(string extension)
42+
{
43+
return VideoExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
44+
}
45+
}
46+
}

LibVideo/Helpers/PinyinHelper.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System.Text;
2+
3+
namespace LibVideo.Helpers
4+
{
5+
/// <summary>
6+
/// Lightweight pinyin initial extractor using GB2312 encoding ranges.
7+
/// Zero external dependencies. Converts Chinese characters to their
8+
/// pinyin first letter (e.g. "战狼" → "ZL").
9+
/// </summary>
10+
public static class PinyinHelper
11+
{
12+
private static readonly Encoding _gb2312;
13+
14+
static PinyinHelper()
15+
{
16+
try
17+
{
18+
_gb2312 = Encoding.GetEncoding("GB2312");
19+
}
20+
catch
21+
{
22+
_gb2312 = null;
23+
}
24+
}
25+
26+
/// <summary>
27+
/// Returns a string where each Chinese character is replaced by its
28+
/// pinyin initial letter. Non-Chinese characters are kept as-is.
29+
/// Example: "战狼2" → "ZL2"
30+
/// </summary>
31+
public static string GetInitials(string text)
32+
{
33+
if (string.IsNullOrEmpty(text) || _gb2312 == null) return text ?? "";
34+
35+
var sb = new StringBuilder(text.Length);
36+
foreach (char ch in text)
37+
{
38+
if (ch >= 0x4E00 && ch <= 0x9FFF)
39+
{
40+
sb.Append(GetChineseInitial(ch));
41+
}
42+
else
43+
{
44+
sb.Append(ch);
45+
}
46+
}
47+
48+
return sb.ToString();
49+
}
50+
51+
private static char GetChineseInitial(char ch)
52+
{
53+
byte[] bytes;
54+
try
55+
{
56+
bytes = _gb2312.GetBytes(ch.ToString());
57+
}
58+
catch
59+
{
60+
return ch;
61+
}
62+
63+
if (bytes.Length != 2) return ch;
64+
65+
int code = bytes[0] * 256 + bytes[1];
66+
67+
if (code < 0xB0A1 || code > 0xD7F9) return ch;
68+
69+
if (code <= 0xB0C4) return 'A';
70+
if (code <= 0xB2C0) return 'B';
71+
if (code <= 0xB4ED) return 'C';
72+
if (code <= 0xB6E9) return 'D';
73+
if (code <= 0xB7A1) return 'E';
74+
if (code <= 0xB8C0) return 'F';
75+
if (code <= 0xB9FD) return 'G';
76+
if (code <= 0xBBF6) return 'H';
77+
if (code <= 0xBFA5) return 'J';
78+
if (code <= 0xC0AB) return 'K';
79+
if (code <= 0xC2E7) return 'L';
80+
if (code <= 0xC4C2) return 'M';
81+
if (code <= 0xC5B5) return 'N';
82+
if (code <= 0xC5BD) return 'O';
83+
if (code <= 0xC6D9) return 'P';
84+
if (code <= 0xC8BA) return 'Q';
85+
if (code <= 0xC8F5) return 'R';
86+
if (code <= 0xCBF9) return 'S';
87+
if (code <= 0xCDD9) return 'T';
88+
if (code <= 0xCEF3) return 'W';
89+
if (code <= 0xD1B8) return 'X';
90+
if (code <= 0xD4D0) return 'Y';
91+
if (code <= 0xD7F9) return 'Z';
92+
93+
return ch;
94+
}
95+
}
96+
}

LibVideo/LibVideo2.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
<Reference Include="WindowsBase" />
104104
<Reference Include="PresentationCore" />
105105
<Reference Include="PresentationFramework" />
106+
<Reference Include="System.Web.Extensions" />
106107
</ItemGroup>
107108
<ItemGroup>
108109
<ApplicationDefinition Include="App.xaml">
@@ -149,6 +150,9 @@
149150
<Compile Include="Models\MetadataService.cs" />
150151
<Compile Include="ViewModels\MainViewModel.cs" />
151152
<Compile Include="Data\DatabaseManager.cs" />
153+
<Compile Include="Helpers\AppPaths.cs" />
154+
<Compile Include="Helpers\MediaExtensions.cs" />
155+
<Compile Include="Helpers\PinyinHelper.cs" />
152156
</ItemGroup>
153157
<ItemGroup>
154158
<Compile Include="Properties\AssemblyInfo.cs">
@@ -192,6 +196,7 @@
192196
</BootstrapperPackage>
193197
</ItemGroup>
194198
<ItemGroup>
199+
<Folder Include="Helpers\" />
195200
<Folder Include="Resources\" />
196201
</ItemGroup>
197202
<ItemGroup>

LibVideo/MainWindow.xaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
</Grid>
6767

6868
<!-- Main DataGrid -->
69-
<DataGrid Grid.Row="1" x:Name="outputGrid" SelectedItem="{Binding SelectedVideo}" Style="{StaticResource MaterialDesignDataGrid}" ItemsSource="{Binding VideoItems}" Margin="16,0,16,16" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow" IsReadOnly="true" VirtualizingPanel.IsVirtualizing="True">
69+
<DataGrid Grid.Row="1" x:Name="outputGrid" SelectedItem="{Binding SelectedVideo}" Style="{StaticResource MaterialDesignDataGrid}" ItemsSource="{Binding VideoItems}" Margin="16,0,16,16" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow" IsReadOnly="true" CanUserSortColumns="True" VirtualizingPanel.IsVirtualizing="True">
7070
<DataGrid.CellStyle>
7171
<Style TargetType="DataGridCell" BasedOn="{StaticResource MaterialDesignDataGridCell}">
7272
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
@@ -79,7 +79,7 @@
7979
</Style>
8080
</DataGrid.RowStyle>
8181
<DataGrid.Columns>
82-
<DataGridTemplateColumn Width="85">
82+
<DataGridTemplateColumn Width="85" CanUserSort="False">
8383
<DataGridTemplateColumn.CellTemplate>
8484
<DataTemplate>
8585
<StackPanel Orientation="Horizontal" Visibility="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}, Converter={StaticResource BooleanToVisibilityConverter}}">
@@ -168,7 +168,10 @@
168168

169169
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="20,0,10,0">
170170
<TextBlock Text="{Binding CurrentMetadata.Title}" FontSize="26" FontWeight="Bold" Foreground="White" Margin="0,0,0,6" TextWrapping="Wrap" />
171-
<TextBlock Text="{Binding CurrentMetadata.Genre}" FontSize="13" Foreground="#B3FFFFFF" Margin="0,0,0,10" />
171+
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
172+
<TextBlock Text="{Binding CurrentMetadata.Genre}" FontSize="13" Foreground="#B3FFFFFF" />
173+
<TextBlock Text="{Binding CurrentMetadata.RatingDisplay}" FontSize="13" Foreground="#FFD700" Margin="12,0,0,0" />
174+
</StackPanel>
172175
<TextBlock Text="{Binding CurrentMetadata.Plot}" FontSize="14" Foreground="#E6FFFFFF" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" MaxHeight="85" LineHeight="20" />
173176
</StackPanel>
174177
</Grid>

0 commit comments

Comments
 (0)