mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
chores
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.2.222.0" />
|
||||
Version="1.2.231.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Models.Db;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||
@@ -17,36 +15,42 @@ using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching; // 关键:用于线程调度
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Microsoft.Windows.Globalization;
|
||||
using Microsoft.Windows.AppLifecycle; // 关键:App生命周期管理
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
|
||||
private Window? m_window;
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
public static new App Current => (App)Application.Current;
|
||||
|
||||
private static Mutex? _instanceMutex;
|
||||
private readonly string _appKey = Windows.ApplicationModel.Package.Current.Id.FamilyName;
|
||||
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
// Must be done before InitializeComponent
|
||||
if (!TryHandleSingleInstance())
|
||||
{
|
||||
// 如果移交成功直接退出当前进程
|
||||
Environment.Exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureSingleInstance();
|
||||
this.InitializeComponent();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
PathHelper.EnsureDirectories();
|
||||
@@ -54,28 +58,81 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
|
||||
// 注册全局异常捕获
|
||||
UnhandledException += App_UnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
private void EnsureSingleInstance()
|
||||
/// <summary>
|
||||
/// 处理单实例逻辑。
|
||||
/// 返回 true 表示我是主实例,继续运行。
|
||||
/// 返回 false 表示我是第二个实例,已通知主实例,我应该退出。
|
||||
/// </summary>
|
||||
private bool TryHandleSingleInstance()
|
||||
{
|
||||
_instanceMutex = new Mutex(true, Constants.App.AppName, out bool createdNew);
|
||||
// 尝试查找或注册当前实例
|
||||
var mainInstance = AppInstance.FindOrRegisterForKey(_appKey);
|
||||
|
||||
if (!createdNew)
|
||||
// 如果当前实例就是注册的那个主实例
|
||||
if (mainInstance.IsCurrent)
|
||||
{
|
||||
User32.MessageBox(HWND.NULL, new ResourceLoader().GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL);
|
||||
Environment.Exit(0);
|
||||
// 监听 "Activated" 事件。
|
||||
// 当第二个实例启动并重定向过来时,这个事件会被触发。
|
||||
mainInstance.Activated += OnMainInstanceActivated;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 我不是主实例,我是后来者。
|
||||
// 获取当前实例的激活参数(比如是通过文件双击打开的,这里能拿到文件路径)
|
||||
var args = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
|
||||
// 将激活请求重定向给主实例
|
||||
// 注意:这里是同步等待,确保发送成功后再退出
|
||||
try
|
||||
{
|
||||
mainInstance.RedirectActivationToAsync(args).AsTask().Wait();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 即使重定向失败,作为第二个实例也应该退出
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
/// <summary>
|
||||
/// 当第二个实例试图启动时,主实例会收到此回调
|
||||
/// </summary>
|
||||
private void OnMainInstanceActivated(object? sender, AppActivationArguments e)
|
||||
{
|
||||
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
// 这个事件是在后台线程触发的,必须切回 UI 线程操作窗口
|
||||
m_window?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
HandleActivation();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 唤醒逻辑
|
||||
/// </summary>
|
||||
private void HandleActivation()
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<LyricsWindowSwitchWindow>();
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
// 初始化数据库
|
||||
await EnsureDatabasesAsync();
|
||||
|
||||
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
var fileSystemService = Ioc.Default.GetRequiredService<IFileSystemService>();
|
||||
|
||||
// 开始后台扫描任务
|
||||
foreach (var item in settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (item.LastSyncTime == null)
|
||||
@@ -85,8 +142,10 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
fileSystemService.StartAllFolderTimers();
|
||||
|
||||
WindowHook.OpenOrShowWindow<SystemTrayWindow>();
|
||||
// 初始化托盘
|
||||
m_window = WindowHook.OpenOrShowWindow<SystemTrayWindow>();
|
||||
|
||||
// 根据设置打开歌词窗口
|
||||
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
|
||||
{
|
||||
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
|
||||
@@ -102,12 +161,101 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据设置自动打开主界面
|
||||
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureDatabasesAsync()
|
||||
{
|
||||
var playHistoryFactory = Ioc.Default.GetRequiredService<IDbContextFactory<PlayHistoryDbContext>>();
|
||||
var fileCacheFactory = Ioc.Default.GetRequiredService<IDbContextFactory<FilesIndexDbContext>>();
|
||||
|
||||
await SafeInitDatabaseAsync(
|
||||
"PlayHistory",
|
||||
PathHelper.PlayHistoryPath,
|
||||
async () =>
|
||||
{
|
||||
using var db = await playHistoryFactory.CreateDbContextAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
},
|
||||
isCritical: true
|
||||
);
|
||||
|
||||
await SafeInitDatabaseAsync(
|
||||
"FileCache",
|
||||
PathHelper.FilesIndexPath,
|
||||
async () =>
|
||||
{
|
||||
using var db = await fileCacheFactory.CreateDbContextAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
},
|
||||
isCritical: false
|
||||
);
|
||||
}
|
||||
|
||||
private async Task SafeInitDatabaseAsync(string dbName, string dbPath, Func<Task> initAction, bool isCritical)
|
||||
{
|
||||
try
|
||||
{
|
||||
await initAction();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DB Error] {dbName} init failed: {ex.Message}");
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
// 尝试清理连接池
|
||||
SqliteConnection.ClearAllPools();
|
||||
|
||||
if (isCritical)
|
||||
{
|
||||
var backupPath = dbPath + ".bak_" + DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
File.Move(dbPath, backupPath, true);
|
||||
await ShowErrorDialogAsync("Database Recovery", $"Database {dbName} is damaged, the old database has been backed up to {backupPath}, and the program will create a new database.");
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(dbPath);
|
||||
}
|
||||
}
|
||||
await initAction();
|
||||
System.Diagnostics.Debug.WriteLine($"[DB Info] {dbName} recovered successfully.");
|
||||
}
|
||||
catch (Exception fatalEx)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[] : {fatalEx.Message}");
|
||||
await ShowErrorDialogAsync("Fatal Error", $"{dbName} recovery failed, please delete the file at {dbPath} and try again by restarting the program. ({fatalEx.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowErrorDialogAsync(string title, string content)
|
||||
{
|
||||
// 这里假设 m_window 已经存在。如果没有显示主窗口,这个弹窗可能无法显示。
|
||||
// 在 App 启动极早期的错误,可能需要退化为 Log 或者 System.Diagnostics.Process.Start 打开记事本报错
|
||||
if (m_window != null)
|
||||
{
|
||||
m_window.DispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
|
||||
{
|
||||
Title = title,
|
||||
Content = content,
|
||||
CloseButtonText = "OK",
|
||||
XamlRoot = m_window.Content?.XamlRoot // 确保 Content 不为空
|
||||
};
|
||||
if (dialog.XamlRoot != null) await dialog.ShowAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
@@ -115,14 +263,19 @@ namespace BetterLyrics.WinUI3
|
||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
// Register services
|
||||
Ioc.Default.ConfigureServices(
|
||||
new ServiceCollection()
|
||||
// 数据库工厂
|
||||
.AddDbContextFactory<PlayHistoryDbContext>(options => options.UseSqlite($"Data Source={PathHelper.PlayHistoryPath}"))
|
||||
.AddDbContextFactory<FilesIndexDbContext>(options => options.UseSqlite($"Data Source={PathHelper.FilesIndexPath}"))
|
||||
|
||||
// 日志
|
||||
.AddLogging(loggingBuilder =>
|
||||
{
|
||||
loggingBuilder.ClearProviders();
|
||||
loggingBuilder.AddSerilog();
|
||||
})
|
||||
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
|
||||
@@ -135,6 +288,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<ILocalizationService, LocalizationService>()
|
||||
.AddSingleton<IFileSystemService, FileSystemService>()
|
||||
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
|
||||
|
||||
// ViewModels
|
||||
.AddSingleton<AppSettingsControlViewModel>()
|
||||
.AddSingleton<PlaybackSettingsControlViewModel>()
|
||||
@@ -167,7 +321,8 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
|
||||
{
|
||||
_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
|
||||
// FirstChance 异常非常多(比如内部 try-catch 也会触发),通常建议只在 Debug 模式记录,或者过滤特定类型
|
||||
// _logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
|
||||
@@ -180,4 +335,4 @@ namespace BetterLyrics.WinUI3
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,11 @@
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
@@ -97,7 +102,6 @@
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SMBLibrary" Version="1.5.5.1" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
@@ -130,6 +134,10 @@
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="FlaUI.UIA3" />
|
||||
<TrimmerRootAssembly Include="Interop.UIAutomationClient" />
|
||||
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore" />
|
||||
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Abstractions" />
|
||||
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Relational" />
|
||||
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<TrimmerRootAssembly Include="NAudio.Wasapi" />
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
|
||||
|
||||
@@ -190,6 +190,12 @@
|
||||
</dev:SettingsExpander.ItemsHeader>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageSettingsPlayHistory" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button x:Uid="SettingsPageExportPlayHistoryButton" Command="{x:Bind ViewModel.ExportPlayHistoryCommand}" />
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ude;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
@@ -29,6 +30,18 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return Encoding.GetEncoding(encoding);
|
||||
}
|
||||
|
||||
public static async Task CopyFileAsync(string sourcePath, string destinationPath)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(destinationPath);
|
||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await sourceStream.CopyToAsync(destinationStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SanitizeFileName(string fileName, char replacement = '_')
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static string PlayQueuePath => Path.Combine(LocalFolder, "play-queue.m3u");
|
||||
public static string PlayHistoryPath => Path.Combine(LocalFolder, "play-history.db");
|
||||
public static string FilesIndexPath => Path.Combine(LocalFolder, "files-index.db");
|
||||
public static string FilesIndexPath => Path.Combine(CacheFolder, "files-index.db");
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Db
|
||||
{
|
||||
public partial class FilesIndexDbContext : DbContext
|
||||
{
|
||||
public FilesIndexDbContext(DbContextOptions<FilesIndexDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<FilesIndexItem> FilesIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Db
|
||||
{
|
||||
public partial class PlayHistoryDbContext : DbContext
|
||||
{
|
||||
public PlayHistoryDbContext(DbContextOptions<PlayHistoryDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<PlayHistoryItem> PlayHistory { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
SetFromTrack(track);
|
||||
}
|
||||
|
||||
public ExtendedTrack(FileCacheEntity? entity, Stream? stream = null) : base()
|
||||
public ExtendedTrack(FilesIndexItem? entity, Stream? stream = null) : base()
|
||||
{
|
||||
if (entity == null) return;
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
using SQLite;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
[Table("FileCache")]
|
||||
public class FileCacheEntity
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
// 关联到 MediaFolder.Id。
|
||||
// 区分不同配置(即使两个配置连的是同一个 SMB,但在 APP 里视为不同源)。
|
||||
// 删除配置时,可以由 MediaFolderId 快速级联删除所有缓存。
|
||||
[Indexed]
|
||||
public string MediaFolderId { get; set; }
|
||||
|
||||
// 存储父文件夹的标准 URI (smb://host/share/parent)
|
||||
// 根目录文件的 ParentUri 可以为空,或者等于 MediaFolder 的 Base Uri
|
||||
[Indexed]
|
||||
public string? ParentUri { get; set; }
|
||||
|
||||
// 确保它是 URL 编码过且格式统一的
|
||||
[Indexed(Unique = true)]
|
||||
public string Uri { get; set; }
|
||||
|
||||
public string FileName { get; set; } = "";
|
||||
|
||||
public bool IsDirectory { get; set; }
|
||||
|
||||
// 记录文件大小,同步时用来对比文件是否变化
|
||||
public long FileSize { get; set; }
|
||||
|
||||
// 记录修改时间,同步时对比使用
|
||||
public DateTime? LastModified { get; set; }
|
||||
|
||||
public string Title { get; set; } = "";
|
||||
public string Artists { get; set; } = "";
|
||||
public string Album { get; set; } = "";
|
||||
public int? Year { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
public double SampleRate { get; set; }
|
||||
public int BitDepth { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public string AudioFormatName { get; set; } = "";
|
||||
public string AudioFormatShortName { get; set; } = "";
|
||||
public string Encoder { get; set; } = "";
|
||||
public string? EmbeddedLyrics { get; set; }
|
||||
public string? LocalAlbumArtPath { get; set; }
|
||||
public bool IsMetadataParsed { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
[Index(nameof(MediaFolderId))] // 普通索引
|
||||
[Index(nameof(ParentUri))] // 普通索引
|
||||
[Index(nameof(Uri), IsUnique = true)] // 唯一索引
|
||||
public class FilesIndexItem
|
||||
{
|
||||
[Key] // 主键
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 明确指定为自增 (Identity)
|
||||
public int Id { get; set; }
|
||||
|
||||
// 关联到 MediaFolder.Id
|
||||
// 注意:作为索引列,必须限制长度,否则 SQL Server 会报错 (索引最大900字节)
|
||||
[MaxLength(450)]
|
||||
public string MediaFolderId { get; set; }
|
||||
|
||||
// 存储父文件夹的标准 URI
|
||||
// 允许为空
|
||||
[MaxLength(450)]
|
||||
public string? ParentUri { get; set; }
|
||||
|
||||
// 唯一索引列
|
||||
// 必须限制长度。450字符 * 2字节/字符 = 900字节 (正好卡在 SQL Server 限制内)
|
||||
[Required]
|
||||
[MaxLength(450)]
|
||||
public string Uri { get; set; }
|
||||
|
||||
public string FileName { get; set; } = "";
|
||||
|
||||
public bool IsDirectory { get; set; }
|
||||
|
||||
public long FileSize { get; set; }
|
||||
|
||||
public DateTime? LastModified { get; set; }
|
||||
|
||||
// 下面的元数据字段通常不需要索引,可以使用 MaxLength 稍微优化空间,
|
||||
// 或者直接留空(默认为 nvarchar(max))
|
||||
public string Title { get; set; } = "";
|
||||
public string Artists { get; set; } = "";
|
||||
public string Album { get; set; } = "";
|
||||
public int? Year { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
public double SampleRate { get; set; }
|
||||
public int BitDepth { get; set; }
|
||||
public int Duration { get; set; }
|
||||
|
||||
[MaxLength(50)] // 格式名称通常很短,限制一下是个好习惯
|
||||
public string AudioFormatName { get; set; } = "";
|
||||
|
||||
[MaxLength(20)]
|
||||
public string AudioFormatShortName { get; set; } = "";
|
||||
|
||||
public string Encoder { get; set; } = "";
|
||||
|
||||
// 歌词可能会很长,保留默认的 nvarchar(max) 即可
|
||||
public string? EmbeddedLyrics { get; set; }
|
||||
|
||||
public string? LocalAlbumArtPath { get; set; }
|
||||
|
||||
public bool IsMetadataParsed { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,39 @@
|
||||
using SQLite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
[Preserve(AllMembers = true)]
|
||||
[Table("PlayHistory")]
|
||||
[Index(nameof(Title))]
|
||||
[Index(nameof(Artist))]
|
||||
[Index(nameof(StartedAt))] // 用于按时间排序查询(如:最近播放)
|
||||
[Index(nameof(PlayerId))]
|
||||
public class PlayHistoryItem
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // AutoIncrement
|
||||
public int Id { get; set; }
|
||||
|
||||
[Indexed] public string Title { get; set; }
|
||||
[Indexed] public string Artist { get; set; }
|
||||
public string Album { get; set; }
|
||||
// 注意:作为索引列,必须加 MaxLength。
|
||||
// 如果不加,默认为 nvarchar(max),SQL Server 无法对其建立高效索引。
|
||||
[MaxLength(450)]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[MaxLength(450)]
|
||||
public string Artist { get; set; } = "";
|
||||
|
||||
// Album 没有索引,可以不限制长度,或者为了规范也限制一下
|
||||
public string Album { get; set; } = "";
|
||||
|
||||
public DateTime StartedAt { get; set; }
|
||||
|
||||
[Indexed] public DateTime StartedAt { get; set; }
|
||||
public double DurationPlayedMs { get; set; }
|
||||
|
||||
public double TotalDurationMs { get; set; }
|
||||
|
||||
[Indexed]
|
||||
public string PlayerId { get; set; }
|
||||
// PlayerId 通常是个 GUID 或者短字符串,给 100 长度通常足够了,节省索引空间
|
||||
[MaxLength(100)]
|
||||
public string PlayerId { get; set; } = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
|
||||
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
|
||||
|
||||
FileCacheEntity? bestMatch = null;
|
||||
FilesIndexItem? bestMatch = null;
|
||||
|
||||
foreach (var item in allFiles)
|
||||
{
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Db;
|
||||
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using SQLite;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -28,7 +29,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILogger<FileSystemService> _logger;
|
||||
|
||||
private readonly SQLiteAsyncConnection _db;
|
||||
private readonly IDbContextFactory<FilesIndexDbContext> _contextFactory;
|
||||
|
||||
private bool _isInitialized = false;
|
||||
|
||||
// 定时器字典
|
||||
@@ -36,54 +38,37 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
// 当前正在执行的扫描任务字典
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeScanTokens = new();
|
||||
|
||||
private static readonly SemaphoreSlim _dbLock = new(1, 1);
|
||||
private static readonly SemaphoreSlim _folderScanLock = new(1, 1);
|
||||
|
||||
public FileSystemService(ISettingsService settingsService, ILocalizationService localizationService, ILogger<FileSystemService> logger)
|
||||
public FileSystemService(
|
||||
ISettingsService settingsService,
|
||||
ILocalizationService localizationService,
|
||||
ILogger<FileSystemService> logger,
|
||||
IDbContextFactory<FilesIndexDbContext> contextFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_localizationService = localizationService;
|
||||
_settingsService = settingsService;
|
||||
_db = new SQLiteAsyncConnection(PathHelper.FilesIndexPath);
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
public async Task<List<FilesIndexItem>> GetFilesAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId, bool forceRefresh = false)
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
string queryParentUri = parentFolder == null ? "" : parentFolder.Uri;
|
||||
if (parentFolder == null && !forceRefresh) forceRefresh = true;
|
||||
|
||||
await _db.CreateTableAsync<FileCacheEntity>();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
public async Task<List<FileCacheEntity>> GetFilesAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId, bool forceRefresh = false)
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
string queryParentUri;
|
||||
if (parentFolder == null)
|
||||
{
|
||||
if (!forceRefresh) forceRefresh = true;
|
||||
queryParentUri = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
queryParentUri = parentFolder.Uri;
|
||||
}
|
||||
|
||||
List<FileCacheEntity> cachedEntities = new List<FileCacheEntity>();
|
||||
|
||||
if (parentFolder != null)
|
||||
{
|
||||
cachedEntities = await _db.Table<FileCacheEntity>()
|
||||
.Where(x => x.MediaFolderId == configId && x.ParentUri == queryParentUri)
|
||||
.ToListAsync();
|
||||
}
|
||||
var cachedEntities = await context.FilesIndex
|
||||
.AsNoTracking() // 读操作不追踪,提升性能
|
||||
.Where(x => x.MediaFolderId == configId && x.ParentUri == queryParentUri)
|
||||
.ToListAsync();
|
||||
|
||||
bool needSync = forceRefresh || cachedEntities.Count == 0;
|
||||
|
||||
if (needSync)
|
||||
{
|
||||
// SyncAsync 内部自己管理 Context
|
||||
cachedEntities = await SyncAsync(provider, parentFolder, configId);
|
||||
}
|
||||
|
||||
@@ -91,17 +76,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从远端/本地同步文件至数据库,该阶段不会解析文件全部元数据。
|
||||
/// <para/>
|
||||
/// 如果某个已有文件被修改或有新文件被添加,会预留空位,等待后续填充(通常交给 <see cref="ScanMediaFolderAsync"/> 完成)
|
||||
/// 从远端/本地同步文件至数据库
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="parentFolder"></param>
|
||||
/// <param name="configId"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<List<FileCacheEntity>> SyncAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId)
|
||||
private async Task<List<FilesIndexItem>> SyncAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId)
|
||||
{
|
||||
List<FileCacheEntity> remoteItems;
|
||||
List<FilesIndexItem> remoteItems;
|
||||
try
|
||||
{
|
||||
remoteItems = await provider.GetFilesAsync(parentFolder);
|
||||
@@ -116,80 +95,79 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
|
||||
string targetParentUri = "";
|
||||
if (remoteItems.Count > 0)
|
||||
{
|
||||
targetParentUri = remoteItems[0].ParentUri ?? "";
|
||||
}
|
||||
else if (parentFolder != null)
|
||||
{
|
||||
targetParentUri = parentFolder.Uri;
|
||||
}
|
||||
else
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _db.RunInTransactionAsync(conn =>
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// 开启事务 (EF Core 也能管理事务)
|
||||
using var transaction = await context.Database.BeginTransactionAsync();
|
||||
|
||||
// 1. 获取数据库中现有的该目录下的文件
|
||||
var dbItems = await context.FilesIndex
|
||||
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
|
||||
.ToListAsync();
|
||||
|
||||
var dbMap = dbItems.ToDictionary(x => x.Uri, x => x);
|
||||
|
||||
// 2. 远端数据去重(防止 Provider 返回重复 Uri)
|
||||
var remoteDistinct = remoteItems
|
||||
.GroupBy(x => x.Uri)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
var remoteUris = new HashSet<string>();
|
||||
|
||||
// 3. 处理 新增 和 更新
|
||||
foreach (var remote in remoteDistinct)
|
||||
{
|
||||
var dbItems = conn.Table<FileCacheEntity>()
|
||||
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
|
||||
.ToList();
|
||||
remoteUris.Add(remote.Uri);
|
||||
|
||||
var dbMap = dbItems.ToDictionary(x => x.Uri, x => x);
|
||||
|
||||
var remoteMap = remoteItems
|
||||
.GroupBy(x => x.Uri)
|
||||
.Select(g => g.First())
|
||||
.ToDictionary(x => x.Uri, x => x);
|
||||
|
||||
var toInsert = new List<FileCacheEntity>();
|
||||
var toUpdate = new List<FileCacheEntity>();
|
||||
var toDelete = new List<FileCacheEntity>();
|
||||
|
||||
foreach (var remote in remoteItems)
|
||||
if (dbMap.TryGetValue(remote.Uri, out var existing))
|
||||
{
|
||||
if (dbMap.TryGetValue(remote.Uri, out var existing))
|
||||
{
|
||||
bool isChanged = existing.FileSize != remote.FileSize ||
|
||||
existing.LastModified != remote.LastModified;
|
||||
// 检查是否变更
|
||||
bool isChanged = existing.FileSize != remote.FileSize ||
|
||||
existing.LastModified != remote.LastModified;
|
||||
|
||||
if (isChanged)
|
||||
{
|
||||
existing.FileSize = remote.FileSize;
|
||||
existing.LastModified = remote.LastModified;
|
||||
existing.IsMetadataParsed = false; // 标记为未解析,下次会重新读取元数据
|
||||
|
||||
toUpdate.Add(existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 数据库里原有的 Title, Artist, LocalAlbumArtPath 都会被完美保留
|
||||
}
|
||||
}
|
||||
else
|
||||
if (isChanged)
|
||||
{
|
||||
toInsert.Add(remote);
|
||||
existing.FileSize = remote.FileSize;
|
||||
existing.LastModified = remote.LastModified;
|
||||
existing.IsMetadataParsed = false; // 标记重新解析
|
||||
|
||||
// EF Core 自动追踪 existing 的变化,无需手动 Update
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var dbItem in dbItems)
|
||||
else
|
||||
{
|
||||
if (!remoteMap.ContainsKey(dbItem.Uri))
|
||||
{
|
||||
toDelete.Add(dbItem);
|
||||
}
|
||||
// 新增
|
||||
// 注意:如果 Id 是自增的,不要手动赋值 Id,除非是 Guid
|
||||
context.FilesIndex.Add(remote);
|
||||
}
|
||||
}
|
||||
|
||||
if (toInsert.Count > 0) conn.InsertAll(toInsert);
|
||||
if (toUpdate.Count > 0) conn.UpdateAll(toUpdate);
|
||||
if (toDelete.Count > 0)
|
||||
// 4. 处理 删除 (数据库有,远端没有)
|
||||
foreach (var dbItem in dbItems)
|
||||
{
|
||||
if (!remoteUris.Contains(dbItem.Uri))
|
||||
{
|
||||
foreach (var item in toDelete) conn.Delete(item);
|
||||
context.FilesIndex.Remove(dbItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var finalItems = await _db.Table<FileCacheEntity>()
|
||||
await context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// 5. 返回最新数据
|
||||
// 这里的 dbItems 已经被 Update 更新了内存状态,但 Remove 的还在列表里,Add 的不在列表里
|
||||
// 所以最稳妥的是重新查一次,或者手动维护列表。为了准确性,重新查询 (AsNoTracking)
|
||||
var finalItems = await context.FilesIndex
|
||||
.AsNoTracking()
|
||||
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
|
||||
.ToListAsync();
|
||||
|
||||
@@ -204,37 +182,34 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateMetadataAsync(FileCacheEntity entity)
|
||||
public async Task UpdateMetadataAsync(FilesIndexItem entity)
|
||||
{
|
||||
// 现在的实体已经包含了完整信息,直接 Update 即可
|
||||
// 我们只需要确保 Where 子句用的是主键或者 Uri
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// 简化版 SQL,直接用 ORM 的 Update
|
||||
// 但因为 entity 对象可能包含一些不应该被覆盖的旧数据(如果多线程操作),
|
||||
// 手写 SQL 只更新 Metadata 字段更安全。
|
||||
|
||||
string sql = @"
|
||||
UPDATE FileCache
|
||||
SET
|
||||
Title = ?, Artists = ?, Album = ?,
|
||||
Year = ?, Bitrate = ?, SampleRate = ?, BitDepth = ?,
|
||||
Duration = ?, AudioFormatName = ?, AudioFormatShortName = ?, Encoder = ?,
|
||||
EmbeddedLyrics = ?, LocalAlbumArtPath = ?,
|
||||
IsMetadataParsed = 1
|
||||
WHERE Id = ?"; // 推荐用 Id (主键) 最快,如果没有 Id 则用 Uri
|
||||
|
||||
await _db.ExecuteAsync(sql,
|
||||
entity.Title, entity.Artists, entity.Album,
|
||||
entity.Year, entity.Bitrate, entity.SampleRate, entity.BitDepth,
|
||||
entity.Duration, entity.AudioFormatName, entity.AudioFormatShortName, entity.Encoder,
|
||||
entity.EmbeddedLyrics, entity.LocalAlbumArtPath,
|
||||
entity.Id // WHERE Id = ?
|
||||
);
|
||||
// 使用 EF Core 7.0+ 的 ExecuteUpdateAsync 高效更新
|
||||
// 这会直接生成 UPDATE SQL,不经过内存加载,性能极高
|
||||
await context.FilesIndex
|
||||
.Where(x => x.Id == entity.Id) // 优先用 Id
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(p => p.Title, entity.Title)
|
||||
.SetProperty(p => p.Artists, entity.Artists)
|
||||
.SetProperty(p => p.Album, entity.Album)
|
||||
.SetProperty(p => p.Year, entity.Year)
|
||||
.SetProperty(p => p.Bitrate, entity.Bitrate)
|
||||
.SetProperty(p => p.SampleRate, entity.SampleRate)
|
||||
.SetProperty(p => p.BitDepth, entity.BitDepth)
|
||||
.SetProperty(p => p.Duration, entity.Duration)
|
||||
.SetProperty(p => p.AudioFormatName, entity.AudioFormatName)
|
||||
.SetProperty(p => p.AudioFormatShortName, entity.AudioFormatShortName)
|
||||
.SetProperty(p => p.Encoder, entity.Encoder)
|
||||
.SetProperty(p => p.EmbeddedLyrics, entity.EmbeddedLyrics)
|
||||
.SetProperty(p => p.LocalAlbumArtPath, entity.LocalAlbumArtPath)
|
||||
.SetProperty(p => p.IsMetadataParsed, true)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FileCacheEntity entity)
|
||||
public async Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FilesIndexItem entity)
|
||||
{
|
||||
// 直接传递实体给 Provider
|
||||
return await provider.OpenReadAsync(entity);
|
||||
}
|
||||
|
||||
@@ -258,7 +233,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
if (_activeScanTokens.TryGetValue(folder.Id, out var activeScanCts))
|
||||
{
|
||||
activeScanCts.Cancel();
|
||||
// 强制终止正在扫描的操作
|
||||
}
|
||||
|
||||
try
|
||||
@@ -272,17 +246,16 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceCleaningCache");
|
||||
});
|
||||
|
||||
await InitializeAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
await _dbLock.WaitAsync();
|
||||
try
|
||||
await context.FilesIndex
|
||||
.Where(x => x.MediaFolderId == folder.Id)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
// VACUUM 是 SQLite 特有的命令
|
||||
if (context.Database.IsSqlite())
|
||||
{
|
||||
await _db.ExecuteAsync("DELETE FROM FileCache WHERE MediaFolderId = ?", folder.Id);
|
||||
await _db.ExecuteAsync("VACUUM");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dbLock.Release();
|
||||
await context.Database.ExecuteSqlRawAsync("VACUUM");
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -325,8 +298,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
|
||||
_dispatcherQueue.TryEnqueue(() => folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceConnecting"));
|
||||
|
||||
await InitializeAsync();
|
||||
|
||||
using var fs = folder.CreateFileSystem();
|
||||
if (fs == null || !await fs.ConnectAsync())
|
||||
{
|
||||
@@ -340,8 +311,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
|
||||
_dispatcherQueue.TryEnqueue(() => folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceFetchingFileList"));
|
||||
|
||||
var filesToProcess = new List<FileCacheEntity>();
|
||||
var foldersToScan = new Queue<FileCacheEntity?>();
|
||||
var filesToProcess = new List<FilesIndexItem>();
|
||||
var foldersToScan = new Queue<FilesIndexItem?>();
|
||||
foldersToScan.Enqueue(null); // 根目录
|
||||
|
||||
while (foldersToScan.Count > 0)
|
||||
@@ -349,7 +320,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
if (scanCts.Token.IsCancellationRequested) return;
|
||||
|
||||
var currentParent = foldersToScan.Dequeue();
|
||||
|
||||
var items = await GetFilesAsync(fs, currentParent, folder.Id, forceRefresh: true);
|
||||
|
||||
foreach (var item in items)
|
||||
@@ -414,10 +384,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
|
||||
if (track.Duration > 0)
|
||||
{
|
||||
// 保存封面
|
||||
string? artPath = await SaveAlbumArtToDiskAsync(track);
|
||||
|
||||
// 填充实体
|
||||
item.Title = track.Title;
|
||||
item.Artists = track.Artist;
|
||||
item.Album = track.Album;
|
||||
@@ -429,7 +397,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
item.AudioFormatName = track.AudioFormatName;
|
||||
item.AudioFormatShortName = track.AudioFormatShortName;
|
||||
item.Encoder = track.Encoder;
|
||||
item.EmbeddedLyrics = track.RawLyrics; // 内嵌歌词
|
||||
item.EmbeddedLyrics = track.RawLyrics;
|
||||
item.LocalAlbumArtPath = artPath;
|
||||
item.IsMetadataParsed = true;
|
||||
}
|
||||
@@ -441,7 +409,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
{
|
||||
using var reader = new StreamReader(stream);
|
||||
string content = await reader.ReadToEndAsync();
|
||||
|
||||
item.EmbeddedLyrics = content;
|
||||
item.IsMetadataParsed = true;
|
||||
}
|
||||
@@ -449,15 +416,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
|
||||
if (item.IsMetadataParsed)
|
||||
{
|
||||
await _dbLock.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
await UpdateMetadataAsync(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dbLock.Release();
|
||||
}
|
||||
// 更新操作:直接调用 UpdateMetadataAsync
|
||||
// 此时不需要 _dbLock,因为 UpdateMetadataAsync 内部会 CreateDbContextAsync
|
||||
// 而 _folderScanLock 已经保证了当前文件夹扫描的独占性
|
||||
await UpdateMetadataAsync(item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -488,7 +450,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
finally
|
||||
{
|
||||
_folderScanLock.Release();
|
||||
|
||||
_activeScanTokens.TryRemove(folder.Id, out _);
|
||||
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
@@ -499,23 +460,22 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<FileCacheEntity>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds)
|
||||
public async Task<List<FilesIndexItem>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds)
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
if (enabledConfigIds == null || !enabledConfigIds.Any())
|
||||
{
|
||||
return new List<FileCacheEntity>();
|
||||
return new List<FilesIndexItem>();
|
||||
}
|
||||
|
||||
var idList = enabledConfigIds.ToList();
|
||||
|
||||
// SQL 逻辑: SELECT * FROM FileCache WHERE IsMetadataParsed = 1 AND MediaFolderId IN (...)
|
||||
var results = await _db.Table<FileCacheEntity>()
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// SQL: SELECT * FROM FileCache WHERE IsMetadataParsed = 1 AND MediaFolderId IN (...)
|
||||
return await context.FilesIndex
|
||||
.AsNoTracking()
|
||||
.Where(x => x.IsMetadataParsed && idList.Contains(x.MediaFolderId))
|
||||
.ToListAsync();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void StartAllFolderTimers()
|
||||
@@ -575,11 +535,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
}, newCts.Token);
|
||||
}
|
||||
|
||||
// 参数为 string parentUri,表示哪个文件夹的内容变了
|
||||
public event EventHandler<string>? FolderUpdated;
|
||||
|
||||
private async Task<string?> SaveAlbumArtToDiskAsync(ExtendedTrack track)
|
||||
{
|
||||
// 代码未变,纯 IO 操作
|
||||
var picData = track.AlbumArtByteArray;
|
||||
if (picData == null || picData.Length == 0) return null;
|
||||
|
||||
@@ -632,6 +592,5 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
{
|
||||
public interface IFileSystemService
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化(连接)数据库
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task InitializeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库拉取文件(必要时需要从远端/本地同步至数据库)
|
||||
/// </summary>
|
||||
@@ -24,7 +18,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
/// <param name="configId"></param>
|
||||
/// <param name="forceRefresh">强制需要从远端/本地同步至数据库</param>
|
||||
/// <returns></returns>
|
||||
Task<List<FileCacheEntity>> GetFilesAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId, bool forceRefresh = false);
|
||||
Task<List<FilesIndexItem>> GetFilesAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId, bool forceRefresh = false);
|
||||
|
||||
/// <summary>
|
||||
/// 打开文件(通过远端/本地流)
|
||||
@@ -32,14 +26,14 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FileCacheEntity entity);
|
||||
Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FilesIndexItem entity);
|
||||
|
||||
/// <summary>
|
||||
/// 更新数据库(单个文件)
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
Task UpdateMetadataAsync(FileCacheEntity entity);
|
||||
Task UpdateMetadataAsync(FilesIndexItem entity);
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库删除
|
||||
@@ -60,7 +54,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
/// </summary>
|
||||
/// <param name="enabledConfigIds"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<FileCacheEntity>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds);
|
||||
Task<List<FilesIndexItem>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds);
|
||||
|
||||
void StartAllFolderTimers();
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
/// </summary>
|
||||
/// <param name="parentFolder"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null);
|
||||
Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null);
|
||||
/// <summary>
|
||||
/// 打开流
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
Task<Stream?> OpenReadAsync(FileCacheEntity file);
|
||||
Task<Stream?> OpenReadAsync(FilesIndexItem file);
|
||||
Task DisconnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,22 +42,14 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_client.IsConnected) return true;
|
||||
await _client.AutoConnect(); // AutoConnect 会自动尝试 FTP/FTPS
|
||||
return _client.IsConnected;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"FTP连接失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
if (_client.IsConnected) return true;
|
||||
await _client.AutoConnect(); // AutoConnect 会自动尝试 FTP/FTPS
|
||||
return _client.IsConnected;
|
||||
}
|
||||
|
||||
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
|
||||
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
|
||||
{
|
||||
var result = new List<FileCacheEntity>();
|
||||
var result = new List<FilesIndexItem>();
|
||||
|
||||
string targetServerPath;
|
||||
Uri parentUri;
|
||||
@@ -104,7 +96,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
Path = item.FullName
|
||||
};
|
||||
|
||||
result.Add(new FileCacheEntity
|
||||
result.Add(new FilesIndexItem
|
||||
{
|
||||
MediaFolderId = _config.Id,
|
||||
// 如果是根目录扫描,ParentUri 用 Config 的;否则用传入文件夹的
|
||||
@@ -130,7 +122,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity file)
|
||||
public async Task<Stream?> OpenReadAsync(FilesIndexItem file)
|
||||
{
|
||||
if (file == null) return null;
|
||||
|
||||
|
||||
@@ -20,12 +20,20 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
|
||||
public Task<bool> ConnectAsync()
|
||||
{
|
||||
return Task.FromResult(Directory.Exists(_rootLocalPath));
|
||||
var isExisted = Directory.Exists(_rootLocalPath);
|
||||
if (isExisted)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FileNotFoundException(null, _rootLocalPath);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
|
||||
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
|
||||
{
|
||||
var result = new List<FileCacheEntity>();
|
||||
var result = new List<FilesIndexItem>();
|
||||
|
||||
string targetPath;
|
||||
string parentUriString;
|
||||
@@ -70,7 +78,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
size = fi.Length;
|
||||
}
|
||||
|
||||
result.Add(new FileCacheEntity
|
||||
result.Add(new FilesIndexItem
|
||||
{
|
||||
MediaFolderId = _config.Id, // 关联配置 ID
|
||||
|
||||
@@ -94,7 +102,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
|
||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity entity)
|
||||
public async Task<Stream?> OpenReadAsync(FilesIndexItem entity)
|
||||
{
|
||||
if (entity == null) return null;
|
||||
|
||||
|
||||
@@ -42,29 +42,22 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_client = new SMB2Client();
|
||||
_client = new SMB2Client();
|
||||
|
||||
// 连接主机
|
||||
bool connected = _client.Connect(_config.UriHost, SMBTransportType.DirectTCPTransport);
|
||||
if (!connected) return false;
|
||||
// 连接主机
|
||||
bool connected = _client.Connect(_config.UriHost, SMBTransportType.DirectTCPTransport);
|
||||
if (!connected) return false;
|
||||
|
||||
// 登录
|
||||
var status = _client.Login(string.Empty, _config.UserName, _config.Password);
|
||||
if (status != NTStatus.STATUS_SUCCESS) return false;
|
||||
// 登录
|
||||
var status = _client.Login(string.Empty, _config.UserName, _config.Password);
|
||||
if (status != NTStatus.STATUS_SUCCESS) return false;
|
||||
|
||||
// 连接共享目录 (TreeConnect)
|
||||
// SMBLibrary 必须先连接到 Share,后续所有文件操作都是基于这个 Share 的相对路径
|
||||
if (string.IsNullOrEmpty(_shareName)) return false;
|
||||
// 连接共享目录 (TreeConnect)
|
||||
// SMBLibrary 必须先连接到 Share,后续所有文件操作都是基于这个 Share 的相对路径
|
||||
if (string.IsNullOrEmpty(_shareName)) return false;
|
||||
|
||||
_fileStore = _client.TreeConnect(_shareName, out status);
|
||||
return status == NTStatus.STATUS_SUCCESS;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_fileStore = _client.TreeConnect(_shareName, out status);
|
||||
return status == NTStatus.STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,9 +67,9 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
/// 传入要列出的文件夹实体。
|
||||
/// 如果传入 null,则默认列出 MediaFolder 配置的根目录。
|
||||
/// </param>
|
||||
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
|
||||
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
|
||||
{
|
||||
var result = new List<FileCacheEntity>();
|
||||
var result = new List<FilesIndexItem>();
|
||||
if (_fileStore == null) return result;
|
||||
|
||||
string smbPath = GetPathRelativeToShare(parentFolder);
|
||||
@@ -128,7 +121,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
var baseUri = new Uri(parentUriString);
|
||||
var newUri = new Uri(baseUri, item.FileName);
|
||||
|
||||
result.Add(new FileCacheEntity
|
||||
result.Add(new FilesIndexItem
|
||||
{
|
||||
MediaFolderId = _config.Id,
|
||||
ParentUri = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri,
|
||||
@@ -155,7 +148,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
/// 打开文件流
|
||||
/// </summary>
|
||||
/// <param name="file">只需要传入文件实体即可</param>
|
||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity file)
|
||||
public async Task<Stream?> OpenReadAsync(FilesIndexItem file)
|
||||
{
|
||||
if (_fileStore == null || file == null) return null;
|
||||
|
||||
@@ -181,7 +174,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
_client?.Disconnect();
|
||||
}
|
||||
|
||||
private string GetPathRelativeToShare(FileCacheEntity? entity)
|
||||
private string GetPathRelativeToShare(FilesIndexItem? entity)
|
||||
{
|
||||
Uri targetUri;
|
||||
|
||||
|
||||
@@ -36,22 +36,13 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 测试连接:Propfind 请求配置的根路径
|
||||
// GetStandardUri 已经包含了用户设置的路径
|
||||
var result = await _client.Propfind(_config.GetStandardUri().AbsoluteUri);
|
||||
return result.IsSuccessful;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = await _client.Propfind(_config.GetStandardUri().AbsoluteUri);
|
||||
return result.IsSuccessful;
|
||||
}
|
||||
|
||||
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
|
||||
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
|
||||
{
|
||||
var list = new List<FileCacheEntity>();
|
||||
var list = new List<FilesIndexItem>();
|
||||
|
||||
Uri targetUri;
|
||||
if (parentFolder == null)
|
||||
@@ -98,7 +89,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
if (string.IsNullOrEmpty(extension) || !FileHelper.AllSupportedExtensions.Contains(extension)) continue;
|
||||
}
|
||||
|
||||
list.Add(new FileCacheEntity
|
||||
list.Add(new FilesIndexItem
|
||||
{
|
||||
MediaFolderId = _config.Id,
|
||||
|
||||
@@ -118,7 +109,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity entity)
|
||||
public async Task<Stream?> OpenReadAsync(FilesIndexItem entity)
|
||||
{
|
||||
if (entity == null) return null;
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
int maxScore = 0;
|
||||
|
||||
FileCacheEntity? bestFileEntity = null;
|
||||
FilesIndexItem? bestFileEntity = null;
|
||||
MediaFolder? bestFolderConfig = null;
|
||||
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
@@ -345,7 +345,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
|
||||
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
|
||||
|
||||
FileCacheEntity? bestFile = null;
|
||||
FilesIndexItem? bestFile = null;
|
||||
int maxScore = 0;
|
||||
|
||||
foreach (var item in allFiles)
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
{
|
||||
public interface IPlayHistoryService
|
||||
{
|
||||
Task InitializeAsync();
|
||||
Task AddLogAsync(PlayHistoryItem item);
|
||||
Task<List<PlayHistoryItem>> GetRecentLogsAsync(int limit = 50);
|
||||
Task<List<PlayHistoryItem>> GetLogsByDateRangeAsync(DateTime start, DateTime end);
|
||||
|
||||
@@ -1,157 +1,155 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Db;
|
||||
using BetterLyrics.WinUI3.Models.Stats;
|
||||
using SQLite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
{
|
||||
public class PlayHistoryService : IPlayHistoryService
|
||||
{
|
||||
private SQLiteAsyncConnection _db;
|
||||
private readonly string _dbPath;
|
||||
private readonly IDbContextFactory<PlayHistoryDbContext> _contextFactory;
|
||||
|
||||
public PlayHistoryService()
|
||||
public PlayHistoryService(IDbContextFactory<PlayHistoryDbContext> contextFactory)
|
||||
{
|
||||
_dbPath = PathHelper.PlayHistoryPath;
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
if (_db != null) return;
|
||||
|
||||
_db = new SQLiteAsyncConnection(_dbPath);
|
||||
await _db.CreateTableAsync<PlayHistoryItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一条播放记录
|
||||
/// </summary>
|
||||
public async Task AddLogAsync(PlayHistoryItem item)
|
||||
{
|
||||
await InitializeAsync();
|
||||
// 再次确保这里是 UTC 时间,方便跨时区统计
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// 确保 UTC
|
||||
if (item.StartedAt.Kind != DateTimeKind.Utc)
|
||||
{
|
||||
item.StartedAt = item.StartedAt.ToUniversalTime();
|
||||
}
|
||||
await _db.InsertAsync(item);
|
||||
|
||||
context.PlayHistory.Add(item);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近的播放记录 (用于“最近播放”列表)
|
||||
/// </summary>
|
||||
public async Task<List<PlayHistoryItem>> GetRecentLogsAsync(int limit = 50)
|
||||
{
|
||||
await InitializeAsync();
|
||||
return await _db.Table<PlayHistoryItem>()
|
||||
.OrderByDescending(x => x.StartedAt)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
return await context.PlayHistory
|
||||
.AsNoTracking() // 读操作,不需要追踪状态,提升性能
|
||||
.OrderByDescending(x => x.StartedAt)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取特定时间段的所有原始记录 (用于生成复杂的图表,如 Hourly Heatmap)
|
||||
/// </summary>
|
||||
public async Task<List<PlayHistoryItem>> GetLogsByDateRangeAsync(DateTime start, DateTime end)
|
||||
{
|
||||
await InitializeAsync();
|
||||
return await _db.Table<PlayHistoryItem>()
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.ToListAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
return await context.PlayHistory
|
||||
.AsNoTracking()
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计时间段内 Top N 歌曲
|
||||
/// </summary>
|
||||
public async Task<List<SongPlayCount>> GetTopSongsAsync(DateTime start, DateTime end, int limit = 10)
|
||||
{
|
||||
await InitializeAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// SQLite 语法: Group By Title 和 Artist
|
||||
string query = @"
|
||||
SELECT Title, Artist, COUNT(*) as PlayCount
|
||||
FROM PlayHistory
|
||||
WHERE StartedAt >= ? AND StartedAt <= ?
|
||||
GROUP BY Title, Artist
|
||||
ORDER BY PlayCount DESC
|
||||
LIMIT ?";
|
||||
|
||||
return await _db.QueryAsync<SongPlayCount>(query, start, end, limit);
|
||||
// EF Core 会自动将这个 LINQ 翻译成高效的 GROUP BY SQL
|
||||
return await context.PlayHistory
|
||||
.AsNoTracking()
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.GroupBy(x => new { x.Title, x.Artist }) // 组合分组
|
||||
.Select(g => new SongPlayCount
|
||||
{
|
||||
Title = g.Key.Title,
|
||||
Artist = g.Key.Artist,
|
||||
PlayCount = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.PlayCount)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计时间段内 Top N 歌手
|
||||
/// </summary>
|
||||
public async Task<List<ArtistPlayCount>> GetTopArtistsAsync(DateTime start, DateTime end, int limit = 10)
|
||||
{
|
||||
await InitializeAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// 同时统计播放次数和总播放时长(秒)
|
||||
string query = @"
|
||||
SELECT Artist, COUNT(*) as PlayCount, SUM(DurationPlayedMs)/1000.0 as TotalDurationSeconds
|
||||
FROM PlayHistory
|
||||
WHERE StartedAt >= ? AND StartedAt <= ?
|
||||
GROUP BY Artist
|
||||
ORDER BY PlayCount DESC
|
||||
LIMIT ?";
|
||||
|
||||
return await _db.QueryAsync<ArtistPlayCount>(query, start, end, limit);
|
||||
return await context.PlayHistory
|
||||
.AsNoTracking()
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.GroupBy(x => x.Artist)
|
||||
.Select(g => new ArtistPlayCount
|
||||
{
|
||||
Artist = g.Key,
|
||||
PlayCount = g.Count(),
|
||||
// 注意:SQLite 存储 double 精度,这里求和后转秒
|
||||
TotalDurationSeconds = g.Sum(x => x.DurationPlayedMs) / 1000.0
|
||||
})
|
||||
.OrderByDescending(x => x.PlayCount)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取总听歌时长
|
||||
/// </summary>
|
||||
public async Task<TimeSpan> GetTotalListeningDurationAsync(DateTime start, DateTime end)
|
||||
{
|
||||
await InitializeAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
var result = await _db.ExecuteScalarAsync<double>(
|
||||
"SELECT SUM(DurationPlayedMs) FROM PlayHistory WHERE StartedAt >= ? AND StartedAt <= ?",
|
||||
start, end);
|
||||
var totalMs = await context.PlayHistory
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.SumAsync(x => x.DurationPlayedMs); // 直接在数据库层面求和
|
||||
|
||||
return TimeSpan.FromMilliseconds(result);
|
||||
return TimeSpan.FromMilliseconds(totalMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取播放器来源分布
|
||||
/// </summary>
|
||||
public async Task<List<PlayerStats>> GetPlayerDistributionAsync(DateTime start, DateTime end)
|
||||
{
|
||||
await InitializeAsync();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
string query = @"
|
||||
SELECT PlayerId, COUNT(*) as Count
|
||||
FROM PlayHistory
|
||||
WHERE StartedAt >= ? AND StartedAt <= ?
|
||||
GROUP BY PlayerId
|
||||
ORDER BY Count DESC";
|
||||
|
||||
return await _db.QueryAsync<PlayerStats>(query, start, end);
|
||||
return await context.PlayHistory
|
||||
.AsNoTracking()
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.GroupBy(x => x.PlayerId)
|
||||
.Select(g => new PlayerStats
|
||||
{
|
||||
PlayerId = g.Key,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderByDescending(x => x.Count)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteLogAsync(int id)
|
||||
{
|
||||
await InitializeAsync();
|
||||
await _db.DeleteAsync<PlayHistoryItem>(id);
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// EF Core 删除需要先查询,或者使用 ExecuteDeleteAsync (EF Core 7+)
|
||||
// 写法 1 (传统):
|
||||
// var item = await context.PlayHistory.FindAsync(id);
|
||||
// if (item != null) { context.PlayHistory.Remove(item); await context.SaveChangesAsync(); }
|
||||
|
||||
// 写法 2 (EF Core 7.0+ 高效写法,直接生成 DELETE SQL):
|
||||
await context.PlayHistory
|
||||
.Where(x => x.Id == id)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
|
||||
public async Task ClearHistoryAsync()
|
||||
{
|
||||
await InitializeAsync();
|
||||
await _db.DeleteAllAsync<PlayHistoryItem>();
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// 高效清空表
|
||||
await context.PlayHistory.ExecuteDeleteAsync();
|
||||
}
|
||||
|
||||
public async Task GenerateTestDataAsync(int count = 100)
|
||||
{
|
||||
// 这里的逻辑稍微重构了一下,使用批量插入提升性能
|
||||
var random = new Random();
|
||||
|
||||
var presetSongs = new List<(string Title, string Artist, string Album)>
|
||||
{
|
||||
// --- 欧美流行 ---
|
||||
("Anti-Hero", "Taylor Swift", "Midnights"),
|
||||
("Cruel Summer", "Taylor Swift", "Lover"),
|
||||
("Blank Space", "Taylor Swift", "1989"),
|
||||
@@ -164,8 +162,6 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
("Bad Guy", "Billie Eilish", "When We All Fall Asleep, Where Do We Go?"),
|
||||
("Flowers", "Miley Cyrus", "Endless Summer Vacation"),
|
||||
("Stay", "The Kid LAROI & Justin Bieber", "F*ck Love 3: Over You"),
|
||||
|
||||
// --- 华语流行 ---
|
||||
("七里香", "周杰伦", "七里香"),
|
||||
("晴天", "周杰伦", "叶惠美"),
|
||||
("一路向北", "周杰伦", "11月的肖邦"),
|
||||
@@ -179,8 +175,6 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
("泡沫", "G.E.M. 邓紫棋", "Xposed"),
|
||||
("因为爱情", "王菲 & 陈奕迅", "Stranger Under My Skin"),
|
||||
("红豆", "王菲", "唱游"),
|
||||
|
||||
// --- 摇滚/经典 ---
|
||||
("Bohemian Rhapsody", "Queen", "A Night at the Opera"),
|
||||
("Don't Stop Me Now", "Queen", "Jazz"),
|
||||
("Numb", "Linkin Park", "Meteora"),
|
||||
@@ -189,8 +183,6 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
("Viva La Vida", "Coldplay", "Viva La Vida"),
|
||||
("Smells Like Teen Spirit", "Nirvana", "Nevermind"),
|
||||
("Hotel California", "Eagles", "Hotel California"),
|
||||
|
||||
// --- 日韩/二次元 ---
|
||||
("Lemon", "米津玄師", "Lemon"),
|
||||
("Kick Back", "米津玄師", "KICK BACK"),
|
||||
("アイドル", "YOASOBI", "アイドル"),
|
||||
@@ -200,59 +192,49 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
("Butter", "BTS", "Butter"),
|
||||
("How You Like That", "BLACKPINK", "The Album"),
|
||||
("Ditto", "NewJeans", "OMG"),
|
||||
|
||||
// --- 电子/纯音乐 ---
|
||||
("Get Lucky", "Daft Punk", "Random Access Memories"),
|
||||
("The Nights", "Avicii", "The Days / Nights"),
|
||||
("Summer", "Calvin Harris", "Motion"),
|
||||
};
|
||||
|
||||
var playerIds = new[] {
|
||||
"Spotify", "Spotify", "Spotify",
|
||||
"MusicBee", "MusicBee",
|
||||
"QQMusic",
|
||||
"NeteaseCloudMusic",
|
||||
"AppleMusic"
|
||||
};
|
||||
var playerIds = new[] { "Spotify", "Spotify", "Spotify", "MusicBee", "MusicBee", "QQMusic", "NeteaseCloudMusic", "AppleMusic" };
|
||||
|
||||
int addedCount = 0;
|
||||
var batchList = new List<PlayHistoryItem>();
|
||||
|
||||
while (addedCount < count)
|
||||
// 我们尝试生成 count 条有效数据
|
||||
// 为了防止死循环,加个硬上限
|
||||
int attempts = 0;
|
||||
while (batchList.Count < count && attempts < count * 5)
|
||||
{
|
||||
attempts++;
|
||||
var song = presetSongs[random.Next(presetSongs.Count)];
|
||||
|
||||
var playerId = playerIds[random.Next(playerIds.Length)];
|
||||
|
||||
// 生成时间:过去 365 天内均匀分布
|
||||
var daysBack = random.Next(0, 365);
|
||||
var hoursBack = random.Next(0, 24);
|
||||
var minutesBack = random.Next(0, 60);
|
||||
var secondsBack = random.Next(0, 60);
|
||||
|
||||
var startedAt = DateTime.Now
|
||||
var startedAt = DateTime.UtcNow // 直接用 UTC
|
||||
.AddDays(-daysBack)
|
||||
.AddHours(-hoursBack)
|
||||
.AddMinutes(-minutesBack)
|
||||
.AddSeconds(-secondsBack);
|
||||
|
||||
// 歌曲总时长 (3分钟 - 5分钟)
|
||||
var totalDurationMs = random.Next(180, 300) * 1000.0;
|
||||
|
||||
// 模拟听歌习惯:
|
||||
// 70% 的概率是听完的 (0.9 - 1.0)
|
||||
// 20% 的概率是切歌 (0.3 - 0.8)
|
||||
// 10% 的概率是刚听就切了 (0.05 - 0.3)
|
||||
double playedRatio;
|
||||
double roll = random.NextDouble();
|
||||
if (roll > 0.3) playedRatio = 0.9 + (random.NextDouble() * 0.1); // 听完
|
||||
else if (roll > 0.1) playedRatio = 0.3 + (random.NextDouble() * 0.5); // 听一半
|
||||
else playedRatio = 0.05 + (random.NextDouble() * 0.25); // 秒切
|
||||
|
||||
if (roll > 0.3) playedRatio = 0.9 + (random.NextDouble() * 0.1);
|
||||
else if (roll > 0.1) playedRatio = 0.3 + (random.NextDouble() * 0.5);
|
||||
else playedRatio = 0.05 + (random.NextDouble() * 0.25);
|
||||
|
||||
var playedDurationMs = totalDurationMs * playedRatio;
|
||||
|
||||
// 只有听了一半以上的才算作记录
|
||||
if (playedDurationMs >= (totalDurationMs / 2))
|
||||
{
|
||||
var item = new PlayHistoryItem
|
||||
batchList.Add(new PlayHistoryItem
|
||||
{
|
||||
Title = song.Title,
|
||||
Artist = song.Artist,
|
||||
@@ -261,13 +243,16 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
StartedAt = startedAt,
|
||||
TotalDurationMs = totalDurationMs,
|
||||
DurationPlayedMs = playedDurationMs
|
||||
};
|
||||
|
||||
await AddLogAsync(item);
|
||||
addedCount++; // 只有成功写入才计数
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (batchList.Count > 0)
|
||||
{
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
await context.PlayHistory.AddRangeAsync(batchList);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>كل الموسيقى</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>البيانات قيد المعالجة. يمكنك التحقق من التقدم المفصل في الإعدادات.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>مسح قائمة الانتظار</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>مدير الإعدادات</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>تصفح مركز مشاركة الموارد عبر الإنترنت</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Alle Musikstücke</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Die Daten werden gerade verarbeitet. Sie können den genauen Fortschritt in den Einstellungen überprüfen.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Warteschlange leeren</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Einstellungsmanager</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Online Share Hub durchsuchen</value>
|
||||
</data>
|
||||
|
||||
@@ -414,8 +414,11 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>All Music</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>The data is being processed. You can check the detailed progress in the settings.</value>
|
||||
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
|
||||
<value>Media Library sync in progress...</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value>There is a problem with Media Library sync</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Clear queue</value>
|
||||
@@ -700,7 +703,7 @@
|
||||
<value>Blur Amount</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>Includes log files, online lyrics cache</value>
|
||||
<value>Includes log files, online lyrics cache, media library index</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>Cache</value>
|
||||
@@ -855,6 +858,9 @@
|
||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||
<value>Exit app when lyrics window is closed</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||
<value>Export play history</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||
<value>Export Settings</value>
|
||||
</data>
|
||||
@@ -1308,6 +1314,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Settings Manager</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Browse Online Share Hub</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Toda la música</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Los datos se están procesando. Puedes consultar el progreso detallado en los ajustes.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Borrar cola</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Gestor de configuración</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Explorar Centro de recursos en línea</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Toute la musique</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Les données sont en cours de traitement. Vous pouvez vérifier l'état d'avancement détaillé dans les paramètres.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Vider la file d'attente</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Gestionnaire de paramètres</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Parcourir le Hub de partage en ligne</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>सभी गाने</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>डेटा प्रोसेस हो रहा है। आप सेटिंग्स में डिटेल्ड प्रोग्रेस देख सकते हैं।</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>प्लेइंग कतार साफ़ करें</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>सेटिंग्स प्रबंधक</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>ऑनलाइन संसाधन शेयरिंग हब ब्राउज़ करें</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Semua Musik</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Data sedang diproses. Anda dapat memeriksa kemajuan terperinci dalam pengaturan.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Bersihkan antrean putar</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Manajer Pengaturan</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Jelajahi Hub Berbagi Sumber Daya Online</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>すべてのミュージック</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>データ処理中です。詳細な進捗状況は設定で確認できます。</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>キューをクリア</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>設定マネージャー</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>オンライン共有センターを閲覧する</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>모든 음악</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>데이터가 처리 중입니다. 설정에서 자세한 진행 상황을 확인할 수 있습니다.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>재생 대기열 비우기</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>설정 관리자</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>온라인 공유 허브 탐색</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Semua Muzik</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Data sedang diproses. Anda boleh menyemak kemajuan terperinci dalam tetapan.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Kosongkan baris gilir main</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Pengurus Tetapan</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Layari Hab Perkongsian Sumber Dalam Talian</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Todas as Músicas</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Os dados estão a ser processados. Pode verificar o progresso detalhado nas definições.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Limpar fila de reprodução</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Gestor de Definições</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Explorar o Centro de Partilha de Recursos Online</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Вся музыка</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Данные находятся в процессе обработки. Подробную информацию о ходе обработки можно посмотреть в настройках.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Очистить очередь воспроизведения</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Менеджер настроек</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Просмотреть онлайн-центр обмена</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>เพลงทั้งหมด</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>กำลังประมวลผลข้อมูลอยู่ คุณสามารถตรวจสอบความคืบหน้าโดยละเอียดได้ในการตั้งค่า</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>ล้างคิวการเล่น</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>ตัวจัดการการตั้งค่า</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>เรียกดูศูนย์แบ่งปันทรัพยากรออนไลน์</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>Tất cả bài hát</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>Dữ liệu đang được xử lý. Bạn có thể kiểm tra tiến độ chi tiết trong phần cài đặt.</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Xóa hàng đợi phát</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Trình quản lý cài đặt</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>Duyệt trung tâm chia sẻ tài nguyên trực tuyến</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,11 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>所有音乐</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>数据正在处理中。您可以在设置中查看详细进度。</value>
|
||||
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
|
||||
<value>正在同步媒体库...</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value>同步媒体库时出现问题</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>清除播放队列</value>
|
||||
@@ -700,7 +703,7 @@
|
||||
<value>模糊量</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>包括日志文件,网络歌词缓存</value>
|
||||
<value>包括日志文件,网络歌词缓存,媒体库索引</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>缓存</value>
|
||||
@@ -855,6 +858,9 @@
|
||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||
<value>关闭歌词窗口时退出程序</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||
<value>导出设置</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -414,8 +414,8 @@
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>所有音樂</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataLoading.Message" xml:space="preserve">
|
||||
<value>資料正在處理中。您可以在設定中檢查詳細進度。</value>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>清除播放佇列</value>
|
||||
@@ -1308,6 +1308,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>設定管理器</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>瀏覽線上資源共享中心</value>
|
||||
</data>
|
||||
|
||||
@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
@@ -74,6 +75,19 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ExportPlayHistoryAsync()
|
||||
{
|
||||
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
var dest = Path.Combine(folder.Path, $"BetterLyrics_Play_History_Export_{DateTime.Now:yyyyMMdd_HHmmss}.db");
|
||||
await FileHelper.CopyFileAsync(PathHelper.PlayHistoryPath, dest);
|
||||
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearCacheFiles()
|
||||
{
|
||||
|
||||
@@ -92,7 +92,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public SongsTabInfo? SelectedSongsTabInfo => AppSettings.StarredPlaylists.ElementAtOrDefault(SelectedSongsTabInfoIndex);
|
||||
|
||||
[ObservableProperty] public partial bool IsDataLoading { get; set; } = false;
|
||||
[ObservableProperty] public partial bool IsDataSyncing { get; set; } = false;
|
||||
[ObservableProperty] public partial bool IsDataSyncError { get; set; } = false;
|
||||
|
||||
[ObservableProperty] public partial ExtendedTrack TrackRightTapped { get; set; } = new();
|
||||
|
||||
@@ -121,6 +122,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
RefreshSongs();
|
||||
|
||||
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
|
||||
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
|
||||
|
||||
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
||||
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
||||
@@ -138,6 +140,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
IsDataSyncError = AppSettings.LocalMediaFolders.Any(x => x.StatusSeverity == InfoBarSeverity.Error);
|
||||
}
|
||||
|
||||
private void TrackPlayingQueue_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueuePaths = [.. TrackPlayingQueue.Select(x => x.Track.DecodedAbsoluteUri)];
|
||||
@@ -495,7 +502,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
await _currentProvider.ConnectAsync();
|
||||
|
||||
var fileCacheStub = new FileCacheEntity
|
||||
var fileCacheStub = new FilesIndexItem
|
||||
{
|
||||
Uri = PlayingTrack.Uri
|
||||
};
|
||||
@@ -656,7 +663,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else if (message.PropertyName == nameof(MediaFolder.IsProcessing))
|
||||
{
|
||||
IsDataLoading = message.NewValue;
|
||||
IsDataSyncing = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
</Flyout>
|
||||
</Grid.Tag>
|
||||
|
||||
<StackPanel Grid.Row="1" Spacing="6">
|
||||
<StackPanel Grid.Row="0" Spacing="6">
|
||||
|
||||
<AutoSuggestBox
|
||||
x:Name="SongSearchBox"
|
||||
@@ -306,10 +306,17 @@
|
||||
</StackPanel>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="MusicGalleryPageDataLoading"
|
||||
Grid.Row="2"
|
||||
x:Uid="MusicGalleryPageDataSync"
|
||||
Grid.Row="1"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" />
|
||||
IsOpen="{x:Bind ViewModel.IsDataSyncing, Mode=OneWay}" />
|
||||
|
||||
<InfoBar
|
||||
x:Uid="MusicGalleryPageDataSyncError"
|
||||
Grid.Row="1"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsDataSyncError, Mode=OneWay}"
|
||||
Severity="Error" />
|
||||
|
||||
<SemanticZoom Grid.Row="2">
|
||||
<SemanticZoom.ZoomedInView>
|
||||
|
||||
Reference in New Issue
Block a user