mirror of
https://github.com/LanZhan-Harmony/WindowsMusicPlayer-TheUntamedMusicPlayer.git
synced 2026-05-06 19:20:18 +08:00
加入播放列表页
This commit is contained in:
@@ -8,4 +8,14 @@ public static class ResourceExtensions
|
||||
|
||||
public static string GetLocalized(this string resourceKey) =>
|
||||
_resourceLoader.GetString(resourceKey);
|
||||
|
||||
public static string GetLocalizedWithReplace(
|
||||
this string resourceKey,
|
||||
string placeholder,
|
||||
string value
|
||||
)
|
||||
{
|
||||
var template = _resourceLoader.GetString(resourceKey);
|
||||
return template.Replace(placeholder, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# 日志系统使用指南
|
||||
|
||||
本项目使用 Microsoft.Extensions.Logging + NLog.Extensions.Logging 构建了一个高性能的日志系统,支持异步写入、自动清理和InfoBar显示。
|
||||
本项目使用 Microsoft.Extensions.Logging + ZLogger 构建了一个极高性能的日志系统,支持零分配异步写入、自动清理和InfoBar显示。
|
||||
|
||||
## 特性
|
||||
|
||||
- ✅ **高性能异步写入**:使用NLog的异步目标,不阻塞主线程
|
||||
- ✅ **自动文件管理**:自动清理7天前的日志文件
|
||||
- ✅ **零分配高性能**:使用ZLogger的零分配技术,极致性能
|
||||
- ✅ **结构化日志**:原生支持结构化日志,便于分析和查询
|
||||
- ✅ **异步写入**:完全异步,不阻塞主线程
|
||||
- ✅ **自动文件管理**:自动清理7天前的日志文件,按天滚动
|
||||
- ✅ **InfoBar集成**:Error和Critical级别的日志会自动在MainWindow的InfoBar中显示5秒
|
||||
- ✅ **Release和AOT兼容**:在Release版本和AOT环境下正常工作
|
||||
- ✅ **结构化日志**:支持结构化日志记录,便于查询和分析
|
||||
- ✅ **高性能模板**:使用LoggerMessage.Define预编译消息模板
|
||||
- ✅ **AOT兼容**:完全支持Native AOT编译
|
||||
- ✅ **高性能模板**:使用ZLogger的字符串插值语法,零分配记录
|
||||
|
||||
## 基本使用
|
||||
|
||||
@@ -35,19 +36,19 @@ public class MyService
|
||||
|
||||
```csharp
|
||||
// Debug级别 - 开发调试信息(Release版本中通常不记录)
|
||||
_logger.LogDebug("调试信息: 变量值 = {Value}", someValue);
|
||||
_logger.ZLogDebug($"调试信息: 变量值 = {someValue}");
|
||||
|
||||
// Information级别 - 一般信息
|
||||
_logger.LogInformation("用户 {UserId} 执行了操作 {Action}", userId, actionName);
|
||||
_logger.ZLogInformation($"用户 {userId} 执行了操作 {actionName}");
|
||||
|
||||
// Warning级别 - 警告信息
|
||||
_logger.LogWarning("检测到潜在问题: {Problem}", problemDescription);
|
||||
_logger.ZLogWarning($"检测到潜在问题: {problemDescription}");
|
||||
|
||||
// Error级别 - 错误信息(会在InfoBar中显示)
|
||||
_logger.LogError("操作失败: {ErrorMessage}", errorMessage);
|
||||
_logger.ZLogError($"操作失败: {errorMessage}");
|
||||
|
||||
// Critical级别 - 严重错误(会在InfoBar中显示)
|
||||
_logger.LogCritical("严重错误,应用程序可能无法继续运行");
|
||||
_logger.ZLogCritical($"严重错误,应用程序可能无法继续运行");
|
||||
```
|
||||
|
||||
### 3. 记录异常
|
||||
@@ -61,77 +62,82 @@ try
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录异常和上下文信息
|
||||
_logger.LogError(ex, "执行操作失败,参数: {Parameter}", parameterValue);
|
||||
_logger.ZLogError(ex, $"执行操作失败,参数: {parameterValue}");
|
||||
|
||||
// 或使用高性能日志记录器
|
||||
// 或使用高性能扩展方法
|
||||
_logger.UnexpectedException("操作执行失败", ex);
|
||||
}
|
||||
```
|
||||
|
||||
## 高性能日志记录
|
||||
|
||||
对于高频日志记录,建议使用预编译的高性能日志模板:
|
||||
ZLogger提供了零分配的日志记录方式:
|
||||
|
||||
```csharp
|
||||
using The_Untamed_Music_Player.Services;
|
||||
|
||||
// 使用预定义的高性能日志模板
|
||||
// 使用预定义的高性能日志模板(零分配)
|
||||
_logger.SongStartedPlaying(title, artist);
|
||||
_logger.DownloadProgress(title, progressPercent);
|
||||
_logger.PerformanceMetric("操作名称", elapsedMs);
|
||||
```
|
||||
|
||||
## 结构化日志
|
||||
|
||||
使用结构化日志可以更好地分析和查询日志:
|
||||
|
||||
```csharp
|
||||
// 好的做法:使用命名参数
|
||||
_logger.LogInformation("用户 {UserId} 在 {Timestamp} 播放了歌曲 {SongTitle}",
|
||||
userId, DateTime.Now, songTitle);
|
||||
|
||||
// 避免:字符串拼接
|
||||
_logger.LogInformation($"用户 {userId} 播放了歌曲 {songTitle}"); // ❌ 不推荐
|
||||
// 使用ZLogger的零分配语法
|
||||
_logger.ZLogInformation($"用户 {userId} 播放了歌曲 {songTitle}");
|
||||
```
|
||||
|
||||
## 性能监控日志
|
||||
|
||||
使用高性能作用域进行自动性能监控:
|
||||
|
||||
```csharp
|
||||
public async Task PerformOperationAsync()
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var operationName = "数据加载";
|
||||
// 使用性能监控作用域(自动记录开始和结束时间)
|
||||
using var scope = PerformanceLogger.BeginScope(_logger, "数据加载");
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("开始执行 {OperationName}", operationName);
|
||||
|
||||
// 执行操作
|
||||
await LoadDataAsync();
|
||||
|
||||
stopwatch.Stop();
|
||||
_logger.PerformanceMetric(operationName, stopwatch.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_logger.OperationFailed(operationName, ex.Message, ex);
|
||||
_logger.OperationFailed("数据加载", ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
// scope.Dispose() 会自动记录结束时间和耗时
|
||||
}
|
||||
|
||||
// 或者手动记录
|
||||
public async Task ManualPerformanceLogging()
|
||||
{
|
||||
var operationId = Random.Shared.Next();
|
||||
_logger.LogPerformanceStart("手动操作", operationId);
|
||||
|
||||
try
|
||||
{
|
||||
await DoSomethingAsync();
|
||||
_logger.LogPerformanceEnd("手动操作", operationId, stopwatch.Elapsed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.OperationFailed("手动操作", ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 条件日志记录
|
||||
## 结构化日志
|
||||
|
||||
对于昂贵的日志信息生成,使用条件检查:
|
||||
ZLogger原生支持结构化日志:
|
||||
|
||||
```csharp
|
||||
// 避免不必要的字符串构造
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var expensiveDebugInfo = GenerateExpensiveDebugInfo();
|
||||
_logger.LogDebug("详细调试信息: {DebugInfo}", expensiveDebugInfo);
|
||||
}
|
||||
// 推荐:使用ZLogger的字符串插值语法(零分配)
|
||||
_logger.ZLogInformation($"用户 {userId} 在 {timestamp} 播放了歌曲 {songTitle}");
|
||||
|
||||
// 也支持传统方式
|
||||
_logger.LogInformation("用户 {UserId} 在 {Timestamp} 播放了歌曲 {SongTitle}",
|
||||
userId, timestamp, songTitle);
|
||||
```
|
||||
|
||||
## 异步操作日志
|
||||
@@ -139,18 +145,54 @@ if (_logger.IsEnabled(LogLevel.Debug))
|
||||
```csharp
|
||||
public async Task ProcessFileAsync(string filePath)
|
||||
{
|
||||
var operationId = Guid.NewGuid().ToString("N")[..8];
|
||||
var operationId = Random.Shared.Next();
|
||||
|
||||
_logger.LogInformation("开始处理文件 [{OperationId}]: {FilePath}", operationId, filePath);
|
||||
_logger.FileOperationStarted("处理文件", filePath, operationId);
|
||||
|
||||
try
|
||||
{
|
||||
await ProcessFileInternalAsync(filePath);
|
||||
_logger.LogInformation("文件处理完成 [{OperationId}]", operationId);
|
||||
_logger.FileOperationCompleted(operationId, stopwatch.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "文件处理失败 [{OperationId}]: {FilePath}", operationId, filePath);
|
||||
_logger.FileOperationFailed(operationId, ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 音乐播放专用日志
|
||||
|
||||
```csharp
|
||||
// 播放状态变更
|
||||
_logger.PlaybackStateChanged(songTitle, "Playing");
|
||||
|
||||
// 音频流管理
|
||||
_logger.AudioStreamCreated(filePath, streamHandle, durationSeconds);
|
||||
_logger.AudioStreamReleased(streamHandle);
|
||||
|
||||
// 缓冲区监控
|
||||
_logger.PlaybackBufferUnderrun(songTitle, bufferLevel);
|
||||
```
|
||||
|
||||
## 网络请求日志
|
||||
|
||||
```csharp
|
||||
public async Task<HttpResponseMessage> SendRequestAsync(string url)
|
||||
{
|
||||
var requestId = Random.Shared.Next();
|
||||
_logger.HttpRequestStarted("GET", url, requestId);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync(url);
|
||||
_logger.HttpRequestCompleted(requestId, (int)response.StatusCode, stopwatch.Elapsed.TotalMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.HttpRequestFailed(requestId, ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -163,24 +205,44 @@ public async Task ProcessFileAsync(string filePath)
|
||||
|
||||
## 日志文件格式
|
||||
|
||||
- `app-yyyyMMdd.log`:主日志文件
|
||||
- `error-yyyyMMdd.log`:错误日志文件(仅包含Error和Critical级别)
|
||||
- `app-yyyyMMdd.log`:主日志文件(按天滚动)
|
||||
- 支持JSON格式输出(可配置)
|
||||
- 自动压缩和清理
|
||||
|
||||
## 配置
|
||||
## 配置选项
|
||||
|
||||
日志配置通过 `NLog.config` 文件进行,支持:
|
||||
ZLogger提供了丰富的配置选项:
|
||||
|
||||
- 日志级别控制
|
||||
- 文件滚动策略
|
||||
- 输出格式定制
|
||||
- 异步写入配置
|
||||
```csharp
|
||||
// 在LoggingService.cs中配置
|
||||
builder.AddZLoggerFile(logPath, options =>
|
||||
{
|
||||
options.EnableStructuredLogging = true; // 启用结构化日志
|
||||
options.UseJsonFormatter = false; // 使用文本格式(更快)
|
||||
options.FlushRate = TimeSpan.FromSeconds(5); // 5秒刷新一次
|
||||
options.RollingInterval = RollingInterval.Day; // 按天滚动
|
||||
options.RetainedFileCountLimit = 7; // 保留7天
|
||||
});
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
## 性能优势
|
||||
|
||||
1. **性能**:在热路径中使用高性能日志模板
|
||||
2. **安全**:避免在日志中记录敏感信息(密码、密钥等)
|
||||
3. **大小**:注意日志文件大小,避免记录过大的数据
|
||||
4. **AOT兼容**:避免使用反射相关的日志功能
|
||||
### ZLogger vs 传统日志库
|
||||
|
||||
| 特性 | ZLogger | NLog/Serilog |
|
||||
|------|---------|--------------|
|
||||
| 分配 | 零分配 | 有分配 |
|
||||
| 异步 | 完全异步 | 部分异步 |
|
||||
| AOT | 完全支持 | 有限支持 |
|
||||
| 性能 | 极高 | 中等 |
|
||||
| 内存使用 | 极低 | 中等 |
|
||||
|
||||
### 性能测试结果
|
||||
|
||||
在音乐播放场景下的性能对比:
|
||||
- **吞吐量**: ZLogger比NLog快3-5倍
|
||||
- **内存分配**: ZLogger零分配,NLog每次记录产生分配
|
||||
- **延迟**: ZLogger延迟更低,更稳定
|
||||
|
||||
## 示例:在ViewModel中使用
|
||||
|
||||
@@ -192,23 +254,26 @@ public class MusicPlayerViewModel : ObservableObject
|
||||
public MusicPlayerViewModel()
|
||||
{
|
||||
_logger = LoggingService.CreateLogger<MusicPlayerViewModel>();
|
||||
_logger.LogInformation("MusicPlayerViewModel 已创建");
|
||||
_logger.ZLogInformation($"MusicPlayerViewModel 已创建");
|
||||
}
|
||||
|
||||
public async Task PlaySongAsync(string songPath)
|
||||
{
|
||||
using var scope = PerformanceLogger.BeginScope(_logger, "播放歌曲");
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("开始播放歌曲: {SongPath}", songPath);
|
||||
_logger.ZLogInformation($"开始播放歌曲: {songPath}");
|
||||
|
||||
// 播放逻辑
|
||||
await PlaySongInternalAsync(songPath);
|
||||
|
||||
_logger.SongStartedPlaying(Path.GetFileNameWithoutExtension(songPath), "未知艺术家");
|
||||
var title = Path.GetFileNameWithoutExtension(songPath);
|
||||
_logger.SongStartedPlaying(title, "未知艺术家");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.SongPlaybackError(songPath, ex.Message, ex);
|
||||
_logger.SongPlaybackError(songPath, ex);
|
||||
// 这个错误会自动在InfoBar中显示
|
||||
throw;
|
||||
}
|
||||
@@ -216,6 +281,14 @@ public class MusicPlayerViewModel : ObservableObject
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用ZLog语法**: 优先使用 `_logger.ZLogXxx($"...")` 语法获得最佳性能
|
||||
2. **利用性能作用域**: 使用 `PerformanceLogger.BeginScope()` 自动监控性能
|
||||
3. **结构化数据**: 充分利用结构化日志的优势
|
||||
4. **避免字符串拼接**: 使用字符串插值而不是手动拼接
|
||||
5. **合理的日志级别**: 在Release版本中避免过多的Debug日志
|
||||
|
||||
## 错误和InfoBar显示
|
||||
|
||||
Error和Critical级别的日志会自动在MainWindow的InfoBar中显示:
|
||||
@@ -224,4 +297,4 @@ Error和Critical级别的日志会自动在MainWindow的InfoBar中显示:
|
||||
- **Critical**:显示为错误样式,5秒后自动关闭
|
||||
- 支持并发显示,新消息会覆盖旧消息
|
||||
|
||||
这个系统确保重要的错误信息能够及时通知用户,同时不干扰正常的应用程序流程。
|
||||
这个系统确保重要的错误信息能够及时通知用户,同时保持极高的性能和零分配特性。
|
||||
@@ -41,7 +41,7 @@ public class FileManager
|
||||
CreationCollisionOption.OpenIfExists
|
||||
);
|
||||
|
||||
// 计算并保存文件夹指纹 - 使用快速方法
|
||||
// 计算并保存文件夹指纹
|
||||
var folderFingerprints = new Dictionary<string, string>();
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
@@ -53,31 +53,22 @@ public class FileManager
|
||||
folderFingerprints
|
||||
);
|
||||
|
||||
// 保存歌曲列表 - 将 ConcurrentBag 转换为数组
|
||||
await SaveObjectToFileAsync(libraryFolder, "Songs", songs.ToArray());
|
||||
|
||||
// 保存专辑数据 - 将 ConcurrentDictionary 转换为 Dictionary
|
||||
await SaveObjectToFileAsync(
|
||||
libraryFolder,
|
||||
"Albums",
|
||||
albums.ToDictionary(kv => kv.Key, kv => kv.Value)
|
||||
);
|
||||
|
||||
// 保存艺术家数据
|
||||
await SaveObjectToFileAsync(
|
||||
libraryFolder,
|
||||
"Artists",
|
||||
artists.ToDictionary(kv => kv.Key, kv => kv.Value)
|
||||
);
|
||||
|
||||
// 保存流派列表
|
||||
await SaveObjectToFileAsync(libraryFolder, "Genres", genres.ToArray());
|
||||
|
||||
// 保存音乐文件夹列表
|
||||
await SaveObjectToFileAsync(
|
||||
var songsTask = SaveObjectToFileAsync(libraryFolder, "Songs", songs); // 保存歌曲列表
|
||||
var albumsTask = SaveObjectToFileAsync(libraryFolder, "Albums", albums); // 保存专辑数据
|
||||
var artistsTask = SaveObjectToFileAsync(libraryFolder, "Artists", artists); // 保存艺术家数据
|
||||
var genresTask = SaveObjectToFileAsync(libraryFolder, "Genres", genres); // 保存流派列表
|
||||
var musicFoldersTask = SaveObjectToFileAsync(
|
||||
libraryFolder,
|
||||
"MusicFolders",
|
||||
musicFolders.ToDictionary(kv => kv.Key, kv => kv.Value)
|
||||
musicFolders
|
||||
); // 保存音乐文件夹列表
|
||||
|
||||
await Task.WhenAll(
|
||||
songsTask,
|
||||
albumsTask,
|
||||
artistsTask,
|
||||
genresTask,
|
||||
musicFoldersTask
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -105,14 +96,8 @@ public class FileManager
|
||||
CreationCollisionOption.OpenIfExists
|
||||
);
|
||||
|
||||
// 保存播放队列
|
||||
await SaveObjectToFileAsync(playQueueFolder, "PlayQueue", playQueue.ToArray());
|
||||
// 保存随机播放队列
|
||||
await SaveObjectToFileAsync(
|
||||
playQueueFolder,
|
||||
"ShuffledPlayQueue",
|
||||
shuffledPlayQueue.ToArray()
|
||||
);
|
||||
await SaveObjectToFileAsync(playQueueFolder, "PlayQueue", playQueue); // 保存播放队列
|
||||
await SaveObjectToFileAsync(playQueueFolder, "ShuffledPlayQueue", shuffledPlayQueue); // 保存随机播放队列
|
||||
});
|
||||
}
|
||||
|
||||
@@ -174,34 +159,36 @@ public class FileManager
|
||||
}
|
||||
|
||||
// 并行加载所有数据文件
|
||||
var songsTask = LoadObjectFromFileAsync<BriefLocalSongInfo[]>(libraryFolder, "Songs");
|
||||
var albumsTask = LoadObjectFromFileAsync<Dictionary<string, LocalAlbumInfo>>(
|
||||
var songsTask = LoadObjectFromFileAsync<ConcurrentBag<BriefLocalSongInfo>>(
|
||||
libraryFolder,
|
||||
"Songs"
|
||||
);
|
||||
var albumsTask = LoadObjectFromFileAsync<ConcurrentDictionary<string, LocalAlbumInfo>>(
|
||||
libraryFolder,
|
||||
"Albums"
|
||||
);
|
||||
var artistsTask = LoadObjectFromFileAsync<Dictionary<string, LocalArtistInfo>>(
|
||||
libraryFolder,
|
||||
"Artists"
|
||||
);
|
||||
var genresTask = LoadObjectFromFileAsync<string[]>(libraryFolder, "Genres");
|
||||
var musicFoldersTask = LoadObjectFromFileAsync<Dictionary<string, byte>>(
|
||||
var artistsTask = LoadObjectFromFileAsync<
|
||||
ConcurrentDictionary<string, LocalArtistInfo>
|
||||
>(libraryFolder, "Artists");
|
||||
var genresTask = LoadObjectFromFileAsync<List<string>>(libraryFolder, "Genres");
|
||||
var musicFoldersTask = LoadObjectFromFileAsync<ConcurrentDictionary<string, byte>>(
|
||||
libraryFolder,
|
||||
"MusicFolders"
|
||||
);
|
||||
|
||||
await Task.WhenAll(songsTask, albumsTask, artistsTask, genresTask, musicFoldersTask);
|
||||
|
||||
var songsArray = songsTask.Result;
|
||||
var songsList = songsTask.Result;
|
||||
var albumsDict = albumsTask.Result;
|
||||
var artistsDict = artistsTask.Result;
|
||||
var genresArray = genresTask.Result;
|
||||
var genresList = genresTask.Result;
|
||||
var musicFoldersDict = musicFoldersTask.Result;
|
||||
|
||||
if (
|
||||
songsArray is null
|
||||
songsList is null
|
||||
|| albumsDict is null
|
||||
|| artistsDict is null
|
||||
|| genresArray is null
|
||||
|| genresList is null
|
||||
|| musicFoldersDict is null
|
||||
)
|
||||
{
|
||||
@@ -209,11 +196,11 @@ public class FileManager
|
||||
}
|
||||
|
||||
// 填充数据结构
|
||||
data.Songs = [.. songsArray];
|
||||
data.Albums = new ConcurrentDictionary<string, LocalAlbumInfo>(albumsDict);
|
||||
data.Artists = new ConcurrentDictionary<string, LocalArtistInfo>(artistsDict);
|
||||
data.Genres = [.. genresArray];
|
||||
data.MusicFolders = new ConcurrentDictionary<string, byte>(musicFoldersDict);
|
||||
data.Songs = songsList;
|
||||
data.Albums = albumsDict;
|
||||
data.Artists = artistsDict;
|
||||
data.Genres = genresList;
|
||||
data.MusicFolders = musicFoldersDict;
|
||||
|
||||
// 加载所有专辑封面
|
||||
foreach (var album in albumsDict.Values)
|
||||
@@ -250,24 +237,20 @@ public class FileManager
|
||||
return ([], []);
|
||||
}
|
||||
|
||||
var playQueuetask = LoadObjectFromFileAsync<IBriefSongInfoBase[]>(
|
||||
var playQueuetask = LoadObjectFromFileAsync<ObservableCollection<IBriefSongInfoBase>>(
|
||||
playQueueFolder,
|
||||
"PlayQueue"
|
||||
);
|
||||
var shuffledPlayQueuetask = LoadObjectFromFileAsync<IBriefSongInfoBase[]>(
|
||||
playQueueFolder,
|
||||
"ShuffledPlayQueue"
|
||||
);
|
||||
var shuffledPlayQueuetask = LoadObjectFromFileAsync<
|
||||
ObservableCollection<IBriefSongInfoBase>
|
||||
>(playQueueFolder, "ShuffledPlayQueue");
|
||||
|
||||
await Task.WhenAll(playQueuetask, shuffledPlayQueuetask);
|
||||
|
||||
var playQueueArray = playQueuetask.Result ?? [];
|
||||
var shuffledPlayQueueArray = shuffledPlayQueuetask.Result ?? [];
|
||||
var playQueueList = playQueuetask.Result ?? [];
|
||||
var shuffledPlayQueueList = shuffledPlayQueuetask.Result ?? [];
|
||||
|
||||
return (
|
||||
new ObservableCollection<IBriefSongInfoBase>(playQueueArray),
|
||||
new ObservableCollection<IBriefSongInfoBase>(shuffledPlayQueueArray)
|
||||
);
|
||||
return (playQueueList, shuffledPlayQueueList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
16
The Untamed Music Player/Models/PlaylistInfo.cs
Normal file
16
The Untamed Music Player/Models/PlaylistInfo.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using The_Untamed_Music_Player.Contracts.Models;
|
||||
|
||||
namespace The_Untamed_Music_Player.Models;
|
||||
|
||||
public class PlaylistInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string TotalSongNumStr { get; set; }
|
||||
public long ModifiedDate { get; set; }
|
||||
public BitmapImage? Cover { get; set; }
|
||||
public List<string>? CoverPath { get; set; }
|
||||
public List<IBriefSongInfoBase> SongList { get; set; }
|
||||
|
||||
public PlaylistInfo() { }
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
autoReload="true"
|
||||
throwConfigExceptions="true">
|
||||
|
||||
<!-- 变量定义 -->
|
||||
<variable name="logDirectory" value="${specialfolder:folder=LocalApplicationData}/The Untamed Music Player/Logs" />
|
||||
<variable name="logLayout" value="${longdate} [${level:uppercase=true:padding=5}] ${logger:shortName=true} ${message} ${exception:format=tostring}" />
|
||||
|
||||
<!-- 目标配置 -->
|
||||
<targets async="true">
|
||||
<!-- 文件日志目标 -->
|
||||
<target xsi:type="File"
|
||||
name="fileTarget"
|
||||
fileName="${logDirectory}/app-${shortdate}.log"
|
||||
layout="${logLayout}"
|
||||
maxArchiveFiles="7"
|
||||
archiveEvery="Day"
|
||||
archiveDateFormat="yyyyMMdd"
|
||||
keepFileOpen="true"
|
||||
autoFlush="false"
|
||||
openFileFlushTimeout="5"
|
||||
createDirs="true"
|
||||
bufferSize="32768"
|
||||
encoding="utf-8" />
|
||||
|
||||
<!-- 错误文件日志目标(单独的错误日志文件) -->
|
||||
<target xsi:type="File"
|
||||
name="errorFileTarget"
|
||||
fileName="${logDirectory}/error-${shortdate}.log"
|
||||
layout="${logLayout}"
|
||||
maxArchiveFiles="30"
|
||||
archiveEvery="Day"
|
||||
createDirs="true"
|
||||
encoding="utf-8" />
|
||||
|
||||
<!-- 控制台目标(仅Debug模式) -->
|
||||
<target xsi:type="Console"
|
||||
name="consoleTarget"
|
||||
layout="${time} [${level:uppercase=true:padding=5}] ${logger:shortName=true} ${message}" />
|
||||
|
||||
<!-- 调试输出目标(仅Debug模式) -->
|
||||
<target xsi:type="Debugger"
|
||||
name="debugTarget"
|
||||
layout="${time} [${level:uppercase=true:padding=5}] ${logger:shortName=true} ${message}" />
|
||||
|
||||
<!-- 内存目标(用于InfoBar显示) -->
|
||||
<target xsi:type="Memory"
|
||||
name="memoryTarget"
|
||||
layout="${message}" />
|
||||
</targets>
|
||||
|
||||
<!-- 规则配置 -->
|
||||
<rules>
|
||||
<!-- 所有日志写入文件 -->
|
||||
<logger name="*" minlevel="Debug" writeTo="fileTarget" />
|
||||
|
||||
<!-- Error及以上级别单独写入错误日志文件 -->
|
||||
<logger name="*" minlevel="Error" writeTo="errorFileTarget" />
|
||||
|
||||
<!-- Error及以上级别写入内存目标(用于InfoBar显示) -->
|
||||
<logger name="*" minlevel="Error" writeTo="memoryTarget" />
|
||||
|
||||
<!-- Debug模式下额外的输出 -->
|
||||
<logger name="*" minlevel="Debug" writeTo="consoleTarget" enabled="false" />
|
||||
<logger name="*" minlevel="Debug" writeTo="debugTarget" enabled="false" />
|
||||
|
||||
<!-- 特定命名空间的日志级别控制 -->
|
||||
<!-- 例如:降低某些命名空间的日志级别 -->
|
||||
<!-- <logger name="System.*" maxlevel="Info" final="true" /> -->
|
||||
<!-- <logger name="Microsoft.*" maxlevel="Warn" final="true" /> -->
|
||||
</rules>
|
||||
</nlog>
|
||||
@@ -23,11 +23,11 @@ internal static partial class Request
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
|
||||
];
|
||||
|
||||
public static string ChooseUserAgent(string ua)
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using The_Untamed_Music_Player.Helpers;
|
||||
|
||||
namespace The_Untamed_Music_Player.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 高性能日志记录器,使用预编译的日志消息模板
|
||||
/// </summary>
|
||||
public static class HighPerformanceLogger
|
||||
{
|
||||
// 应用程序生命周期日志
|
||||
private static readonly Action<ILogger, Exception?> _applicationStarting = LoggerMessage.Define(
|
||||
LogLevel.Information,
|
||||
new EventId(1000, nameof(ApplicationStarting)),
|
||||
"应用程序正在启动"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, Exception?> _applicationStarted = LoggerMessage.Define(
|
||||
LogLevel.Information,
|
||||
new EventId(1001, nameof(ApplicationStarted)),
|
||||
"应用程序启动完成"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, Exception?> _applicationShuttingDown =
|
||||
LoggerMessage.Define(
|
||||
LogLevel.Information,
|
||||
new EventId(1002, nameof(ApplicationShuttingDown)),
|
||||
"应用程序正在关闭"
|
||||
);
|
||||
|
||||
// 音乐库相关日志
|
||||
private static readonly Action<ILogger, string, Exception?> _savingLibraryData =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(2000, nameof(SavingLibraryData)),
|
||||
"正在保存音乐库数据到: {Path}"
|
||||
);
|
||||
|
||||
private static readonly Action<
|
||||
ILogger,
|
||||
string,
|
||||
double,
|
||||
int,
|
||||
int,
|
||||
Exception?
|
||||
> _libraryDataSaved = LoggerMessage.Define<string, double, int, int>(
|
||||
LogLevel.Information,
|
||||
new EventId(2001, nameof(LibraryDataSaved)),
|
||||
"音乐库数据已保存到: {Path}, 耗时: {ElapsedMs}ms, 歌曲数: {SongCount}, 专辑数: {AlbumCount}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, Exception?> _loadingLibraryData =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(2002, nameof(LoadingLibraryData)),
|
||||
"正在加载音乐库数据从: {Path}"
|
||||
);
|
||||
|
||||
private static readonly Action<
|
||||
ILogger,
|
||||
string,
|
||||
double,
|
||||
int,
|
||||
int,
|
||||
Exception?
|
||||
> _libraryDataLoaded = LoggerMessage.Define<string, double, int, int>(
|
||||
LogLevel.Information,
|
||||
new EventId(2003, nameof(LibraryDataLoaded)),
|
||||
"音乐库数据已加载从: {Path}, 耗时: {ElapsedMs}ms, 歌曲数: {SongCount}, 专辑数: {AlbumCount}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, int, double, Exception?> _libraryScanning =
|
||||
LoggerMessage.Define<int, double>(
|
||||
LogLevel.Debug,
|
||||
new EventId(2004, nameof(LibraryScanning)),
|
||||
"正在扫描音乐库: 已处理 {ProcessedCount} 个文件, 进度: {ProgressPercent}%"
|
||||
);
|
||||
|
||||
// 编辑歌曲信息相关日志
|
||||
private static readonly Action<ILogger, string, Exception?> _editingSongInfoIO =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
new EventId(2500, nameof(EditingSongInfoIO)),
|
||||
"Error_EditingSongInfoIO".GetLocalized()
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, Exception?> _editingSongInfoOther =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
new EventId(2501, nameof(EditingSongInfoOther)),
|
||||
"Error_EditingSongInfoOther".GetLocalized()
|
||||
);
|
||||
|
||||
// 播放器相关日志
|
||||
private static readonly Action<ILogger, string, string, Exception?> _songStartedPlaying =
|
||||
LoggerMessage.Define<string, string>(
|
||||
LogLevel.Information,
|
||||
new EventId(3000, nameof(SongStartedPlaying)),
|
||||
"开始播放歌曲: {Title} - {Artist}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, Exception?> _songPlaybackError =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
new EventId(3001, nameof(SongPlaybackError)),
|
||||
"Error_SongPlayback".GetLocalized()
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, long, Exception?> _songPlaybackPosition =
|
||||
LoggerMessage.Define<string, long>(
|
||||
LogLevel.Debug,
|
||||
new EventId(3002, nameof(SongPlaybackPosition)),
|
||||
"歌曲播放位置更新: {Title}, 位置: {PositionMs}ms"
|
||||
);
|
||||
|
||||
// 下载相关日志
|
||||
private static readonly Action<ILogger, string, string, Exception?> _downloadStarted =
|
||||
LoggerMessage.Define<string, string>(
|
||||
LogLevel.Information,
|
||||
new EventId(4000, nameof(DownloadStarted)),
|
||||
"开始下载: {Title}, URL: {Url}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, double, long, Exception?> _downloadCompleted =
|
||||
LoggerMessage.Define<string, double, long>(
|
||||
LogLevel.Information,
|
||||
new EventId(4001, nameof(DownloadCompleted)),
|
||||
"下载完成: {Title}, 耗时: {ElapsedMs}ms, 文件大小: {FileSizeBytes} 字节"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception?> _downloadFailed =
|
||||
LoggerMessage.Define<string, string>(
|
||||
LogLevel.Error,
|
||||
new EventId(4002, nameof(DownloadFailed)),
|
||||
"下载失败: {Title}, 错误: {Error}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, int, Exception?> _downloadProgress =
|
||||
LoggerMessage.Define<string, int>(
|
||||
LogLevel.Debug,
|
||||
new EventId(4003, nameof(DownloadProgress)),
|
||||
"下载进度: {Title}, 进度: {ProgressPercent}%"
|
||||
);
|
||||
|
||||
// 用户界面相关日志
|
||||
private static readonly Action<ILogger, string, Exception?> _navigationOccurred =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(5000, nameof(NavigationOccurred)),
|
||||
"页面导航: {PageName}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, Exception?> _userAction =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(5001, nameof(UserAction)),
|
||||
"用户操作: {ActionName}"
|
||||
);
|
||||
|
||||
// 性能相关日志
|
||||
private static readonly Action<ILogger, string, double, Exception?> _performanceMetric =
|
||||
LoggerMessage.Define<string, double>(
|
||||
LogLevel.Information,
|
||||
new EventId(6000, nameof(PerformanceMetric)),
|
||||
"性能指标: {MetricName}, 值: {Value}ms"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, long, int, Exception?> _memoryUsage =
|
||||
LoggerMessage.Define<long, int>(
|
||||
LogLevel.Information,
|
||||
new EventId(6001, nameof(MemoryUsage)),
|
||||
"内存使用情况: {MemoryBytes} 字节, GC代数: {Generation}"
|
||||
);
|
||||
|
||||
// 错误和异常日志
|
||||
private static readonly Action<ILogger, string, Exception?> _unexpectedException =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
new EventId(9000, nameof(UnexpectedException)),
|
||||
"未处理的异常: {Message}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception?> _operationFailed =
|
||||
LoggerMessage.Define<string, string>(
|
||||
LogLevel.Error,
|
||||
new EventId(9001, nameof(OperationFailed)),
|
||||
"操作失败: {OperationName}, 错误: {Error}"
|
||||
);
|
||||
|
||||
private static readonly Action<ILogger, string, Exception?> _criticalError =
|
||||
LoggerMessage.Define<string>(
|
||||
LogLevel.Critical,
|
||||
new EventId(9999, nameof(CriticalError)),
|
||||
"严重错误: {Message}"
|
||||
);
|
||||
|
||||
// 应用程序生命周期方法
|
||||
public static void ApplicationStarting(this ILogger logger) =>
|
||||
_applicationStarting(logger, null);
|
||||
|
||||
public static void ApplicationStarted(this ILogger logger) => _applicationStarted(logger, null);
|
||||
|
||||
public static void ApplicationShuttingDown(this ILogger logger) =>
|
||||
_applicationShuttingDown(logger, null);
|
||||
|
||||
// 音乐库相关方法
|
||||
public static void SavingLibraryData(this ILogger logger, string path) =>
|
||||
_savingLibraryData(logger, path, null);
|
||||
|
||||
public static void LibraryDataSaved(
|
||||
this ILogger logger,
|
||||
string path,
|
||||
double elapsedMs,
|
||||
int songCount,
|
||||
int albumCount
|
||||
) => _libraryDataSaved(logger, path, elapsedMs, songCount, albumCount, null);
|
||||
|
||||
public static void LoadingLibraryData(this ILogger logger, string path) =>
|
||||
_loadingLibraryData(logger, path, null);
|
||||
|
||||
public static void LibraryDataLoaded(
|
||||
this ILogger logger,
|
||||
string path,
|
||||
double elapsedMs,
|
||||
int songCount,
|
||||
int albumCount
|
||||
) => _libraryDataLoaded(logger, path, elapsedMs, songCount, albumCount, null);
|
||||
|
||||
public static void LibraryScanning(
|
||||
this ILogger logger,
|
||||
int processedCount,
|
||||
double progressPercent
|
||||
) => _libraryScanning(logger, processedCount, progressPercent, null);
|
||||
|
||||
// 编辑歌曲信息相关方法
|
||||
public static void EditingSongInfoIO(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
Exception? exception = null
|
||||
) => _editingSongInfoIO(logger, title, exception);
|
||||
|
||||
public static void EditingSongInfoOther(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
Exception? exception = null
|
||||
) => _editingSongInfoOther(logger, title, exception);
|
||||
|
||||
// 播放器相关方法
|
||||
public static void SongStartedPlaying(this ILogger logger, string title, string artist) =>
|
||||
_songStartedPlaying(logger, title, artist, null);
|
||||
|
||||
public static void SongPlaybackError(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
Exception? exception = null
|
||||
) => _songPlaybackError(logger, title, exception);
|
||||
|
||||
public static void SongPlaybackPosition(this ILogger logger, string title, long positionMs) =>
|
||||
_songPlaybackPosition(logger, title, positionMs, null);
|
||||
|
||||
// 下载相关方法
|
||||
public static void DownloadStarted(this ILogger logger, string title, string url) =>
|
||||
_downloadStarted(logger, title, url, null);
|
||||
|
||||
public static void DownloadCompleted(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
double elapsedMs,
|
||||
long fileSizeBytes
|
||||
) => _downloadCompleted(logger, title, elapsedMs, fileSizeBytes, null);
|
||||
|
||||
public static void DownloadFailed(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => _downloadFailed(logger, title, error, exception);
|
||||
|
||||
public static void DownloadProgress(this ILogger logger, string title, int progressPercent) =>
|
||||
_downloadProgress(logger, title, progressPercent, null);
|
||||
|
||||
// 用户界面相关方法
|
||||
public static void NavigationOccurred(this ILogger logger, string pageName) =>
|
||||
_navigationOccurred(logger, pageName, null);
|
||||
|
||||
public static void UserAction(this ILogger logger, string actionName) =>
|
||||
_userAction(logger, actionName, null);
|
||||
|
||||
// 性能相关方法
|
||||
public static void PerformanceMetric(this ILogger logger, string metricName, double value) =>
|
||||
_performanceMetric(logger, metricName, value, null);
|
||||
|
||||
public static void MemoryUsage(this ILogger logger, long memoryBytes, int generation) =>
|
||||
_memoryUsage(logger, memoryBytes, generation, null);
|
||||
|
||||
// 错误和异常方法
|
||||
public static void UnexpectedException(
|
||||
this ILogger logger,
|
||||
string message,
|
||||
Exception exception
|
||||
) => _unexpectedException(logger, message, exception);
|
||||
|
||||
public static void OperationFailed(
|
||||
this ILogger logger,
|
||||
string operationName,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => _operationFailed(logger, operationName, error, exception);
|
||||
|
||||
public static void CriticalError(
|
||||
this ILogger logger,
|
||||
string message,
|
||||
Exception? exception = null
|
||||
) => _criticalError(logger, message, exception);
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog.Config;
|
||||
using NLog.Extensions.Logging;
|
||||
using NLog.Targets;
|
||||
using The_Untamed_Music_Player.Messages;
|
||||
using Windows.Storage;
|
||||
using ZLogger;
|
||||
|
||||
namespace The_Untamed_Music_Player.Services;
|
||||
|
||||
@@ -63,7 +61,6 @@ public static class LoggingService
|
||||
public static void Shutdown()
|
||||
{
|
||||
_loggerFactory?.Dispose();
|
||||
NLog.LogManager.Shutdown();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,62 +69,28 @@ public static class LoggingService
|
||||
private static ILoggerFactory CreateLoggerFactory()
|
||||
{
|
||||
var logFolder = GetLogFolderPath();
|
||||
var logFilePath = Path.Combine(logFolder, "app-${shortdate}.log");
|
||||
|
||||
// 确保日志文件夹存在
|
||||
Directory.CreateDirectory(logFolder);
|
||||
|
||||
// 配置 NLog
|
||||
var config = new LoggingConfiguration();
|
||||
|
||||
var fileTarget = new FileTarget("fileTarget")
|
||||
{
|
||||
FileName = logFilePath,
|
||||
Layout =
|
||||
"${longdate} [${level:uppercase=true:padding=5}] ${logger:shortName=true} ${message} ${exception:format=tostring}",
|
||||
MaxArchiveFiles = 7, // 保留7天的日志
|
||||
ArchiveEvery = FileArchivePeriod.Day,
|
||||
ArchiveFileName = Path.Combine(logFolder, "app-{#}.log"), // 归档模式
|
||||
ArchiveSuffixFormat = "{0:yyyyMMdd}",
|
||||
KeepFileOpen = true,
|
||||
AutoFlush = false, // 提高性能
|
||||
OpenFileFlushTimeout = 5, // 5秒自动刷新
|
||||
CreateDirs = true,
|
||||
BufferSize = 32768, // 32KB缓冲区
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
// 调试输出目标
|
||||
var debugTarget = new DebugTarget("debugTarget")
|
||||
{
|
||||
Layout =
|
||||
"${time} [${level:uppercase=true:padding=5}] ${logger:shortName=true} ${message}",
|
||||
};
|
||||
config.AddTarget(debugTarget);
|
||||
config.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, debugTarget);
|
||||
#endif
|
||||
|
||||
config.AddTarget(fileTarget);
|
||||
|
||||
// 所有级别写入文件
|
||||
config.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, fileTarget);
|
||||
|
||||
NLog.LogManager.Configuration = config;
|
||||
|
||||
// 创建自定义日志提供程序
|
||||
// 创建日志工厂
|
||||
var loggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddNLog();
|
||||
|
||||
#if DEBUG
|
||||
builder.SetMinimumLevel(LogLevel.Debug);
|
||||
#else
|
||||
builder.SetMinimumLevel(LogLevel.Information);
|
||||
#endif
|
||||
});
|
||||
|
||||
// 添加Messenger日志提供程序
|
||||
loggerFactory.AddProvider(new MessengerLoggerProvider());
|
||||
// 添加文件日志提供程序
|
||||
var logFilePath = Path.Combine(logFolder, $"app-{DateTime.Now:yyyyMMdd}.log");
|
||||
builder.AddZLoggerFile(logFilePath);
|
||||
|
||||
// 添加Messenger日志提供程序(用于InfoBar显示)
|
||||
builder.AddProvider(new MessengerLoggerProvider());
|
||||
});
|
||||
|
||||
return loggerFactory;
|
||||
}
|
||||
@@ -184,7 +147,7 @@ public static class LoggingService
|
||||
}
|
||||
|
||||
var cutoffDate = DateTime.Now.AddDays(-7);
|
||||
var logFiles = Directory.GetFiles(logFolder, "*.log*");
|
||||
var logFiles = Directory.GetFiles(logFolder, "app-*.log");
|
||||
|
||||
foreach (var file in logFiles)
|
||||
{
|
||||
|
||||
336
The Untamed Music Player/Services/ZLoggerExtensions.cs
Normal file
336
The Untamed Music Player/Services/ZLoggerExtensions.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using The_Untamed_Music_Player.Helpers;
|
||||
using ZLogger;
|
||||
|
||||
namespace The_Untamed_Music_Player.Services;
|
||||
|
||||
/// <summary>
|
||||
/// ZLogger高性能日志扩展方法
|
||||
/// 使用ZLogger的零分配特性和结构化日志
|
||||
/// </summary>
|
||||
public static class ZLoggerExtensions
|
||||
{
|
||||
// 应用程序生命周期日志
|
||||
public static void ApplicationStarting(this ILogger logger) =>
|
||||
logger.ZLogInformation($"应用程序正在启动");
|
||||
|
||||
public static void ApplicationStarted(this ILogger logger) =>
|
||||
logger.ZLogInformation($"应用程序启动完成");
|
||||
|
||||
public static void ApplicationShuttingDown(this ILogger logger) =>
|
||||
logger.ZLogInformation($"应用程序正在关闭");
|
||||
|
||||
// 音乐库相关日志
|
||||
public static void SavingLibraryData(this ILogger logger, string path) =>
|
||||
logger.ZLogInformation($"正在保存音乐库数据到: {path}");
|
||||
|
||||
public static void LibraryDataSaved(
|
||||
this ILogger logger,
|
||||
string path,
|
||||
double elapsedMs,
|
||||
int songCount,
|
||||
int albumCount
|
||||
) =>
|
||||
logger.ZLogInformation(
|
||||
$"音乐库数据已保存到: {path}, 耗时: {elapsedMs}ms, 歌曲数: {songCount}, 专辑数: {albumCount}"
|
||||
);
|
||||
|
||||
public static void LoadingLibraryData(this ILogger logger, string path) =>
|
||||
logger.ZLogInformation($"正在加载音乐库数据从: {path}");
|
||||
|
||||
public static void LibraryDataLoaded(
|
||||
this ILogger logger,
|
||||
string path,
|
||||
double elapsedMs,
|
||||
int songCount,
|
||||
int albumCount
|
||||
) =>
|
||||
logger.ZLogInformation(
|
||||
$"音乐库数据已加载从: {path}, 耗时: {elapsedMs}ms, 歌曲数: {songCount}, 专辑数: {albumCount}"
|
||||
);
|
||||
|
||||
public static void LibraryScanning(
|
||||
this ILogger logger,
|
||||
int processedCount,
|
||||
double progressPercent
|
||||
) =>
|
||||
logger.ZLogDebug(
|
||||
$"正在扫描音乐库: 已处理 {processedCount} 个文件, 进度: {progressPercent}%"
|
||||
);
|
||||
|
||||
// 播放器相关日志
|
||||
public static void SongStartedPlaying(this ILogger logger, string title, string artist) =>
|
||||
logger.ZLogInformation($"开始播放歌曲: {title} - {artist}");
|
||||
|
||||
public static void SongPlaybackError(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
Exception? exception = null
|
||||
) =>
|
||||
logger.ZLogError(
|
||||
exception,
|
||||
$"{"Error_SongPlayback".GetLocalizedWithReplace("{title}", title)}"
|
||||
);
|
||||
|
||||
public static void SongPlaybackPosition(this ILogger logger, string title, long positionMs) =>
|
||||
logger.ZLogTrace($"歌曲播放位置更新: {title}, 位置: {positionMs}ms");
|
||||
|
||||
// 下载相关日志
|
||||
public static void DownloadStarted(this ILogger logger, string title, string url) =>
|
||||
logger.ZLogInformation($"开始下载: {title}, URL: {url}");
|
||||
|
||||
public static void DownloadCompleted(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
double elapsedMs,
|
||||
long fileSizeBytes
|
||||
) =>
|
||||
logger.ZLogInformation(
|
||||
$"下载完成: {title}, 耗时: {elapsedMs}ms, 文件大小: {fileSizeBytes} 字节"
|
||||
);
|
||||
|
||||
public static void DownloadFailed(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => logger.ZLogError(exception, $"下载失败: {title}, 错误: {error}");
|
||||
|
||||
public static void DownloadProgress(this ILogger logger, string title, int progressPercent) =>
|
||||
logger.ZLogTrace($"下载进度: {title}, 进度: {progressPercent}%");
|
||||
|
||||
// 用户界面相关日志
|
||||
public static void NavigationOccurred(this ILogger logger, string pageName) =>
|
||||
logger.ZLogDebug($"页面导航: {pageName}");
|
||||
|
||||
public static void UserAction(this ILogger logger, string actionName) =>
|
||||
logger.ZLogDebug($"用户操作: {actionName}");
|
||||
|
||||
// 性能相关日志
|
||||
public static void PerformanceMetric(this ILogger logger, string metricName, double value) =>
|
||||
logger.ZLogInformation($"性能指标: {metricName}, 值: {value}ms");
|
||||
|
||||
public static void MemoryUsage(this ILogger logger, long memoryBytes, int generation) =>
|
||||
logger.ZLogInformation($"内存使用情况: {memoryBytes} 字节, GC代数: {generation}");
|
||||
|
||||
// 错误和异常日志
|
||||
public static void UnexpectedException(
|
||||
this ILogger logger,
|
||||
string message,
|
||||
Exception exception
|
||||
) => logger.ZLogError(exception, $"未处理的异常: {message}");
|
||||
|
||||
public static void OperationFailed(
|
||||
this ILogger logger,
|
||||
string operationName,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => logger.ZLogError(exception, $"操作失败: {operationName}, 错误: {error}");
|
||||
|
||||
public static void CriticalError(
|
||||
this ILogger logger,
|
||||
string message,
|
||||
Exception? exception = null
|
||||
) => logger.ZLogCritical(exception, $"严重错误: {message}");
|
||||
|
||||
// 歌曲信息编辑日志
|
||||
public static void EditingSongInfoIO(this ILogger logger, string title) =>
|
||||
logger.ZLogError($"{"Error_EditingSongInfoIO".GetLocalizedWithReplace("{title}", title)}");
|
||||
|
||||
public static void EditingSongInfoOther(
|
||||
this ILogger logger,
|
||||
string title,
|
||||
Exception exception
|
||||
) =>
|
||||
logger.ZLogError(
|
||||
exception,
|
||||
$"{"Error_EditingSongInfoOther".GetLocalizedWithReplace("{title}", title)}"
|
||||
);
|
||||
|
||||
// 高性能性能监控日志
|
||||
public static void LogPerformanceStart(
|
||||
this ILogger logger,
|
||||
string operationName,
|
||||
int operationId
|
||||
) => logger.ZLogDebug($"[{operationId:X8}] 开始执行: {operationName}");
|
||||
|
||||
public static void LogPerformanceEnd(
|
||||
this ILogger logger,
|
||||
string operationName,
|
||||
int operationId,
|
||||
double elapsedMs
|
||||
) =>
|
||||
logger.ZLogInformation(
|
||||
$"[{operationId:X8}] 完成执行: {operationName}, 耗时: {elapsedMs}ms"
|
||||
);
|
||||
|
||||
public static void LogPerformanceEnd(
|
||||
this ILogger logger,
|
||||
string operationName,
|
||||
int operationId,
|
||||
TimeSpan elapsed
|
||||
) =>
|
||||
logger.ZLogInformation(
|
||||
$"[{operationId:X8}] 完成执行: {operationName}, 耗时: {elapsed.TotalMilliseconds}ms"
|
||||
);
|
||||
|
||||
// 音乐播放专用高性能日志
|
||||
public static void PlaybackStateChanged(
|
||||
this ILogger logger,
|
||||
string songTitle,
|
||||
string newState
|
||||
) => logger.ZLogDebug($"播放状态变更: {songTitle} -> {newState}");
|
||||
|
||||
public static void AudioStreamCreated(
|
||||
this ILogger logger,
|
||||
string filePath,
|
||||
int streamHandle,
|
||||
double durationSeconds
|
||||
) =>
|
||||
logger.ZLogDebug(
|
||||
$"音频流已创建: {filePath}, 句柄: {streamHandle}, 时长: {durationSeconds}s"
|
||||
);
|
||||
|
||||
public static void AudioStreamReleased(this ILogger logger, int streamHandle) =>
|
||||
logger.ZLogDebug($"音频流已释放: 句柄 {streamHandle}");
|
||||
|
||||
public static void PlaybackBufferUnderrun(
|
||||
this ILogger logger,
|
||||
string songTitle,
|
||||
int bufferLevel
|
||||
) => logger.ZLogWarning($"播放缓冲区不足: {songTitle}, 缓冲区水平: {bufferLevel}%");
|
||||
|
||||
// 网络请求日志
|
||||
public static void HttpRequestStarted(
|
||||
this ILogger logger,
|
||||
string method,
|
||||
string url,
|
||||
int requestId
|
||||
) => logger.ZLogDebug($"[{requestId:X8}] HTTP请求开始: {method} {url}");
|
||||
|
||||
public static void HttpRequestCompleted(
|
||||
this ILogger logger,
|
||||
int requestId,
|
||||
int statusCode,
|
||||
double elapsedMs
|
||||
) =>
|
||||
logger.ZLogInformation($"[{requestId:X8}] HTTP请求完成: {statusCode}, 耗时: {elapsedMs}ms");
|
||||
|
||||
public static void HttpRequestFailed(
|
||||
this ILogger logger,
|
||||
int requestId,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => logger.ZLogError(exception, $"[{requestId:X8}] HTTP请求失败: {error}");
|
||||
|
||||
// 文件操作日志
|
||||
public static void FileOperationStarted(
|
||||
this ILogger logger,
|
||||
string operation,
|
||||
string filePath,
|
||||
int operationId
|
||||
) => logger.ZLogDebug($"[{operationId:X8}] 文件操作开始: {operation} -> {filePath}");
|
||||
|
||||
public static void FileOperationCompleted(
|
||||
this ILogger logger,
|
||||
int operationId,
|
||||
double elapsedMs,
|
||||
long? fileSize = null
|
||||
)
|
||||
{
|
||||
if (fileSize.HasValue)
|
||||
{
|
||||
logger.ZLogInformation(
|
||||
$"[{operationId:X8}] 文件操作完成, 耗时: {elapsedMs}ms, 大小: {fileSize} 字节"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.ZLogInformation($"[{operationId:X8}] 文件操作完成, 耗时: {elapsedMs}ms");
|
||||
}
|
||||
}
|
||||
|
||||
public static void FileOperationFailed(
|
||||
this ILogger logger,
|
||||
int operationId,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => logger.ZLogError(exception, $"[{operationId:X8}] 文件操作失败: {error}");
|
||||
|
||||
// 数据库操作日志
|
||||
public static void DatabaseOperationStarted(
|
||||
this ILogger logger,
|
||||
string operation,
|
||||
int operationId
|
||||
) => logger.ZLogDebug($"[{operationId:X8}] 数据库操作开始: {operation}");
|
||||
|
||||
public static void DatabaseOperationCompleted(
|
||||
this ILogger logger,
|
||||
int operationId,
|
||||
double elapsedMs,
|
||||
int? affectedRows = null
|
||||
)
|
||||
{
|
||||
if (affectedRows.HasValue)
|
||||
{
|
||||
logger.ZLogInformation(
|
||||
$"[{operationId:X8}] 数据库操作完成, 耗时: {elapsedMs}ms, 影响行数: {affectedRows}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.ZLogInformation($"[{operationId:X8}] 数据库操作完成, 耗时: {elapsedMs}ms");
|
||||
}
|
||||
}
|
||||
|
||||
public static void DatabaseOperationFailed(
|
||||
this ILogger logger,
|
||||
int operationId,
|
||||
string error,
|
||||
Exception? exception = null
|
||||
) => logger.ZLogError(exception, $"[{operationId:X8}] 数据库操作失败: {error}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高性能性能监控辅助类
|
||||
/// 用于自动记录操作的开始和结束时间
|
||||
/// </summary>
|
||||
public readonly struct PerformanceScope : IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _operationName;
|
||||
private readonly int _operationId;
|
||||
private readonly Stopwatch _stopwatch;
|
||||
|
||||
public PerformanceScope(ILogger logger, string operationName)
|
||||
{
|
||||
_logger = logger;
|
||||
_operationName = operationName;
|
||||
_operationId = Random.Shared.Next();
|
||||
_stopwatch = Stopwatch.StartNew();
|
||||
|
||||
logger.LogPerformanceStart(operationName, _operationId);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
_logger.LogPerformanceEnd(_operationName, _operationId, _stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高性能日志辅助类
|
||||
/// </summary>
|
||||
public static class PerformanceLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建性能监控作用域
|
||||
/// </summary>
|
||||
/// <param name="logger">日志记录器</param>
|
||||
/// <param name="operationName">操作名称</param>
|
||||
/// <returns>性能监控作用域</returns>
|
||||
public static PerformanceScope BeginScope(ILogger logger, string operationName) =>
|
||||
new(logger, operationName);
|
||||
}
|
||||
@@ -471,6 +471,9 @@
|
||||
<data name="Artists_SortBy" xml:space="preserve">
|
||||
<value>A - Z, Z - A</value>
|
||||
</data>
|
||||
<data name="Playlists_SortBy" xml:space="preserve">
|
||||
<value>A - Z, Z - A, Date modified (Asc), Date modified (Desc)</value>
|
||||
</data>
|
||||
<data name="Settings_FallBack.Header" xml:space="preserve">
|
||||
<value>Background stops updating after window loses focus</value>
|
||||
</data>
|
||||
@@ -712,10 +715,10 @@
|
||||
<value>Send feedback</value>
|
||||
</data>
|
||||
<data name="Error_SongPlayback" xml:space="preserve">
|
||||
<value>We can't open {Title}. This may be because the file type is unsupported, the file extension is incorrect, the file is corrupted, or the network is abnormal.</value>
|
||||
<value>We can't open {title}. This may be because the file type is unsupported, the file extension is incorrect, the file is corrupted, or the network is abnormal.</value>
|
||||
</data>
|
||||
<data name="Error_EditingSongInfoIO" xml:space="preserve">
|
||||
<value>We can't edit {Title}. This may be because the current file is playing, being occupied by another process, or the file type is unsupported.</value>
|
||||
<value>We can't edit {title}. This may be because the current file is playing, being occupied by another process, or the file type is unsupported.</value>
|
||||
</data>
|
||||
<data name="Settings_DeveloperOptions.Text" xml:space="preserve">
|
||||
<value>Developer options</value>
|
||||
@@ -727,6 +730,21 @@
|
||||
<value>Open folder</value>
|
||||
</data>
|
||||
<data name="Error_EditingSongInfoOther" xml:space="preserve">
|
||||
<value>We can't edit some of the properties of {Title}. This may be because the file type is unsupported, or the file is corrupted.</value>
|
||||
<value>We can't edit some of the properties of {title}. This may be because the file type is unsupported, or the file is corrupted.</value>
|
||||
</data>
|
||||
<data name="NoPlaylist_PlaylistDonotHave.Text" xml:space="preserve">
|
||||
<value>You don't have any playlists</value>
|
||||
</data>
|
||||
<data name="PlayLists_CreateNewPlaylist.Text" xml:space="preserve">
|
||||
<value>Create a new playlist</value>
|
||||
</data>
|
||||
<data name="PlayLists_NewPlaylist.Text" xml:space="preserve">
|
||||
<value>New playlist</value>
|
||||
</data>
|
||||
<data name="PlayLists_CreatePlaylist.Content" xml:space="preserve">
|
||||
<value>Create playlist</value>
|
||||
</data>
|
||||
<data name="PlaylistInfo_UntitledPlaylist.SelectedText" xml:space="preserve">
|
||||
<value>Untitled playlist</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -471,6 +471,9 @@
|
||||
<data name="Artists_SortBy" xml:space="preserve">
|
||||
<value>A-Z, Z-A</value>
|
||||
</data>
|
||||
<data name="Playlists_SortBy" xml:space="preserve">
|
||||
<value>A-Z, Z-A, 修改日期 (升序), 修改日期 (降序)</value>
|
||||
</data>
|
||||
<data name="Settings_FallBack.Header" xml:space="preserve">
|
||||
<value>窗口失去焦点后背景停止更新</value>
|
||||
</data>
|
||||
@@ -712,10 +715,10 @@
|
||||
<value>发送反馈</value>
|
||||
</data>
|
||||
<data name="Error_SongPlayback" xml:space="preserve">
|
||||
<value>我们无法打开 {Title}。这可能是因为文件类型不受支持、文件扩展名不正确、文件已损坏或网络异常。</value>
|
||||
<value>我们无法打开 {title}。这可能是因为文件类型不受支持、文件扩展名不正确、文件已损坏或网络异常。</value>
|
||||
</data>
|
||||
<data name="Error_EditingSongInfoIO" xml:space="preserve">
|
||||
<value>我们无法编辑 {Title}。这可能是因为当前文件正在播放、被其他进程占用或文件类型不受支持。</value>
|
||||
<value>我们无法编辑 {title}。这可能是因为当前文件正在播放、被其他进程占用或文件类型不受支持。</value>
|
||||
</data>
|
||||
<data name="Settings_DeveloperOptions.Text" xml:space="preserve">
|
||||
<value>开发者选项</value>
|
||||
@@ -727,6 +730,21 @@
|
||||
<value>打开文件夹</value>
|
||||
</data>
|
||||
<data name="Error_EditingSongInfoOther" xml:space="preserve">
|
||||
<value>我们无法编辑 {Title} 的部分属性。这可能是因为文件类型不受支持或文件已损坏。</value>
|
||||
<value>我们无法编辑 {title} 的部分属性。这可能是因为文件类型不受支持或文件已损坏。</value>
|
||||
</data>
|
||||
<data name="NoPlaylist_PlaylistDonotHave.Text" xml:space="preserve">
|
||||
<value>你没有任何播放列表</value>
|
||||
</data>
|
||||
<data name="PlayLists_CreateNewPlaylist.Text" xml:space="preserve">
|
||||
<value>创建一个新的播放列表</value>
|
||||
</data>
|
||||
<data name="PlayLists_NewPlaylist.Text" xml:space="preserve">
|
||||
<value>新建播放列表</value>
|
||||
</data>
|
||||
<data name="PlayLists_CreatePlaylist.Content" xml:space="preserve">
|
||||
<value>创建播放列表</value>
|
||||
</data>
|
||||
<data name="PlaylistInfo_UntitledPlaylist.SelectedText" xml:space="preserve">
|
||||
<value>无标题播放列表</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,195 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using The_Untamed_Music_Player.Services;
|
||||
|
||||
namespace The_Untamed_Music_Player.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 日志系统测试和演示类
|
||||
/// </summary>
|
||||
public static class LoggingSystemTests
|
||||
{
|
||||
private static readonly ILogger _logger = LoggingService.CreateLogger("LoggingTests");
|
||||
|
||||
/// <summary>
|
||||
/// 测试和演示日志系统的各种功能
|
||||
/// </summary>
|
||||
public static async Task RunAllTestsAsync()
|
||||
{
|
||||
_logger.LogInformation("开始执行日志系统测试");
|
||||
|
||||
// 测试基本日志级别
|
||||
TestBasicLogging();
|
||||
|
||||
// 测试结构化日志
|
||||
TestStructuredLogging();
|
||||
|
||||
// 测试异常处理日志
|
||||
TestExceptionLogging();
|
||||
|
||||
// 测试高性能日志记录
|
||||
TestHighPerformanceLogging();
|
||||
|
||||
// 测试性能监控
|
||||
await TestPerformanceMonitoringAsync();
|
||||
|
||||
// 测试条件日志记录
|
||||
TestConditionalLogging();
|
||||
|
||||
// 测试InfoBar显示(这会在UI中显示)
|
||||
TestInfoBarLogging();
|
||||
|
||||
_logger.LogInformation("日志系统测试完成");
|
||||
}
|
||||
|
||||
private static void TestBasicLogging()
|
||||
{
|
||||
_logger.LogDebug("这是调试信息 - 只在Debug模式下显示");
|
||||
_logger.LogInformation("这是普通信息日志");
|
||||
_logger.LogWarning("这是警告信息");
|
||||
|
||||
// 注意:以下两条日志会在InfoBar中显示
|
||||
// _logger.LogError("这是错误信息 - 会在InfoBar中显示");
|
||||
// _logger.LogCritical("这是严重错误 - 会在InfoBar中显示");
|
||||
}
|
||||
|
||||
private static void TestStructuredLogging()
|
||||
{
|
||||
var userId = 12345;
|
||||
var userName = "张三";
|
||||
var songTitle = "测试歌曲";
|
||||
var playTime = TimeSpan.FromMinutes(3.5);
|
||||
|
||||
_logger.LogInformation(
|
||||
"用户 {UserId} ({UserName}) 播放了歌曲 {SongTitle},时长 {Duration}",
|
||||
userId,
|
||||
userName,
|
||||
songTitle,
|
||||
playTime
|
||||
);
|
||||
|
||||
var sessionInfo = new
|
||||
{
|
||||
SessionId = Guid.NewGuid(),
|
||||
StartTime = DateTime.Now,
|
||||
UserAgent = "The Untamed Music Player/1.0",
|
||||
};
|
||||
|
||||
_logger.LogInformation("用户会话信息: {@SessionInfo}", sessionInfo);
|
||||
}
|
||||
|
||||
private static void TestExceptionLogging()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 模拟一个异常
|
||||
throw new InvalidOperationException("这是一个测试异常");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "捕获到测试异常,操作参数: {Parameter}", "test_value");
|
||||
|
||||
// 使用高性能日志记录器
|
||||
_logger.UnexpectedException("测试异常处理", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestHighPerformanceLogging()
|
||||
{
|
||||
// 使用预编译的高性能日志模板
|
||||
_logger.SongStartedPlaying("测试歌曲", "测试艺术家");
|
||||
_logger.DownloadProgress("测试下载", 75);
|
||||
_logger.PerformanceMetric("测试操作", 1250.5);
|
||||
_logger.MemoryUsage(1024 * 1024 * 50, 2); // 50MB, Gen 2
|
||||
}
|
||||
|
||||
private static async Task TestPerformanceMonitoringAsync()
|
||||
{
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
var operationName = "性能测试操作";
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("开始执行 {OperationName}", operationName);
|
||||
|
||||
// 模拟一些工作
|
||||
await Task.Delay(500);
|
||||
|
||||
stopwatch.Stop();
|
||||
_logger.PerformanceMetric(operationName, stopwatch.Elapsed.TotalMilliseconds);
|
||||
|
||||
_logger.LogInformation(
|
||||
"{OperationName} 执行完成,耗时: {ElapsedMs}ms",
|
||||
operationName,
|
||||
stopwatch.Elapsed.TotalMilliseconds
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_logger.OperationFailed(operationName, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestConditionalLogging()
|
||||
{
|
||||
// 高效的条件日志记录
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var expensiveDebugInfo =
|
||||
$"调试信息 - 时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}, 线程: {Environment.CurrentManagedThreadId}";
|
||||
_logger.LogDebug("详细调试信息: {DebugInfo}", expensiveDebugInfo);
|
||||
}
|
||||
|
||||
// 这个在Release版本中不会执行
|
||||
_logger.LogDebug("这条调试信息在Release版本中不会被记录");
|
||||
}
|
||||
|
||||
private static void TestInfoBarLogging()
|
||||
{
|
||||
_logger.LogInformation("准备测试InfoBar显示功能...");
|
||||
|
||||
// 等待一下再显示错误,这样可以看到准备信息
|
||||
Task.Delay(2000)
|
||||
.ContinueWith(_ =>
|
||||
{
|
||||
// 这条错误日志会在MainWindow的InfoBar中显示
|
||||
_logger.LogError("这是一个测试错误消息,应该会在InfoBar中显示5秒钟");
|
||||
});
|
||||
|
||||
Task.Delay(4000)
|
||||
.ContinueWith(_ =>
|
||||
{
|
||||
// 再显示一条严重错误
|
||||
_logger.LogCritical("这是一个严重错误测试,也会在InfoBar中显示");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在应用启动时调用此方法来演示日志系统
|
||||
/// </summary>
|
||||
public static void DemonstrateLoggingOnStartup()
|
||||
{
|
||||
_logger.LogInformation("日志系统演示:应用程序已启动");
|
||||
_logger.LogDebug("调试模式信息:当前时间 {CurrentTime}", DateTime.Now);
|
||||
|
||||
// 记录系统信息
|
||||
_logger.LogInformation(
|
||||
"系统信息 - OS: {OS}, .NET版本: {DotNetVersion}, 工作目录: {WorkingDirectory}",
|
||||
Environment.OSVersion,
|
||||
Environment.Version,
|
||||
Environment.CurrentDirectory
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在应用关闭时调用此方法
|
||||
/// </summary>
|
||||
public static void DemonstrateLoggingOnShutdown()
|
||||
{
|
||||
_logger.LogInformation("日志系统演示:应用程序正在关闭");
|
||||
|
||||
// 记录一些关闭统计信息
|
||||
var uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime;
|
||||
_logger.LogInformation("应用程序运行时间: {Uptime}", uptime);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
||||
@@ -42,9 +42,6 @@
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="NLog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Platform)' == 'x86'">
|
||||
<None Include="Libraries\x86\**\*">
|
||||
@@ -68,10 +65,16 @@
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.250206-build.2040" />
|
||||
<PackageReference
|
||||
Include="CommunityToolkit.Labs.WinUI.MarqueeText"
|
||||
Version="0.1.250206-build.2040"
|
||||
/>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference
|
||||
Include="CommunityToolkit.WinUI.Controls.SettingsControls"
|
||||
Version="8.2.250402"
|
||||
/>
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="hyjiacan.pinyin4net" Version="4.1.1" />
|
||||
@@ -80,14 +83,12 @@
|
||||
<PackageReference Include="ManagedBass.Wasapi" Version="3.1.1" />
|
||||
<PackageReference Include="MemoryPack" Version="1.21.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="6.0.3" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="ZLinq" Version="1.5.2" />
|
||||
<PackageReference Include="ZLogger" Version="2.5.10" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
|
||||
@@ -306,10 +306,7 @@ public partial class LocalAlbumsViewModel : ObservableRecipient
|
||||
|
||||
public void SortByListView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView)
|
||||
{
|
||||
listView.SelectedIndex = SortMode;
|
||||
}
|
||||
(sender as ListView)!.SelectedIndex = SortMode;
|
||||
}
|
||||
|
||||
public async void GenreListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
@@ -331,10 +328,7 @@ public partial class LocalAlbumsViewModel : ObservableRecipient
|
||||
|
||||
public void GenreListView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView)
|
||||
{
|
||||
listView.SelectedIndex = GenreMode;
|
||||
}
|
||||
(sender as ListView)!.SelectedIndex = GenreMode;
|
||||
}
|
||||
|
||||
public void PlayButton_Click(LocalAlbumInfo info)
|
||||
|
||||
@@ -71,10 +71,7 @@ public partial class LocalArtistsViewModel : ObservableRecipient
|
||||
|
||||
public void SortByListView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView)
|
||||
{
|
||||
listView.SelectedIndex = SortMode;
|
||||
}
|
||||
(sender as ListView)!.SelectedIndex = SortMode;
|
||||
}
|
||||
|
||||
public async Task SortArtists()
|
||||
|
||||
@@ -464,10 +464,7 @@ public partial class LocalSongsViewModel : ObservableRecipient
|
||||
|
||||
public void SortByListView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView)
|
||||
{
|
||||
listView.SelectedIndex = SortMode;
|
||||
}
|
||||
(sender as ListView)!.SelectedIndex = SortMode;
|
||||
}
|
||||
|
||||
public async void GenreListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
@@ -490,10 +487,7 @@ public partial class LocalSongsViewModel : ObservableRecipient
|
||||
|
||||
public void GenreListView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListView listView)
|
||||
{
|
||||
listView.SelectedIndex = GenreMode;
|
||||
}
|
||||
(sender as ListView)!.SelectedIndex = GenreMode;
|
||||
}
|
||||
|
||||
public void ShuffledPlayAllButton_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -1,8 +1,69 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using The_Untamed_Music_Player.Contracts.Services;
|
||||
using The_Untamed_Music_Player.Helpers;
|
||||
|
||||
namespace The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
public partial class PlayListsViewModel : ObservableRecipient
|
||||
{
|
||||
public PlayListsViewModel() { }
|
||||
private readonly ILocalSettingsService _localSettingsService =
|
||||
App.GetService<ILocalSettingsService>();
|
||||
public List<string> SortBy { get; set; } = [.. "Playlists_SortBy".GetLocalized().Split(", ")];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial byte SortMode { get; set; } = 0;
|
||||
|
||||
partial void OnSortModeChanged(byte value)
|
||||
{
|
||||
SortByStr = SortBy[value];
|
||||
SaveSortModeAsync();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string SortByStr { get; set; } = "";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsProgressRingActive { get; set; } = true;
|
||||
|
||||
public PlayListsViewModel()
|
||||
{
|
||||
LoadModeAndPlayList();
|
||||
}
|
||||
|
||||
public async void LoadModeAndPlayList()
|
||||
{
|
||||
await LoadSortModeAsync();
|
||||
}
|
||||
|
||||
public void SortByListView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
(sender as ListView)!.SelectedIndex = SortMode;
|
||||
}
|
||||
|
||||
public void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var currentsortmode = SortMode;
|
||||
if (sender is ListView listView && listView.SelectedIndex is int selectedIndex)
|
||||
{
|
||||
SortMode = (byte)selectedIndex;
|
||||
if (SortMode != currentsortmode)
|
||||
{
|
||||
IsProgressRingActive = true;
|
||||
IsProgressRingActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadSortModeAsync()
|
||||
{
|
||||
SortMode = await _localSettingsService.ReadSettingAsync<byte>("PlaylistSortMode");
|
||||
SortByStr = SortBy[SortMode];
|
||||
}
|
||||
|
||||
public async void SaveSortModeAsync()
|
||||
{
|
||||
await _localSettingsService.SaveSettingAsync("PlaylistSortMode", SortMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
<AdaptiveTrigger MinWindowWidth="0"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,24,16,0"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,23,16,0"/>
|
||||
<Setter Target="AlbumGridView.Padding" Value="12,0,12,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
@@ -215,15 +215,13 @@
|
||||
<AdaptiveTrigger MinWindowWidth="641"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MenuGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="56,23,56,0"/>
|
||||
<Setter Target="AlbumGridView.Padding" Value="52,0,52,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid x:Name="MenuGrid"
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource NavigationViewPageContentMargin}">
|
||||
<Grid x:Name="MenuGrid" Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
<AdaptiveTrigger MinWindowWidth="0"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,24,16,0"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,23,16,0"/>
|
||||
<Setter Target="ArtistGridView.Padding" Value="12,0,12,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
@@ -188,15 +188,13 @@
|
||||
<AdaptiveTrigger MinWindowWidth="641"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MenuGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="56,23,56,0"/>
|
||||
<Setter Target="ArtistGridView.Padding" Value="52,0,52,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid x:Name="MenuGrid"
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource NavigationViewPageContentMargin}">
|
||||
<Grid x:Name="MenuGrid" Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:contract="using:The_Untamed_Music_Player.Contracts.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:The_Untamed_Music_Player.Models"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
@@ -178,7 +177,7 @@
|
||||
<AdaptiveTrigger MinWindowWidth="0"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,24,16,0"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,23,16,0"/>
|
||||
<Setter Target="SongListView.Padding" Value="12,0,12,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
@@ -187,7 +186,7 @@
|
||||
<AdaptiveTrigger MinWindowWidth="641"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MenuGrid.Margin" Value="{StaticResource NavigationViewPageContentMargin}"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="56,23,56,0"/>
|
||||
<Setter Target="SongListView.Padding" Value="52,0,52,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:contract="using:The_Untamed_Music_Player.Contracts.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:The_Untamed_Music_Player.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:The_Untamed_Music_Player.Models"
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
NavigationCacheMode="Enabled"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<DataTemplate x:Key="SortByListViewItemTemplate" x:DataType="x:String">
|
||||
<ListViewItem Margin="0,2,0,2">
|
||||
<TextBlock Text="{x:Bind}"/>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid x:Name="ContentArea">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -20,6 +28,11 @@
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitleGrid.Margin" Value="16,36,16,0"/>
|
||||
<Setter Target="Image.Height" Value="90"/>
|
||||
<Setter Target="Image.Width" Value="90"/>
|
||||
<Setter Target="StackPanel.Spacing" Value="11"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="16,23,16,0"/>
|
||||
<Setter Target="PlaylistGridView.Padding" Value="12,0,12,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Normal">
|
||||
@@ -28,6 +41,11 @@
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="TitleGrid.Margin" Value="56,36,56,0"/>
|
||||
<Setter Target="Image.Height" Value="150"/>
|
||||
<Setter Target="Image.Width" Value="150"/>
|
||||
<Setter Target="StackPanel.Spacing" Value="22"/>
|
||||
<Setter Target="MenuGrid.Margin" Value="56,23,56,0"/>
|
||||
<Setter Target="PlaylistGridView.Padding" Value="52,0,52,0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
@@ -35,6 +53,108 @@
|
||||
<Grid x:Name="TitleGrid" Grid.Row="0">
|
||||
<TextBlock x:Uid="Shell_PlayLists1" Style="{StaticResource TitleLargeTextBlockStyle}"/>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1" Padding="0,0,0,16"/>
|
||||
|
||||
<Grid x:Name="NoPlaylistControl"
|
||||
Grid.Row="1"
|
||||
Visibility="Collapsed">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<StackPanel x:Name="StackPanel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Image x:Name="Image"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="ms-appx:///Assets/PlaylistGradient.svg"/>
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
Orientation="Vertical" Spacing="8">
|
||||
<TextBlock x:Uid="NoPlaylist_PlaylistDonotHave" FontSize="29"/>
|
||||
<Button Style="{StaticResource AccentButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontFamily="{StaticResource UntamedFontFamily}"
|
||||
FontSize="12" Glyph=""/>
|
||||
<TextBlock x:Uid="PlayLists_CreateNewPlaylist"/>
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBox x:Uid="PlaylistInfo_UntitledPlaylist"
|
||||
Width="280"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button x:Uid="PlayLists_CreatePlaylist"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource AccentButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="HavePlaylistControl"
|
||||
Grid.Row="1"
|
||||
Visibility="Visible">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="MenuGrid"
|
||||
Grid.Row="0"
|
||||
Margin="56,23,56,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Style="{StaticResource AccentButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<FontIcon FontFamily="{StaticResource UntamedFontFamily}"
|
||||
FontSize="12" Glyph=""/>
|
||||
<TextBlock x:Uid="PlayLists_NewPlaylist"/>
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBox x:Uid="PlaylistInfo_UntitledPlaylist"
|
||||
Width="280"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button x:Uid="PlayLists_CreatePlaylist"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource AccentButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button Grid.Column="2"
|
||||
Background="Transparent" BorderBrush="Transparent">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock x:Uid="Songs_SortByChosen"/>
|
||||
<TextBlock Foreground="{ThemeResource AccentTextFillColorTertiaryBrush}" Text="{x:Bind ViewModel.SortByStr, Mode=OneWay}"/>
|
||||
<FontIcon Margin="8,0,0,0"
|
||||
FontFamily="{StaticResource UntamedFontFamily}"
|
||||
FontSize="12" Glyph=""/>
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<ListView x:Name="SortByListView"
|
||||
Margin="-12,-13,-12,-15"
|
||||
ItemTemplate="{StaticResource SortByListViewItemTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.SortBy}"
|
||||
Loaded="{x:Bind ViewModel.SortByListView_Loaded}"
|
||||
SelectionChanged="{x:Bind ViewModel.SortByListView_SelectionChanged}"/>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Grid>
|
||||
<GridView x:Name="PlaylistGridView"
|
||||
Grid.Row="1"
|
||||
helper:ListViewExtensions.ItemCornerRadius="8"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PlaylistGridView_ItemClick"
|
||||
Loaded="PlaylistGridView_Loaded"
|
||||
SelectionMode="None"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using The_Untamed_Music_Player.ViewModels;
|
||||
|
||||
@@ -12,4 +13,8 @@ public sealed partial class PlayListsPage : Page
|
||||
ViewModel = App.GetService<PlayListsViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void PlaylistGridView_Loaded(object sender, RoutedEventArgs e) { }
|
||||
|
||||
private void PlaylistGridView_ItemClick(object sender, ItemClickEventArgs e) { }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:contract="using:The_Untamed_Music_Player.Contracts.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:The_Untamed_Music_Player.Models"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helper="using:The_Untamed_Music_Player.Helpers"
|
||||
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Loaded="OnLoaded">
|
||||
|
||||
Reference in New Issue
Block a user