refactor: 重构数据库并优化下载列表相关逻辑

This commit is contained in:
yaobiao131
2025-06-16 21:02:26 +08:00
parent 58d66986c8
commit 31dfd6ffaf
24 changed files with 912 additions and 731 deletions

View File

@@ -5,8 +5,9 @@
<PackageVersion Include="Avalonia.Desktop" Version="11.3.0" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.0" />
<PackageVersion Include="Avalonia.Themes.Simple" Version="11.3.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.5" />
<PackageVersion Include="SqlSugarCoreNoDrive" Version="5.1.4.193" />
<PackageVersion Include="FreeSql" Version="3.5.207" />
<PackageVersion Include="FreeSql.DbContext" Version="3.5.207" />
<PackageVersion Include="FreeSql.Provider.SqliteCore" Version="3.5.207" />
<PackageVersion Include="System.Formats.Nrbf" Version="9.0.5" />
<PackageVersion Include="Xaml.Behaviors" Version="11.3.0" />
<PackageVersion Include="Downloader" Version="3.3.4" />

View File

@@ -15,7 +15,6 @@
<PackageReference Include="Avalonia"/>
<PackageReference Include="FFMpegCore"/>
<PackageReference Include="Google.Protobuf"/>
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Microsoft.Data.Sqlite.Core"/>
<PackageReference Include="Newtonsoft.Json"/>
<PackageReference Include="QRCoder"/>

View File

@@ -29,9 +29,9 @@ using DownKyi.Views.Friends;
using DownKyi.Views.Settings;
using DownKyi.Views.Toolbox;
using DownKyi.Views.UserSpace;
using FreeSql;
using Prism.DryIoc;
using Prism.Ioc;
using SqlSugar;
using ViewSeasonsSeries = DownKyi.Views.ViewSeasonsSeries;
using ViewSeasonsSeriesViewModel = DownKyi.ViewModels.ViewSeasonsSeriesViewModel;
@@ -41,7 +41,7 @@ public partial class App : PrismApplication
{
public const string RepoOwner = "yaobiao131";
public const string RepoName = "downkyicore";
public static ObservableCollection<DownloadingItem> DownloadingList { get; set; } = new();
public static ObservableCollection<DownloadedItem> DownloadedList { get; set; } = new();
public new static App Current => (App)Application.Current!;
@@ -49,18 +49,19 @@ public partial class App : PrismApplication
public IClassicDesktopStyleApplicationLifetime? AppLife;
private static Mutex _mutex;
// 下载服务
private IDownloadService? _downloadService;
public override void Initialize()
{
#if !DEBUG
#if !DEBUG
_mutex = new Mutex(true, "Global\\DownKyi", out var createdNew);
if (!createdNew)
{
Environment.Exit(0);
}
#endif
#endif
AvaloniaXamlLoader.Load(this);
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
@@ -74,37 +75,33 @@ public partial class App : PrismApplication
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<ISqlSugarClient>(() => new SqlSugarScope(new ConnectionConfig
{
DbType = DbType.Sqlite,
ConnectionString = $"datasource={StorageManager.GetDbPath()}",
IsAutoCloseConnection = true,
},
db =>
{
db.Aop.OnLogExecuting = (sql, pars) =>
{
if (pars != null && pars.Length > 0)
{
string fullSql = sql;
foreach (var param in pars)
{
// 处理参数值的格式
var value = param.Value is string || param.Value is DateTime
? $"'{param.Value}'"
: param.Value?.ToString() ?? "NULL";
fullSql = fullSql.Replace(param.ParameterName, value);
}
Console.WriteLine($"完整SQL: {fullSql}");
}
else
{
Console.WriteLine($"SQL: {sql}");
}
};
})
);
containerRegistry.RegisterSingleton<IFreeSql>(() =>
{
var freeSql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite, $"Data Source={StorageManager.GetDbPath()}")
.UseAdoConnectionPool(true)
#if DEBUG
.UseMonitorCommand(cmd => Console.WriteLine($"Sql{cmd.CommandText}"))
#endif
.Build();
freeSql.UseJsonMap();
return freeSql;
});
containerRegistry.RegisterSingleton<DownloadStorageService>();
containerRegistry.RegisterScoped<IBaseRepository<Downloading>>(cp =>
{
var freeSql = (IFreeSql)cp.Resolve(typeof(IFreeSql));
var downloadingRepository = freeSql.GetRepository<Downloading>();
downloadingRepository.DbContextOptions.EnableCascadeSave = true;
return downloadingRepository;
});
containerRegistry.RegisterScoped<IBaseRepository<Downloaded>>(cp =>
{
var freeSql = (IFreeSql)cp.Resolve(typeof(IFreeSql));
var downloadRepository = freeSql.GetRepository<Downloaded>();
downloadRepository.DbContextOptions.EnableCascadeSave = true;
return downloadRepository;
});
containerRegistry.RegisterSingleton<MainWindow>();
containerRegistry.RegisterSingleton<IDialogService, DialogService>();
@@ -169,11 +166,11 @@ public partial class App : PrismApplication
{
if (!Design.IsDesignMode)
{
Container.Resolve<ISqlSugarClient>().CodeFirst.InitTables(typeof(DownloadBase), typeof(Downloaded), typeof(Downloading));
Container.Resolve<IFreeSql>().CodeFirst.SyncStructure(typeof(DownloadBase), typeof(Downloaded), typeof(Downloading));
}
// 下载数据存储服务
var downloadStorageService = new DownloadStorageService();
var downloadStorageService = Container.Resolve<DownloadStorageService>();
// 从数据库读取
var downloadingItems = downloadStorageService.GetDownloading();
@@ -217,37 +214,37 @@ public partial class App : PrismApplication
};
// 下载完成列表发生变化时执行的任务
DownloadedList.CollectionChanged += async (sender, e) =>
{
await Task.Run(() =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (e.NewItems == null) return;
foreach (var item in e.NewItems)
{
if (item is DownloadedItem downloaded)
{
//Console.WriteLine("DownloadedList添加");
downloadStorageService.AddDownloaded(downloaded);
}
}
}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
if (e.OldItems == null) return;
foreach (var item in e.OldItems)
{
if (item is DownloadedItem downloaded)
{
//Console.WriteLine("DownloadedList移除");
downloadStorageService.RemoveDownloaded(downloaded);
}
}
}
});
};
// DownloadedList.CollectionChanged += async (sender, e) =>
// {
// await Task.Run(() =>
// {
// if (e.Action == NotifyCollectionChangedAction.Add)
// {
// if (e.NewItems == null) return;
// foreach (var item in e.NewItems)
// {
// if (item is DownloadedItem downloaded)
// {
// //Console.WriteLine("DownloadedList添加");
// downloadStorageService.AddDownloaded(downloaded);
// }
// }
// }
//
// if (e.Action == NotifyCollectionChangedAction.Remove)
// {
// if (e.OldItems == null) return;
// foreach (var item in e.OldItems)
// {
// if (item is DownloadedItem downloaded)
// {
// //Console.WriteLine("DownloadedList移除");
// downloadStorageService.RemoveDownloaded(downloaded);
// }
// }
// }
// });
// };
// 启动下载服务
var download = SettingsManager.GetInstance().GetDownloader();
@@ -295,33 +292,43 @@ public partial class App : PrismApplication
/// <param name="finishedSort"></param>
public static void SortDownloadedList(DownloadFinishedSort finishedSort)
{
var list = DownloadedList?.ToList();
var list = DownloadedList.ToList();
switch (finishedSort)
{
case DownloadFinishedSort.DownloadAsc:
// 按下载先后排序
list?.Sort((x, y) => x.Downloaded.FinishedTimestamp.CompareTo(y.Downloaded.FinishedTimestamp));
list.Sort((x, y) => x.Downloaded.FinishedTimestamp.CompareTo(y.Downloaded.FinishedTimestamp));
break;
case DownloadFinishedSort.DownloadDesc:
// 按下载先后排序
list?.Sort((x, y) => y.Downloaded.FinishedTimestamp.CompareTo(x.Downloaded.FinishedTimestamp));
list.Sort((x, y) => y.Downloaded.FinishedTimestamp.CompareTo(x.Downloaded.FinishedTimestamp));
break;
case DownloadFinishedSort.Number:
// 按序号排序
list?.Sort((x, y) =>
list.Sort((x, y) =>
{
var compare = string.Compare(x.MainTitle, y.MainTitle, StringComparison.Ordinal);
return compare == 0 ? x.Order.CompareTo(y.Order) : compare;
});
break;
case DownloadFinishedSort.NotSet:
default:
break;
}
// 更新下载完成列表
// 如果有更好的方法再重写
DownloadedList?.Clear();
list?.ForEach(item => DownloadedList?.Add(item));
DownloadedList.Clear();
list.ForEach(item => DownloadedList.Add(item));
}
public void RefreshDownloadedList()
{
// 重新获取下载完成列表
var downloadStorageService = Container.Resolve<DownloadStorageService>();
var downloadedItems = downloadStorageService.GetDownloaded();
DownloadedList.Clear();
DownloadedList.AddRange(downloadedItems);
}
private void OnExit(object sender, ControlledApplicationLifetimeExitEventArgs e)

View File

@@ -31,9 +31,11 @@
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics"/>
<PackageReference Include="Avalonia.Themes.Simple"/>
<PackageReference Include="Downloader"/>
<PackageReference Include="FreeSql" />
<PackageReference Include="FreeSql.DbContext" />
<PackageReference Include="FreeSql.Provider.SqliteCore" />
<PackageReference Include="Prism.Avalonia"/>
<PackageReference Include="Prism.DryIoc.Avalonia"/>
<PackageReference Include="SqlSugarCoreNoDrive" />
<PackageReference Include="Xaml.Behaviors"/>
<PackageReference Include="System.Formats.Nrbf" />
</ItemGroup>
@@ -42,10 +44,4 @@
<ItemGroup>
<ProjectReference Include="..\DownKyi.Core\DownKyi.Core.csproj"/>
</ItemGroup>
<ItemGroup>
<None Update="Linker.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using DownKyi.Core.BiliApi.BiliUtils;
using DownKyi.Utils.DataAnnotations;
using FreeSql.DataAnnotations;
namespace DownKyi.Models;
[Serializable]
[SqlSugar.SugarTable("download_base", TableDescription = "下载项的基础信息")]
[Table(Name = "download_base")]
[Description("下载项的基础信息")]
public class DownloadBase
{
public DownloadBase()
@@ -25,75 +28,75 @@ public class DownloadBase
}
// 此条下载项的id
[SqlSugar.SugarColumn(IsPrimaryKey = true, ColumnName = "id")]
[Column(IsPrimary = true, Name = "id")]
public string Id { get; set; }
// 需要下载的内容
[SqlSugar.SugarColumn(IsJson = true, ColumnName = "need_download_content")]
[Column(Name = "need_download_content"),]
public Dictionary<string, bool> NeedDownloadContent { get; set; }
// 视频的id
[SqlSugar.SugarColumn(ColumnName = "bvid")]
public string Bvid { get; set; }
[Column(Name = "bvid")] public string Bvid { get; set; }
[SqlSugar.SugarColumn(ColumnName = "avid")]
public long Avid { get; set; }
[Column(Name = "avid")] public long Avid { get; set; }
[SqlSugar.SugarColumn(ColumnName = "cid")]
public long Cid { get; set; }
[Column(Name = "cid")] public long Cid { get; set; }
[SqlSugar.SugarColumn(ColumnName = "episode_id")]
public long EpisodeId { get; set; }
[Column(Name = "episode_id")] public long EpisodeId { get; set; }
// 视频封面的url
[SqlSugar.SugarColumn(ColumnName = "cover_url", ColumnDescription = "视频封面的url")]
[Column(Name = "cover_url"), Description("视频封面的url")]
public string CoverUrl { get; set; }
// 视频page的封面的url
[SqlSugar.SugarColumn(ColumnName = "page_cover_url", ColumnDescription = "视频page的封面的url")]
[Column(Name = "page_cover_url"), Description("视频page的封面的url")]
public string PageCoverUrl { get; set; }
// 分区id
[SqlSugar.SugarColumn(ColumnName = "zone_id", ColumnDescription = "分区id")]
[Column(Name = "zone_id"), Description("分区id")]
public int ZoneId { get; set; }
// 视频序号
[SqlSugar.SugarColumn(ColumnName = "order", ColumnDescription = "视频序号")]
[Column(Name = "order"), Description("视频序号")]
public int Order { get; set; }
// 视频主标题
[SqlSugar.SugarColumn(ColumnName = "main_title", ColumnDescription = "视频主标题")]
[Column(Name = "main_title"), Description("视频主标题")]
public string MainTitle { get; set; }
// 视频标题
[SqlSugar.SugarColumn(ColumnName = "name", ColumnDescription = "视频标题")]
[Column(Name = "name"), Description("视频标题")]
public string Name { get; set; }
// 时长
[SqlSugar.SugarColumn(ColumnName = "duration", ColumnDescription = "时长")]
[Column(Name = "duration"), Description("时长")]
public string Duration { get; set; }
// 视频编码名称AVC、HEVC
[SqlSugar.SugarColumn(ColumnName = "video_codec_name", ColumnDescription = "视频编码名称AVC、HEVC")]
[Column(Name = "video_codec_name")]
[Description("视频编码名称AVC、HEVC")]
public string VideoCodecName { get; set; }
// 视频画质
[SqlSugar.SugarColumn(ColumnName = "resolution", ColumnDescription = "视频画质", IsJson = true)]
[Column(Name = "resolution"), Description("视频画质"), JsonMap]
public Quality Resolution { get; set; }
// 音频编码
[SqlSugar.SugarColumn(ColumnName = "audio_codec", ColumnDescription = "音频编码", IsJson = true,IsNullable = true)]
[Column(Name = "audio_codec", IsNullable = true), Description("音频编码"), JsonMap]
public Quality AudioCodec { get; set; }
// 文件路径,不包含扩展名,所有内容均以此路径下载
[SqlSugar.SugarColumn(ColumnName = "file_path", ColumnDescription = "文件路径,不包含扩展名,所有内容均以此路径下载")]
[Column(Name = "file_path"), Description("文件路径,不包含扩展名,所有内容均以此路径下载")]
public string FilePath { get; set; }
// 文件大小
[SqlSugar.SugarColumn(ColumnName = "file_size", ColumnDescription = "文件大小", IsNullable = true)]
[Column(Name = "file_size", IsNullable = true), Description("文件大小")]
public string? FileSize { get; set; }
// 视频分p(默认为1)
[SqlSugar.SugarColumn(ColumnName = "page", ColumnDescription = "视频分p(默认为1)")]
[Column(Name = "page"), Description("视频分p(默认为1)")]
public int Page { get; set; } = 1;
[Navigate(nameof(Id))] public Downloaded? Downloaded { get; set; }
[Navigate(nameof(Id))] public Downloading? Downloading { get; set; }
}

View File

@@ -1,21 +1,19 @@
using System;
using SqlSugar;
using FreeSql.DataAnnotations;
namespace DownKyi.Models;
[Serializable]
[SugarTable(TableName = "downloaded")]
[Table(Name = "downloaded")]
public class Downloaded
{
[SugarColumn(IsPrimaryKey = true, ColumnName = "id")]
[Column(IsPrimary = true, Name = "id")]
public string Id { get; set; } = null!;
// 下载速度
[SugarColumn(ColumnName = "max_speed_display")]
public string? MaxSpeedDisplay { get; set; }
[Column(Name = "max_speed_display")] public string? MaxSpeedDisplay { get; set; }
// 完成时间戳
[SugarColumn(ColumnName = "finished_timestamp")]
public long FinishedTimestamp { get; set; }
[Column(Name = "finished_timestamp")] public long FinishedTimestamp { get; set; }
public void SetFinishedTimestamp(long finishedTimestamp)
{
@@ -27,10 +25,7 @@ public class Downloaded
}
// 完成时间
[SugarColumn(ColumnName = "finished_time")]
public string FinishedTime { get; set; }
[Column(Name = "finished_time")] public string FinishedTime { get; set; }
[Navigate(NavigateType.OneToOne, nameof(Id))]
[SugarColumn(IsIgnore = true)]
public DownloadBase DownloadBase { get; set; }
[Navigate(nameof(Id))] public DownloadBase? DownloadBase { get; set; }
}

View File

@@ -1,61 +1,56 @@
using System;
using System.Collections.Generic;
using DownKyi.Core.BiliApi.VideoStream;
using Downloader;
using SqlSugar;
using DownKyi.Utils.DataAnnotations;
using FreeSql.DataAnnotations;
namespace DownKyi.Models;
[Serializable]
public class Downloading
{
[SugarColumn(IsPrimaryKey = true, ColumnName = "id")]
[Column(IsPrimary = true, Name = "id")]
public string Id { get; set; } = null!;
// Aria相关
[SugarColumn(ColumnName = "gid", IsNullable = true)]
[Column(Name = "gid", IsNullable = true)]
public string? Gid { get; set; }
// 下载的文件
[SugarColumn(ColumnName = "download_files", IsJson = true)]
[Column(Name = "download_files"), JsonMap]
public Dictionary<string, string> DownloadFiles { get; set; } = new();
// 已下载的文件
[SugarColumn(ColumnName = "downloaded_files", IsJson = true)]
[Column(Name = "downloaded_files"), JsonMap]
public List<string> DownloadedFiles { get; set; } = new();
// 视频类别
[SugarColumn(ColumnName = "play_stream_type")]
public PlayStreamType PlayStreamType { get; set; }
[Column(Name = "play_stream_type")] public PlayStreamType PlayStreamType { get; set; }
// 下载状态
[SugarColumn(ColumnName = "download_status")]
public DownloadStatus DownloadStatus { get; set; }
[Column(Name = "download_status")] public DownloadStatus DownloadStatus { get; set; }
// 正在下载内容(音频、视频、弹幕、字幕、封面)
[SugarColumn(ColumnName = "download_content", IsNullable = true)]
[Column(Name = "download_content", IsNullable = true)]
public string? DownloadContent { get; set; }
// 下载状态显示
[SugarColumn(ColumnName = "download_status_title", IsNullable = true)]
[Column(Name = "download_status_title", IsNullable = true)]
public string? DownloadStatusTitle { get; set; }
// 下载进度
[SugarColumn(ColumnName = "progress")] public float Progress { get; set; }
[Column(Name = "progress")] public float Progress { get; set; }
// 已下载大小/文件大小
[SugarColumn(ColumnName = "downloading_file_size", IsNullable = true)]
[Column(Name = "downloading_file_size", IsNullable = true)]
public string? DownloadingFileSize { get; set; }
// 下载的最高速度
[SugarColumn(ColumnName = "max_speed")]
public long MaxSpeed { get; set; }
[Column(Name = "max_speed")] public long MaxSpeed { get; set; }
// 下载速度
[SugarColumn(ColumnName = "speed_display", IsNullable = true)]
[Column(Name = "speed_display", IsNullable = true)]
public string? SpeedDisplay { get; set; }
[Navigate(NavigateType.OneToOne, nameof(Id))]
[SugarColumn(IsIgnore = true)]
public DownloadBase DownloadBase { get; set; }
[Navigate(nameof(Id))] public DownloadBase? DownloadBase { get; set; }
}

View File

@@ -33,6 +33,7 @@ public class AddToDownloadService
private IInfoService _videoInfoService;
private VideoInfoView? _videoInfoView;
private List<VideoSection>? _videoSections;
private DownloadStorageService _downloadStorageService = (DownloadStorageService)App.Current.Container.Resolve(typeof(DownloadStorageService));
// 下载内容
private bool _downloadAudio = true;
@@ -297,7 +298,7 @@ public class AddToDownloadService
continue;
}
bool f = item.DownloadBase.Cid == page.Cid &&
bool f = item.DownloadBase.Cid == page.Cid &&
item.Resolution.Id == page.VideoQuality.Quality &&
item.VideoCodecName == page.VideoQuality.SelectedVideoCodec &&
(
@@ -328,7 +329,7 @@ public class AddToDownloadService
continue;
}
bool f = item.DownloadBase.Cid == page.Cid &&
bool f = item.DownloadBase.Cid == page.Cid &&
item.Resolution.Id == page.VideoQuality.Quality &&
item.VideoCodecName == page.VideoQuality.SelectedVideoCodec &&
(
@@ -344,30 +345,34 @@ public class AddToDownloadService
switch (repeatDownloadStrategy)
{
case RepeatDownloadStrategy.Ask:
{
var result = ButtonResult.Cancel;
await Dispatcher.UIThread.Invoke(async () =>
{
var result = ButtonResult.Cancel;
await Dispatcher.UIThread.Invoke(async () =>
{
var param = new DialogParameters
var param = new DialogParameters
{
{ "message", $"{item.Name}已下载,是否重新下载" },
};
await dialogService.ShowDialogAsync(ViewAlreadyDownloadedDialogViewModel.Tag, param, buttonResult => { result = buttonResult.Result; });
await dialogService.ShowDialogAsync(ViewAlreadyDownloadedDialogViewModel.Tag, param, buttonResult => { result = buttonResult.Result; });
});
if (result == ButtonResult.OK)
{
App.PropertyChangeAsync(() =>
{
App.DownloadedList.Remove(item);
_downloadStorageService.RemoveDownloaded(item);
});
if (result == ButtonResult.OK)
{
App.PropertyChangeAsync(() => { App.DownloadedList.Remove(item); });
isDownloaded = false;
}
else
{
isDownloaded = true;
}
break;
isDownloaded = false;
}
else
{
isDownloaded = true;
}
break;
}
case RepeatDownloadStrategy.ReDownload:
isDownloaded = false;
break;

View File

@@ -176,7 +176,7 @@ public class AriaDownloadService : DownloadService, IDownloadService
case DownloadResult.FAILED:
case DownloadResult.ABORT:
default:
return nullMark;
return NullMark;
}
}
@@ -221,7 +221,7 @@ public class AriaDownloadService : DownloadService, IDownloadService
/// <returns></returns>
public override string MixedFlow(DownloadingItem downloading, string? audioUid, string? videoUid)
{
if (videoUid == nullMark)
if (videoUid == NullMark)
{
return null;
}
@@ -284,7 +284,7 @@ public class AriaDownloadService : DownloadService, IDownloadService
/// <exception cref="OperationCanceledException"></exception>
protected override void Pause(DownloadingItem downloading)
{
cancellationToken.ThrowIfCancellationRequested();
CancellationToken?.ThrowIfCancellationRequested();
downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing");
if (downloading.Downloading.DownloadStatus == DownloadStatus.Pause)
@@ -307,7 +307,7 @@ public class AriaDownloadService : DownloadService, IDownloadService
/// <returns></returns>
private async Task<bool> IsExist(DownloadingItem downloading)
{
var isExist = downloadingList.Contains(downloading);
var isExist = DownloadingList.Contains(downloading);
if (isExist)
{
return true;
@@ -479,7 +479,7 @@ public class AriaDownloadService : DownloadService, IDownloadService
ariaManager.DownloadFinish += AriaDownloadFinish;
return ariaManager.GetDownloadStatus(downloading.Downloading.Gid, new Action(() =>
{
cancellationToken.ThrowIfCancellationRequested();
CancellationToken?.ThrowIfCancellationRequested();
switch (downloading.Downloading.DownloadStatus)
{
case DownloadStatus.Pause:
@@ -499,7 +499,7 @@ public class AriaDownloadService : DownloadService, IDownloadService
DownloadingItem? video = null;
try
{
video = downloadingList.FirstOrDefault(it => it.Downloading.Gid == gid);
video = DownloadingList.FirstOrDefault(it => it.Downloading.Gid == gid);
}
catch (InvalidOperationException e)
{

View File

@@ -177,7 +177,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService
}
else
{
return nullMark;
return NullMark;
}
}
catch (FileNotFoundException e)
@@ -185,7 +185,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService
Console.PrintLine("BuiltinDownloadService.DownloadVideo()发生异常: {0}", e);
LogManager.Error("BuiltinDownloadService.DownloadVideo()", e);
return nullMark;
return NullMark;
}
}
@@ -274,7 +274,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService
/// <exception cref="OperationCanceledException"></exception>
protected override void Pause(DownloadingItem downloading)
{
cancellationToken.ThrowIfCancellationRequested();
CancellationToken?.ThrowIfCancellationRequested();
downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing");
if (downloading.Downloading.DownloadStatus == DownloadStatus.Pause)
@@ -297,7 +297,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService
/// <returns></returns>
private bool IsExist(DownloadingItem downloading)
{
return downloadingList.Contains(downloading);
return DownloadingList.Contains(downloading);
}
#region
@@ -379,7 +379,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService
// 阻塞当前任务,监听暂停事件
while (!isComplete)
{
cancellationToken.ThrowIfCancellationRequested();
CancellationToken?.ThrowIfCancellationRequested();
switch (downloading.Downloading.DownloadStatus)
{
case DownloadStatus.Pause:

View File

@@ -176,7 +176,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService
case DownloadResult.FAILED:
case DownloadResult.ABORT:
default:
return nullMark;
return NullMark;
}
}
@@ -218,7 +218,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService
/// <returns></returns>
public override string MixedFlow(DownloadingItem downloading, string? audioUid, string? videoUid)
{
if (videoUid == nullMark)
if (videoUid == NullMark)
{
return null;
}
@@ -278,7 +278,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService
/// <exception cref="OperationCanceledException"></exception>
protected override void Pause(DownloadingItem downloading)
{
cancellationToken.ThrowIfCancellationRequested();
CancellationToken?.ThrowIfCancellationRequested();
downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing");
if (downloading.Downloading.DownloadStatus == DownloadStatus.Pause)
@@ -301,7 +301,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService
/// <returns></returns>
private async Task<bool> IsExist(DownloadingItem downloading)
{
bool isExist = downloadingList.Contains(downloading);
bool isExist = DownloadingList.Contains(downloading);
if (isExist)
{
return true;
@@ -408,7 +408,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService
ariaManager.DownloadFinish += AriaDownloadFinish;
return ariaManager.GetDownloadStatus(downloading.Downloading.Gid, new Action(() =>
{
cancellationToken.ThrowIfCancellationRequested();
CancellationToken?.ThrowIfCancellationRequested();
switch (downloading.Downloading.DownloadStatus)
{
case DownloadStatus.Pause:
@@ -428,7 +428,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService
DownloadingItem video = null;
try
{
video = downloadingList.FirstOrDefault(it => it.Downloading.Gid == gid);
video = DownloadingList.FirstOrDefault(it => it.Downloading.Gid == gid);
}
catch (InvalidOperationException e)
{

View File

@@ -19,7 +19,6 @@ using DownKyi.Models;
using DownKyi.PrismExtension.Dialog;
using DownKyi.Utils;
using DownKyi.ViewModels.DownloadManager;
using ImTools;
using Console = DownKyi.Core.Utils.Debugging.Console;
namespace DownKyi.Services.Download;
@@ -30,16 +29,18 @@ public abstract class DownloadService
// protected TaskbarIcon _notifyIcon;
protected readonly IDialogService? DialogService;
protected ObservableCollection<DownloadingItem> downloadingList;
protected ObservableCollection<DownloadedItem> downloadedList;
protected readonly ObservableCollection<DownloadingItem> DownloadingList;
protected readonly ObservableCollection<DownloadedItem> DownloadedList;
protected Task workTask;
protected CancellationTokenSource tokenSource;
protected CancellationToken cancellationToken;
protected List<Task> downloadingTasks = new();
protected Task? WorkTask;
protected CancellationTokenSource? TokenSource;
protected CancellationToken? CancellationToken;
protected readonly List<Task> DownloadingTasks = new();
protected readonly int retry = 5;
protected readonly string nullMark = "<null>";
protected const int Retry = 5;
protected const string NullMark = "<null>";
protected readonly DownloadStorageService DownloadStorageService = (DownloadStorageService)App.Current.Container.Resolve(typeof(DownloadStorageService));
/// <summary>
/// 初始化
@@ -50,8 +51,8 @@ public abstract class DownloadService
/// <returns></returns>
public DownloadService(ObservableCollection<DownloadingItem> downloadingList, ObservableCollection<DownloadedItem> downloadedList, IDialogService? dialogService)
{
this.downloadingList = downloadingList;
this.downloadedList = downloadedList;
DownloadingList = downloadingList;
DownloadedList = downloadedList;
DialogService = dialogService;
}
@@ -133,7 +134,7 @@ public abstract class DownloadService
var codecs = Constant.GetCodecIds().FirstOrDefault(t => t.Id == video.CodecId);
if (video.Id == downloading.Resolution.Id && codecs?.Name == downloading.VideoCodecName)
{
return new()
return new VideoPlayUrlBasic
{
BackupUrl = video.BackupUrl,
Codecs = video.Codecs,
@@ -147,7 +148,7 @@ public abstract class DownloadService
if (downloading?.PlayUrl?.Durl?.Count > 0)
{
var durl = downloading.PlayUrl.Durl.First();
return new()
return new VideoPlayUrlBasic
{
BackupUrl = durl.BackupUrl,
BaseUrl = durl.Url,
@@ -199,7 +200,7 @@ public abstract class DownloadService
downloading.SpeedDisplay = string.Empty;
var title = $"{downloading.Name}";
var assFile = $"{downloading.DownloadBase.FilePath}.ass";
var assFile = $"{downloading.DownloadBase?.FilePath}.ass";
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku"))
@@ -243,8 +244,8 @@ public abstract class DownloadService
return assFile;
}
protected List<string> BaseDownloadSubtitle(DownloadingItem downloading)
{
// 更新状态显示
@@ -286,7 +287,7 @@ public abstract class DownloadService
if (srtFiles.Count > 0)
{
var srtFile = $"{downloading.DownloadBase.FilePath}.srt";
File.Copy(srtFiles[0], srtFile,true);
File.Copy(srtFiles[0], srtFile, true);
srtFiles.Add(srtFile);
}
@@ -334,17 +335,17 @@ public abstract class DownloadService
return finalFile;
}
private string ConcatVideos(DownloadingItem downloading,List<string> videoUids)
private string ConcatVideos(DownloadingItem downloading, List<string> videoUids)
{
downloading.DownloadStatusTitle = DictionaryResource.GetString("ConcatVideos");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo");
downloading.DownloadingFileSize = string.Empty;
downloading.SpeedDisplay = string.Empty;
var finalFile = $"{downloading.DownloadBase.FilePath}.mp4";
FFMpeg.Instance.ConcatVideos(videoUids,finalFile,(x)=>{});
FFMpeg.Instance.ConcatVideos(videoUids, finalFile, (x) => { });
if (File.Exists(finalFile))
{
var info = new FileInfo(finalFile);
@@ -354,6 +355,7 @@ public abstract class DownloadService
{
downloading.FileSize = Format.FormatFileSize(0);
}
return finalFile;
}
@@ -421,8 +423,8 @@ public abstract class DownloadService
try
{
downloadingTasks.RemoveAll((m) => m.IsCompleted);
foreach (var downloading in downloadingList)
DownloadingTasks.RemoveAll((m) => m.IsCompleted);
foreach (var downloading in DownloadingList)
{
if (downloading.Downloading.DownloadStatus == DownloadStatus.Downloading)
{
@@ -430,7 +432,7 @@ public abstract class DownloadService
}
}
foreach (var downloading in downloadingList)
foreach (var downloading in DownloadingList)
{
if (downloadingCount >= maxDownloading)
{
@@ -441,7 +443,7 @@ public abstract class DownloadService
if (downloading.Downloading.DownloadStatus is not (DownloadStatus.NotStarted or DownloadStatus.WaitForDownload)) continue;
//这里需要立刻设置状态否则如果SingleDownload没有及时执行会重复创建任务
downloading.Downloading.DownloadStatus = DownloadStatus.Downloading;
downloadingTasks.Add(SingleDownload(downloading));
DownloadingTasks.Add(SingleDownload(downloading));
downloadingCount++;
}
}
@@ -457,7 +459,7 @@ public abstract class DownloadService
}
// 判断是否该结束线程若为true跳出while循环
if (cancellationToken.IsCancellationRequested)
if (CancellationToken?.IsCancellationRequested == true)
{
Console.PrintLine($"{Tag}.DoWork() 下载服务结束跳出while循环");
LogManager.Debug($"{Tag}.DoWork()", "下载服务结束");
@@ -465,19 +467,19 @@ public abstract class DownloadService
}
// 判断下载列表中的视频是否全部下载完成
if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0)
if (lastDownloadingCount > 0 && DownloadingList.Count == 0 && DownloadedList.Count > 0)
{
AfterDownload();
}
lastDownloadingCount = downloadingList.Count;
lastDownloadingCount = DownloadingList.Count;
// 降低CPU占用
await Task.Delay(500);
}
await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000));
foreach (var tsk in downloadingTasks.FindAll((m) => !m.IsCompleted))
await Task.WhenAny(Task.WhenAll(DownloadingTasks), Task.Delay(30000));
foreach (var tsk in DownloadingTasks.FindAll((m) => !m.IsCompleted))
{
Console.PrintLine($"{Tag}.DoWork() 任务结束超时");
LogManager.Debug($"{Tag}.DoWork()", "任务结束超时");
@@ -510,7 +512,7 @@ public abstract class DownloadService
LogManager.Debug(Tag, e.Message);
var alertService = new AlertService(DialogService);
var result = await alertService.ShowError($"{path}{DictionaryResource.GetString("DirectoryError")}");
await alertService.ShowError($"{path}{DictionaryResource.GetString("DirectoryError")}");
return;
}
@@ -541,17 +543,17 @@ public abstract class DownloadService
// 如果需要下载音频
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"])
{
for (var i = 0; i < retry; i++)
for (var i = 0; i < Retry; i++)
{
audioUid = DownloadAudio(downloading);
if (audioUid != null && audioUid != nullMark)
if (audioUid != null && audioUid != NullMark)
{
break;
}
}
}
if (audioUid == nullMark)
if (audioUid == NullMark)
{
DownloadFailed(downloading);
return;
@@ -559,22 +561,22 @@ public abstract class DownloadService
Pause(downloading);
// 如果需要下载视频
if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
//videoUid = DownloadVideo(downloading);
for (var i = 0; i < retry; i++)
for (var i = 0; i < Retry; i++)
{
videoUid = DownloadVideo(downloading);
if (videoUid != null && videoUid != nullMark)
if (videoUid != null && videoUid != NullMark)
{
break;
}
}
}
if (videoUid == nullMark)
if (videoUid == NullMark)
{
DownloadFailed(downloading);
return;
@@ -601,29 +603,29 @@ public abstract class DownloadService
isMediaSuccess = File.Exists(outputMedia);
}
}
else if(downloading.PlayUrl.Durl != null)
else if (downloading.PlayUrl.Durl != null)
{
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] ||
downloading.DownloadBase.NeedDownloadContent["downloadVideo"] )
downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
var durls = downloading.PlayUrl.Durl.ToList();
var downloadStatus = durls
.Select((durl, index) => new { Durl = durl, Index = index })
.ToDictionary(x => x.Index, x => new { Durl = x.Durl, Result = string.Empty });
.Select((durl, index) => new { Durl = durl, Index = index })
.ToDictionary(x => x.Index, x => new { Durl = x.Durl, Result = string.Empty });
for (int i = 0; i < durls.Count; i++)
{
downloading.PlayUrl.Durl = new List<PlayUrlDurl> { durls[i] };
var result = DownloadVideo(downloading);
downloadStatus[i] = new { Durl = durls[i], Result = result ?? nullMark };
downloadStatus[i] = new { Durl = durls[i], Result = result ?? NullMark };
}
int retryCount = 0;
while (retryCount < retry && downloadStatus.Values
.Any(x => x.Result == nullMark))
while (retryCount < Retry && downloadStatus.Values
.Any(x => x.Result == NullMark))
{
var toRetry = downloadStatus
.Where(x => retryCount == 0 || x.Value.Result == nullMark)
.Where(x => retryCount == 0 || x.Value.Result == NullMark)
.ToList();
foreach (var item in toRetry)
@@ -632,11 +634,12 @@ public abstract class DownloadService
var result = DownloadVideo(downloading);
downloadStatus[item.Key] = new { item.Value.Durl, Result = result };
}
retryCount++;
await Task.Delay(1000);
}
if(downloadStatus.Values.Any(x => x.Result == nullMark))
if (downloadStatus.Values.Any(x => x.Result == NullMark))
{
DownloadFailed(downloading);
return;
@@ -646,26 +649,27 @@ public abstract class DownloadService
if (durls.Count > 1)
{
var output = ConcatVideos(downloading,downloadStatus.Values
var output = ConcatVideos(downloading, downloadStatus.Values
.Select(x => x.Result).ToList());
isMediaSuccess = File.Exists(output);
}
else
{
var outputMedia = MixedFlow(downloading, null, downloadStatus.First().Value.Result);
isMediaSuccess = File.Exists(outputMedia);
var outputMedia = MixedFlow(downloading, null, downloadStatus.First().Value.Result);
isMediaSuccess = File.Exists(outputMedia);
}
}
if(downloading.DownloadBase.NeedDownloadContent["downloadAudio"] &&
!downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] &&
!downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
//音频分离?
}
Pause(downloading);
}
string? outputDanmaku = null;
// 如果需要下载弹幕
if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"])
@@ -777,11 +781,12 @@ public abstract class DownloadService
Downloaded = downloaded
};
DownloadStorageService.AddDownloaded(downloadedItem);
App.PropertyChangeAsync(() =>
{
// 加入到下载完成list中并从下载中list去除
downloadedList.Add(downloadedItem);
downloadingList.Remove(downloading);
DownloadedList.Add(downloadedItem);
DownloadingList.Remove(downloading);
// 下载完成列表排序
var finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort();
@@ -868,25 +873,22 @@ public abstract class DownloadService
protected async Task BaseEndTask()
{
// 结束任务
tokenSource.Cancel();
TokenSource?.Cancel();
await workTask;
if (WorkTask != null) await WorkTask;
//先简单等待一下
// 下载数据存储服务
var downloadStorageService = new DownloadStorageService();
var downloadStorageService = (DownloadStorageService)App.Current.Container.Resolve(typeof(DownloadStorageService));
// 保存数据
foreach (var item in downloadingList)
foreach (var item in DownloadingList)
{
switch (item.Downloading.DownloadStatus)
{
case DownloadStatus.NotStarted:
break;
case DownloadStatus.WaitForDownload:
break;
case DownloadStatus.PauseStarted:
break;
case DownloadStatus.Pause:
break;
case DownloadStatus.Downloading:
@@ -896,7 +898,6 @@ public abstract class DownloadService
break;
case DownloadStatus.DownloadSucceed:
case DownloadStatus.DownloadFailed:
break;
default:
break;
}
@@ -905,11 +906,6 @@ public abstract class DownloadService
downloadStorageService.UpdateDownloading(item);
}
foreach (var item in downloadedList)
{
downloadStorageService.UpdateDownloaded(item);
}
}
/// <summary>
@@ -917,14 +913,14 @@ public abstract class DownloadService
/// </summary>
protected void BaseStart()
{
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
TokenSource = new CancellationTokenSource();
CancellationToken = TokenSource.Token;
// _notifyIcon = new TaskbarIcon();
// _notifyIcon.IconSource = new BitmapImage(new Uri("pack://application:,,,/Resources/favicon.ico"));
workTask = Task.Run(DoWork);
WorkTask = Task.Run(DoWork);
}
#region
public abstract void Parse(DownloadingItem downloading);

View File

@@ -2,13 +2,21 @@
using System.Linq;
using DownKyi.Models;
using DownKyi.ViewModels.DownloadManager;
using SqlSugar;
using FreeSql;
namespace DownKyi.Services.Download;
public class DownloadStorageService
{
private readonly ISqlSugarClient _sqlSugarClient = (ISqlSugarClient)App.Current.Container.Resolve(typeof(ISqlSugarClient));
private readonly IBaseRepository<Downloading> _downloadingRepository;
private readonly IBaseRepository<Downloaded> _downloadedRepository;
public DownloadStorageService(IBaseRepository<Downloading> downloadingRepository, IBaseRepository<Downloaded> downloadedRepository)
{
_downloadingRepository = downloadingRepository;
_downloadedRepository = downloadedRepository;
}
#region
@@ -27,7 +35,7 @@ public class DownloadStorageService
downloading.Id = downloadingItem.DownloadBase.Id;
downloading.DownloadBase = downloadingItem.DownloadBase;
_sqlSugarClient.UpdateNav(downloading, new UpdateNavRootOptions { IsInsertRoot = true }).IncludesAllFirstLayer().ExecuteCommand();
_downloadingRepository.InsertOrUpdate(downloading);
}
/// <summary>
@@ -41,7 +49,7 @@ public class DownloadStorageService
return;
}
_sqlSugarClient.Deleteable<Downloading>(it => it.Id == downloadingItem.Downloading.Id).ExecuteCommand();
_downloadingRepository.Delete(it => it.Id == downloadingItem.Downloading.Id);
}
/// <summary>
@@ -50,7 +58,7 @@ public class DownloadStorageService
/// <returns></returns>
public List<DownloadingItem> GetDownloading()
{
var downloadingList = _sqlSugarClient.Queryable<Downloading>().IncludesAllFirstLayer().ToList();
var downloadingList = _downloadingRepository.Select.ToList();
return downloadingList.Select(downloading => new DownloadingItem { Downloading = downloading, DownloadBase = downloading.DownloadBase }).ToList();
}
@@ -69,7 +77,7 @@ public class DownloadStorageService
var downloading = downloadingItem.Downloading;
downloading.DownloadBase = downloadingItem.DownloadBase;
_sqlSugarClient.UpdateNav(downloading).IncludesAllFirstLayer().ExecuteCommand();
_downloadingRepository.Update(downloading);
}
#endregion
@@ -89,9 +97,11 @@ public class DownloadStorageService
var downloaded = downloadedItem.Downloaded;
downloaded.Id = downloadedItem.DownloadBase.Id;
_sqlSugarClient.AsTenant().BeginTran();
_sqlSugarClient.Storageable(downloaded).TranLock().ExecuteCommand();
_sqlSugarClient.AsTenant().CommitTran();
var exists = _downloadedRepository.Select.Any(download => download.Id == downloaded.Id);
if (!exists)
{
_downloadedRepository.Insert(downloaded);
}
}
/// <summary>
@@ -105,7 +115,7 @@ public class DownloadStorageService
return;
}
_sqlSugarClient.DeleteNav<Downloaded>(it => it.Id == downloadedItem.Downloaded.Id).Include(o1 => o1.DownloadBase).ExecuteCommand();
_downloadedRepository.Delete(it => it.Id == downloadedItem.Downloaded.Id);
}
/// <summary>
@@ -114,7 +124,7 @@ public class DownloadStorageService
/// <returns></returns>
public List<DownloadedItem> GetDownloaded()
{
var downloadedList = _sqlSugarClient.Queryable<Downloaded>().IncludesAllFirstLayer().ToList();
var downloadedList = _downloadedRepository.Select.LeftJoin(downloaded => downloaded.DownloadBase.Id == downloaded.Id).ToList();
return downloadedList.Select(downloaded => new DownloadedItem { Downloaded = downloaded, DownloadBase = downloaded.DownloadBase }).ToList();
}
@@ -132,7 +142,12 @@ public class DownloadStorageService
var downloaded = downloadedItem.Downloaded;
downloaded.DownloadBase = downloadedItem.DownloadBase;
_sqlSugarClient.UpdateNav(downloaded).IncludesAllFirstLayer().ExecuteCommand();
_downloadedRepository.Update(downloaded);
}
public void ClearDownloaded()
{
_downloadedRepository.DeleteCascadeByDatabase(item => true);
}
#endregion

View File

@@ -0,0 +1,10 @@
using System;
namespace DownKyi.Utils.DataAnnotations;
/// <summary>
/// When the entity class property is <see cref="object"/>, map storage in JSON format. <br />
/// 当实体类属性为【对象】时,以 JSON 形式映射存储
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class JsonMapAttribute : Attribute { }

View File

@@ -0,0 +1,274 @@
using System.Text.Json;
using DownKyi.Utils.DataAnnotations;
using FreeSql;
using FreeSql.DataAnnotations;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
namespace DownKyi.Utils;
public static class SystemTextJsonHelper
{
private static readonly MethodInfo _deserializeMethodInfo =
typeof(JsonSerializer).GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(m => m.Name == "Deserialize"
&& m.IsGenericMethod
&& m.GetParameters().Length == 2
&& m.GetParameters()[0].ParameterType == typeof(string));
public static object? Deserialize(string json, Type type, JsonSerializerOptions options)
{
// 创建泛型方法
var genericMethod = _deserializeMethodInfo.MakeGenericMethod(type);
return genericMethod.Invoke(null, new object[] { json, options });
}
public static string Serialize(object value, JsonSerializerOptions options)
{
return JsonSerializer.Serialize(value, options);
}
}
public static class FreeSqlJsonMapCoreExtensions
{
private static int _isAoped = 0;
private static readonly ConcurrentDictionary<Type, bool> DicTypes = new();
private static readonly MethodInfo? MethodJsonConvertDeserializeObject =
typeof(SystemTextJsonHelper).GetMethod("Deserialize", new[] { typeof(string), typeof(Type), typeof(JsonSerializerOptions) });
private static readonly MethodInfo? MethodJsonConvertSerializeObject =
typeof(SystemTextJsonHelper).GetMethod("Serialize", new[] { typeof(object), typeof(JsonSerializerOptions) });
private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>> DicJsonMapFluentApi = new();
private static readonly object ConcurrentObj = new object();
public static ColumnFluent JsonMap(this ColumnFluent col)
{
DicJsonMapFluentApi.GetOrAdd(col._entityType, et => new ConcurrentDictionary<string, bool>())
.GetOrAdd(col._property.Name, pn => true);
return col;
}
/// <summary>
/// When the entity class property is <see cref="object"/> and the attribute is marked as <see cref="JsonMapAttribute"/>, map storage in JSON format. <br />
/// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时该属性将以JSON形式映射存储
/// </summary>
/// <returns></returns>
public static void UseJsonMap(this IFreeSql fsql)
{
UseJsonMap(fsql, JsonSerializerOptions.Default);
}
public static void UseJsonMap(this IFreeSql fsql, JsonSerializerOptions options)
{
if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0)
{
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) =>
{
if (DicTypes.ContainsKey(type))
return Expression.IfThenElse(
Expression.TypeIs(valueExp, type),
Expression.Return(returnTarget, valueExp),
Expression.Return(returnTarget,
Expression.TypeAs(
Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type),
Expression.Constant(options)), type))
);
return null;
});
}
fsql.Aop.ConfigEntityProperty += (s, e) =>
{
var isJsonMap = e.Property.GetCustomAttributes(typeof(JsonMapAttribute), false).Any() ||
DicJsonMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name);
if (isJsonMap)
{
if (DicTypes.ContainsKey(e.Property.PropertyType) == false &&
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(e.Property.PropertyType))
return; //基础类型使用 JsonMap 无效
if (e.ModifyResult.MapType == null)
{
e.ModifyResult.MapType = typeof(string);
e.ModifyResult.StringLength = -2;
}
if (DicTypes.TryAdd(e.Property.PropertyType, true))
{
lock (ConcurrentObj)
{
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true;
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp,
Type type) =>
{
return Expression.IfThenElse(
Expression.TypeIs(valueExp, e.Property.PropertyType),
Expression.Return(returnTarget,
Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(options)), typeof(object)),
elseExp);
});
}
}
}
};
switch (fsql.Ado.DataType)
{
case DataType.Sqlite:
case DataType.MySql:
case DataType.OdbcMySql:
case DataType.CustomMySql:
case DataType.SqlServer:
case DataType.OdbcSqlServer:
case DataType.CustomSqlServer:
case DataType.Oracle:
case DataType.OdbcOracle:
case DataType.CustomOracle:
case DataType.Dameng:
case DataType.DuckDB:
fsql.Aop.ParseExpression += (_, e) =>
{
//if (e.Expression is MethodCallExpression callExp)
//{
// var objExp = callExp.Object;
// var objType = objExp?.Type;
// if (objType?.FullName == "System.Byte[]") return;
// if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable))
// {
// objExp = callExp.Arguments.FirstOrDefault();
// objType = objExp?.Type;
// }
// if (objType == null) objType = callExp.Method.DeclaringType;
// if (objType != null || objType.IsArrayOrList())
// {
// string left = null;
// switch (callExp.Method.Name)
// {
// case "Any":
// left = objExp == null ? null : getExp(objExp);
// if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]";
// return $"(case when {left} is null then 0 else array_length({left},1) end > 0)";
// case "Contains":
// }
// }
//}
//解析 POCO Json a.Customer.Name
if (e.Expression is MemberExpression memExp)
{
if (e.Expression.IsParameter() == false) return;
var parentMemExps = new Stack<MemberExpression>();
parentMemExps.Push(memExp);
while (true)
{
switch (memExp.Expression?.NodeType)
{
case ExpressionType.MemberAccess:
case ExpressionType.Parameter: break;
default: return;
}
switch (memExp.Expression.NodeType)
{
case ExpressionType.MemberAccess:
memExp = memExp.Expression as MemberExpression;
if (memExp == null) return;
parentMemExps.Push(memExp);
break;
case ExpressionType.Parameter:
var tb = fsql.CodeFirst.GetTableByEntity(memExp.Expression.Type);
if (tb == null) return;
if (tb.ColumnsByCs.TryGetValue(parentMemExps.Pop().Member.Name, out var trycol) == false) return;
if (DicTypes.ContainsKey(trycol.CsType) == false) return;
var result = e.FreeParse(Expression.MakeMemberAccess(memExp.Expression, tb.Properties[trycol.CsName]));
if (parentMemExps.Any() == false)
{
e.Result = result;
return;
}
var jsonPath = "";
switch (fsql.Ado.DataType)
{
case DataType.Sqlite:
case DataType.MySql:
case DataType.OdbcMySql:
case DataType.CustomMySql:
StyleJsonExtract();
return;
case DataType.SqlServer:
case DataType.OdbcSqlServer:
case DataType.CustomSqlServer:
case DataType.Oracle:
case DataType.OdbcOracle:
case DataType.CustomOracle:
case DataType.Dameng:
StyleJsonValue();
return;
case DataType.DuckDB:
StyleDotAccess();
return;
}
StylePgJson();
return;
void StyleJsonExtract()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
jsonPath = $"{jsonPath}.{memExp.Member.Name}";
}
e.Result = $"json_extract({result},'${jsonPath}')";
}
void StyleJsonValue()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
jsonPath = $"{jsonPath}.{memExp.Member.Name}";
}
e.Result = $"json_value({result},'${jsonPath}')";
}
void StyleDotAccess()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
result = $"{result}['{memExp.Member.Name}']";
}
e.Result = result;
}
void StylePgJson()
{
while (parentMemExps.Any())
{
memExp = parentMemExps.Pop();
var opt = parentMemExps.Any() ? "->" : $"->>{(memExp.Type.IsArrayOrList() ? "/*json array*/" : "")}";
result = $"{result}{opt}'{memExp.Member.Name}'";
}
e.Result = result;
}
}
}
}
};
break;
}
}
}

View File

@@ -3,7 +3,6 @@ using Avalonia.Controls.Documents;
using DownKyi.Utils;
using Prism.Commands;
using Prism.Services.Dialogs;
using SqlSugar;
using System.Linq;
using DownKyi.Core.Settings;
using DownKyi.Models;

View File

@@ -12,17 +12,16 @@ using DownKyi.Core.BiliApi.Login;
using DownKyi.Core.Storage;
using DownKyi.Core.Storage.Database;
using DownKyi.Models;
using FreeSql;
using Prism.Commands;
using Prism.Services.Dialogs;
using SqlSugar;
using Exception = System.Exception;
namespace DownKyi.ViewModels.Dialogs;
public class ViewUpgradingDialogViewModel : BaseDialogViewModel
{
public const string Tag = "DialogLoading";
private readonly ISqlSugarClient _sqlSugarClient;
private readonly IBaseRepository<Downloaded> _downloadedRepository;
#region
@@ -70,9 +69,9 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
#endregion
public ViewUpgradingDialogViewModel(ISqlSugarClient sqlSugarClient)
public ViewUpgradingDialogViewModel(IBaseRepository<Downloaded> downloadedRepository)
{
_sqlSugarClient = sqlSugarClient;
_downloadedRepository = downloadedRepository;
Message = "数据迁移中、请不要关闭软件";
}
@@ -90,7 +89,6 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
{
Task.Run(() => { Upgrade1_0_20To1_0_21(); });
}
private void Upgrade1_0_20To1_0_21()
{
@@ -137,18 +135,25 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
{
noMigrate = true;
}
#if DEBUG
var oldDbPath = StorageManager.GetDownload().Replace(".db", "_debug.db");
#else
var oldDbPath = StorageManager.GetDownload();
#endif
if (File.Exists(oldDbPath))
{
SetMessage("正在迁移下载信息");
#if DEBUG
var dbHelper = new SqliteDatabase(oldDbPath);
#else
var dbHelper = new SqliteDatabase(oldDbPath, "bdb8eb69-3698-4af9-b722-9312d0fba623");
#endif
var validRecords = new Dictionary<string, DownloadedWithData>();
var totalCount = 0;
dbHelper.ExecuteQuery(@"SELECT d.id, d.data as downloaded_data, db.data as download_base_data
FROM downloaded d
JOIN download_base db ON d.id = db.id",reader =>
JOIN download_base db ON d.id = db.id", reader =>
{
while (reader.Read())
{
@@ -161,23 +166,24 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
// 反序列化
var record = NrbfDecoder.DecodeClassRecord(stream);
if (!record.TypeNameMatches(typeof(Downloaded))) continue;
var downloadedObj = new Downloaded
{
Id = reader["id"].ToString() ?? Guid.NewGuid().ToString("N"),
MaxSpeedDisplay = record.GetString($"<{nameof(Downloaded.MaxSpeedDisplay)}>k__BackingField"),
FinishedTime = record.GetString($"<{nameof(Downloaded.FinishedTime)}>k__BackingField") ?? "",
FinishedTimestamp = record.GetInt64($"<{nameof(Downloaded.FinishedTimestamp)}>k__BackingField")
};
validRecords.Add(
(string)reader["id"],
new DownloadedWithData
{
(string)reader["id"],
new DownloadedWithData
{
Downloaded = downloadedObj,
DownloadBaseData = (byte[])reader["download_base_data"]
});
totalCount++;
}
catch (Exception e)
@@ -212,15 +218,13 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
return quality;
});
var i = 0;
try
{
int batchSize = 200;
const int batchSize = 200;
var downloadedList = new List<Downloaded>();
int processedCount = 0;
var processedCount = 0;
_sqlSugarClient.Ado.BeginTran();
foreach (var item in validRecords)
{
try
@@ -229,17 +233,16 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
var record = NrbfDecoder.DecodeClassRecord(stream);
if (record.TypeNameMatches(typeof(DownloadBase)))
{
var needDownloadContentRecord =
record.GetClassRecord($"<{nameof(DownloadBase.NeedDownloadContent)}>k__BackingField");
var needDownloadContent = needDownloadContentRecord != null
? readNeedDownloadContent(needDownloadContentRecord)
: new Dictionary<string, bool>();
var needDownloadContentRecord = record.GetClassRecord($"<{nameof(DownloadBase.NeedDownloadContent)}>k__BackingField");
var needDownloadContent = needDownloadContentRecord != null ? readNeedDownloadContent(needDownloadContentRecord) : new Dictionary<string, bool>();
var download = new Downloaded
{
Id = item.Value.Downloaded.Id,
MaxSpeedDisplay = item.Value.Downloaded.MaxSpeedDisplay,
FinishedTime = item.Value.Downloaded.FinishedTime,
FinishedTimestamp = item.Value.Downloaded.FinishedTimestamp,
DownloadBase = new DownloadBase
{
NeedDownloadContent = needDownloadContent,
@@ -247,77 +250,56 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
Avid = record.GetInt64($"<{nameof(DownloadBase.Avid)}>k__BackingField"),
Cid = record.GetInt64($"<{nameof(DownloadBase.Cid)}>k__BackingField"),
EpisodeId = record.GetInt64($"<{nameof(DownloadBase.EpisodeId)}>k__BackingField"),
CoverUrl = record.GetString($"<{nameof(DownloadBase.CoverUrl)}>k__BackingField") ??
"",
PageCoverUrl =
record.GetString($"<{nameof(DownloadBase.PageCoverUrl)}>k__BackingField") ?? "",
CoverUrl = record.GetString($"<{nameof(DownloadBase.CoverUrl)}>k__BackingField") ?? "",
PageCoverUrl = record.GetString($"<{nameof(DownloadBase.PageCoverUrl)}>k__BackingField") ?? "",
ZoneId = record.GetInt32($"<{nameof(DownloadBase.ZoneId)}>k__BackingField"),
Order = record.GetInt32($"<{nameof(DownloadBase.Order)}>k__BackingField"),
MainTitle =
record.GetString($"<{nameof(DownloadBase.MainTitle)}>k__BackingField") ?? "",
MainTitle = record.GetString($"<{nameof(DownloadBase.MainTitle)}>k__BackingField") ?? "",
Name = record.GetString($"<{nameof(DownloadBase.Name)}>k__BackingField") ?? "",
Duration = record.GetString($"<{nameof(DownloadBase.Duration)}>k__BackingField") ??
"",
VideoCodecName =
record.GetString($"<{nameof(DownloadBase.VideoCodecName)}>k__BackingField") ??
"",
Resolution =
readQuality(
record.GetClassRecord(
$"<{nameof(DownloadBase.Resolution)}>k__BackingField")),
AudioCodec =
readQuality(
record.GetClassRecord(
$"<{nameof(DownloadBase.AudioCodec)}>k__BackingField")),
FilePath = record.GetString($"<{nameof(DownloadBase.FilePath)}>k__BackingField") ??
"",
FileSize = record.GetString($"<{nameof(DownloadBase.FileSize)}>k__BackingField") ??
"",
Duration = record.GetString($"<{nameof(DownloadBase.Duration)}>k__BackingField") ?? "",
VideoCodecName = record.GetString($"<{nameof(DownloadBase.VideoCodecName)}>k__BackingField") ?? "",
Resolution = readQuality(record.GetClassRecord($"<{nameof(DownloadBase.Resolution)}>k__BackingField")),
AudioCodec = readQuality(record.GetClassRecord($"<{nameof(DownloadBase.AudioCodec)}>k__BackingField")),
FilePath = record.GetString($"<{nameof(DownloadBase.FilePath)}>k__BackingField") ?? "",
FileSize = record.GetString($"<{nameof(DownloadBase.FileSize)}>k__BackingField") ?? "",
Page = record.GetInt32($"<{nameof(DownloadBase.Page)}>k__BackingField")
}
};
downloadedList.Add(download);
processedCount++;
if (processedCount % batchSize == 0 || processedCount == totalCount)
if (processedCount % batchSize != 0 && processedCount != totalCount) continue;
_downloadedRepository.Insert(downloadedList);
downloadedList.Clear();
// 更新进度
var percent = processedCount / (double)totalCount * 100;
Dispatcher.UIThread.Invoke(() =>
{
_sqlSugarClient.InsertNav(downloadedList)
.Include(o1 => o1.DownloadBase)
.ExecuteCommand();
downloadedList.Clear();
// 更新进度
var percent = processedCount / (double)totalCount * 100;
Dispatcher.UIThread.Invoke(() =>
{
Percent = percent;
SetMessage($"正在迁移下载信息({processedCount}/{totalCount})");
});
}
Percent = percent;
SetMessage($"正在迁移下载信息({processedCount}/{totalCount})");
});
}
}
catch (Exception ex)
{
/*忽略*/
Console.WriteLine(ex.ToString());
}
}
_sqlSugarClient.Ado.CommitTran();
dbHelper.Dispose();
File.Delete(oldDbPath);
Dispatcher.UIThread.Invoke(() =>
{
RestartVisible = true;
SetMessage("下载信息迁移完成");
App.Current.RefreshDownloadedList();
});
}
catch (Exception e)
{
_sqlSugarClient.Ado.RollbackTran();
SetMessage($"迁移失败: {e.Message}");
}
}
else
{
@@ -329,11 +311,10 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel
Dispatcher.UIThread.Invoke(() => RaiseRequestClose(new DialogResult()));
}
}
private class DownloadedWithData
{
public Downloaded Downloaded { get; set; }
public byte[] DownloadBaseData { get; set; }
public Downloaded Downloaded { get; set; } = new();
public byte[] DownloadBaseData { get; set; } = Array.Empty<byte>();
}
}

View File

@@ -2,7 +2,6 @@
using DownKyi.Core.BiliApi.BiliUtils;
using DownKyi.Core.BiliApi.Zone;
using DownKyi.Models;
using DownKyi.PrismExtension.Dialog;
using DownKyi.Utils;
using Prism.Mvvm;
@@ -10,18 +9,6 @@ namespace DownKyi.ViewModels.DownloadManager
{
public class DownloadBaseItem : BindableBase
{
public IDialogService? DialogService;
public DownloadBaseItem()
{
DialogService = null;
}
public DownloadBaseItem(IDialogService? dialogService)
{
DialogService = dialogService;
}
// model数据
private DownloadBase? _downloadBase;
@@ -40,9 +27,9 @@ namespace DownKyi.ViewModels.DownloadManager
}
// 视频分区image
private DrawingImage _zoneImage;
private DrawingImage? _zoneImage;
public DrawingImage ZoneImage
public DrawingImage? ZoneImage
{
get => _zoneImage;
set => SetProperty(ref _zoneImage, value);

View File

@@ -1,183 +1,76 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DownKyi.Images;
using DownKyi.Images;
using DownKyi.Models;
using DownKyi.Services;
using DownKyi.Utils;
using Prism.Commands;
using Prism.Services.Dialogs;
using IDialogService = DownKyi.PrismExtension.Dialog.IDialogService;
namespace DownKyi.ViewModels.DownloadManager
namespace DownKyi.ViewModels.DownloadManager;
public class DownloadedItem : DownloadBaseItem
{
public class DownloadedItem : DownloadBaseItem
public DownloadedItem()
{
public DownloadedItem() : this(null)
{
}
// 打开文件夹按钮
OpenFolder = ButtonIcon.Instance().Folder;
OpenFolder.Fill = DictionaryResource.GetColor("ColorPrimary");
public DownloadedItem(IDialogService? dialogService) : base(dialogService)
{
// 打开文件夹按钮
OpenFolder = ButtonIcon.Instance().Folder;
OpenFolder.Fill = DictionaryResource.GetColor("ColorPrimary");
// 打开视频按钮
OpenVideo = ButtonIcon.Instance().Start;
OpenVideo.Fill = DictionaryResource.GetColor("ColorPrimary");
// 打开视频按钮
OpenVideo = ButtonIcon.Instance().Start;
OpenVideo.Fill = DictionaryResource.GetColor("ColorPrimary");
// 删除按钮
RemoveVideo = ButtonIcon.Instance().Trash;
RemoveVideo.Fill = DictionaryResource.GetColor("ColorWarning");
}
// model数据
public Downloaded Downloaded { get; set; }
// 下载速度
public string? MaxSpeedDisplay
{
get => Downloaded.MaxSpeedDisplay;
set
{
Downloaded.MaxSpeedDisplay = value;
RaisePropertyChanged();
}
}
// 完成时间
public string FinishedTime
{
get => Downloaded.FinishedTime;
set
{
Downloaded.FinishedTime = value;
RaisePropertyChanged("FinishedTime");
}
}
#region
private VectorImage _openFolder;
public VectorImage OpenFolder
{
get => _openFolder;
set => SetProperty(ref _openFolder, value);
}
private VectorImage _openVideo;
public VectorImage OpenVideo
{
get => _openVideo;
set => SetProperty(ref _openVideo, value);
}
private VectorImage _removeVideo;
public VectorImage RemoveVideo
{
get => _removeVideo;
set => SetProperty(ref _removeVideo, value);
}
#endregion
#region
// 打开文件夹事件
private DelegateCommand? _openFolderCommand;
public DelegateCommand OpenFolderCommand => _openFolderCommand ??= new DelegateCommand(ExecuteOpenFolderCommand);
private static readonly IReadOnlyDictionary<string, string[]> FileSuffixMap = new Dictionary<string, string[]>
{
{ "downloadVideo", new[] { ".mp4", ".flv" } },
{ "downloadAudio", new[] { ".aac", ".mp3" } },
{ "downloadCover", new[] { ".jpg" } },
{ "downloadDanmaku", new[] { ".ass" } },
{ "downloadSubtitle", new[] { ".srt" } }
};
/// <summary>
/// 打开文件夹事件
/// </summary>
private void ExecuteOpenFolderCommand()
{
if (DownloadBase == null)
{
return;
}
var downLoadContents = DownloadBase.NeedDownloadContent.Where(e => e.Value == true).Select(e => e.Key);
var fileSuffixes = downLoadContents
.Where(content => FileSuffixMap.ContainsKey(content))
.SelectMany(content => FileSuffixMap[content])
.ToArray();
if (fileSuffixes.Length <= 0) return;
foreach (var suffix in fileSuffixes)
{
var videoPath = $"{DownloadBase.FilePath}{suffix}";
var fileInfo = new FileInfo(videoPath);
if (File.Exists(fileInfo.FullName) && fileInfo.DirectoryName != null)
{
PlatformHelper.OpenFolder(fileInfo.DirectoryName);
return;
}
}
// eventAggregator.GetEvent<MessageEvent>().Publish("没有找到视频文件,可能被删除或移动!");
}
// 打开视频事件
private DelegateCommand? _openVideoCommand;
public DelegateCommand OpenVideoCommand => _openVideoCommand ??= new DelegateCommand(ExecuteOpenVideoCommand);
/// <summary>
/// 打开视频事件
/// </summary>
private void ExecuteOpenVideoCommand()
{
if (DownloadBase == null)
{
return;
}
var videoPath = $"{DownloadBase.FilePath}.mp4";
var fileInfo = new FileInfo(videoPath);
if (File.Exists(fileInfo.FullName))
{
PlatformHelper.Open(fileInfo.FullName);
}
else
{
//eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipAddDownloadingZero"));
//eventAggregator.GetEvent<MessageEvent>().Publish("没有找到视频文件,可能被删除或移动!");
}
}
// 删除事件
private DelegateCommand? _removeVideoCommand;
public DelegateCommand RemoveVideoCommand => _removeVideoCommand ??= new DelegateCommand(ExecuteRemoveVideoCommand);
/// <summary>
/// 删除事件
/// </summary>
private async void ExecuteRemoveVideoCommand()
{
var alertService = new AlertService(DialogService);
var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2);
if (result != ButtonResult.OK)
{
return;
}
App.DownloadedList?.Remove(this);
}
#endregion
// 删除按钮
RemoveVideo = ButtonIcon.Instance().Trash;
RemoveVideo.Fill = DictionaryResource.GetColor("ColorWarning");
}
// model数据
public Downloaded Downloaded { get; set; }
// 下载速度
public string? MaxSpeedDisplay
{
get => Downloaded.MaxSpeedDisplay;
set
{
Downloaded.MaxSpeedDisplay = value;
RaisePropertyChanged();
}
}
// 完成时间
public string FinishedTime
{
get => Downloaded.FinishedTime;
set
{
Downloaded.FinishedTime = value;
RaisePropertyChanged();
}
}
#region
private VectorImage _openFolder;
public VectorImage OpenFolder
{
get => _openFolder;
set => SetProperty(ref _openFolder, value);
}
private VectorImage _openVideo;
public VectorImage OpenVideo
{
get => _openVideo;
set => SetProperty(ref _openVideo, value);
}
private VectorImage _removeVideo;
public VectorImage RemoveVideo
{
get => _removeVideo;
set => SetProperty(ref _removeVideo, value);
}
#endregion
}

View File

@@ -1,23 +1,17 @@
using DownKyi.Core.BiliApi.VideoStream.Models;
using DownKyi.Images;
using DownKyi.Models;
using DownKyi.Services;
using DownKyi.Utils;
using Downloader;
using Prism.Commands;
using Prism.Services.Dialogs;
using DownloadStatus = DownKyi.Models.DownloadStatus;
using IDialogService = DownKyi.PrismExtension.Dialog.IDialogService;
namespace DownKyi.ViewModels.DownloadManager
{
public class DownloadingItem : DownloadBaseItem
{
public DownloadingItem() : this(null)
{
}
public DownloadingItem(IDialogService? dialogService) : base(dialogService)
public DownloadingItem()
{
// 暂停继续按钮
StartOrPause = ButtonIcon.Instance().Pause;
@@ -218,25 +212,6 @@ namespace DownKyi.ViewModels.DownloadManager
}
}
// 下载列表删除事件
private DelegateCommand? _deleteCommand;
public DelegateCommand DeleteCommand => _deleteCommand ??= new DelegateCommand(ExecuteDeleteCommand);
/// <summary>
/// 下载列表删除事件
/// </summary>
private async void ExecuteDeleteCommand()
{
var alertService = new AlertService(DialogService);
var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2);
if (result != ButtonResult.OK)
{
return;
}
App.DownloadingList?.Remove(this);
}
#endregion
}
}

View File

@@ -1,115 +1,120 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Threading;
using DownKyi.Core.Logging;
using DownKyi.Core.Settings;
using DownKyi.Events;
using DownKyi.Services;
using DownKyi.Services.Download;
using DownKyi.Utils;
using Prism.Commands;
using Prism.Events;
using Prism.Regions;
using Prism.Services.Dialogs;
using Console = DownKyi.Core.Utils.Debugging.Console;
using IDialogService = DownKyi.PrismExtension.Dialog.IDialogService;
namespace DownKyi.ViewModels.DownloadManager
namespace DownKyi.ViewModels.DownloadManager;
public class ViewDownloadFinishedViewModel : ViewModelBase
{
public class ViewDownloadFinishedViewModel : ViewModelBase
public const string Tag = "PageDownloadManagerDownloadFinished";
private DownloadStorageService _downloadStorageService;
#region
private ObservableCollection<DownloadedItem> _downloadedList = new();
public ObservableCollection<DownloadedItem> DownloadedList
{
public const string Tag = "PageDownloadManagerDownloadFinished";
get => _downloadedList;
set => SetProperty(ref _downloadedList, value);
}
#region
private int _finishedSortBy;
private ObservableCollection<DownloadedItem> _downloadedList;
public ObservableCollection<DownloadedItem> DownloadedList
public int FinishedSortBy
{
get => _finishedSortBy;
set => SetProperty(ref _finishedSortBy, value);
}
#endregion
public ViewDownloadFinishedViewModel(
IEventAggregator eventAggregator,
IDialogService dialogService,
DownloadStorageService downloadStorageService
) : base(eventAggregator,
dialogService)
{
// 初始化DownloadedList
DownloadedList = App.DownloadedList;
_downloadStorageService = downloadStorageService;
var finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort();
FinishedSortBy = finishedSort switch
{
get => _downloadedList;
set => SetProperty(ref _downloadedList, value);
DownloadFinishedSort.DownloadAsc => 0,
DownloadFinishedSort.DownloadDesc => 1,
DownloadFinishedSort.Number => 2,
_ => 0
};
App.SortDownloadedList(finishedSort);
}
#region
// 下载完成列表排序事件
private DelegateCommand<object>? _finishedSortCommand;
public DelegateCommand<object> FinishedSortCommand => _finishedSortCommand ??= new DelegateCommand<object>(ExecuteFinishedSortCommand);
/// <summary>
/// 下载完成列表排序事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteFinishedSortCommand(object parameter)
{
if (parameter is not int index)
{
return;
}
private int _finishedSortBy;
public int FinishedSortBy
switch (index)
{
get => _finishedSortBy;
set => SetProperty(ref _finishedSortBy, value);
case 0:
App.SortDownloadedList(DownloadFinishedSort.DownloadAsc);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc);
break;
case 1:
App.SortDownloadedList(DownloadFinishedSort.DownloadDesc);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadDesc);
break;
case 2:
App.SortDownloadedList(DownloadFinishedSort.Number);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.Number);
break;
default:
App.SortDownloadedList(DownloadFinishedSort.DownloadAsc);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc);
break;
}
}
#endregion
// 清空下载完成列表事件
private DelegateCommand? _clearAllDownloadedCommand;
public DelegateCommand ClearAllDownloadedCommand => _clearAllDownloadedCommand ??= new DelegateCommand(ExecuteClearAllDownloadedCommand);
public ViewDownloadFinishedViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator, dialogService)
{
// 初始化DownloadedList
DownloadedList = App.DownloadedList;
DownloadedList.CollectionChanged += (sender, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
SetDialogService();
}
};
SetDialogService();
var finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort();
FinishedSortBy = finishedSort switch
{
DownloadFinishedSort.DownloadAsc => 0,
DownloadFinishedSort.DownloadDesc => 1,
DownloadFinishedSort.Number => 2,
_ => 0
};
App.SortDownloadedList(finishedSort);
}
#region
// 下载完成列表排序事件
private DelegateCommand<object>? _finishedSortCommand;
public DelegateCommand<object> FinishedSortCommand => _finishedSortCommand ??= new DelegateCommand<object>(ExecuteFinishedSortCommand);
/// <summary>
/// 下载完成列表排序事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteFinishedSortCommand(object parameter)
{
if (parameter is not int index) { return; }
switch (index)
{
case 0:
App.SortDownloadedList(DownloadFinishedSort.DownloadAsc);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc);
break;
case 1:
App.SortDownloadedList(DownloadFinishedSort.DownloadDesc);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadDesc);
break;
case 2:
App.SortDownloadedList(DownloadFinishedSort.Number);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.Number);
break;
default:
App.SortDownloadedList(DownloadFinishedSort.DownloadAsc);
// 更新设置
SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc);
break;
}
}
// 清空下载完成列表事件
private DelegateCommand? _clearAllDownloadedCommand;
public DelegateCommand ClearAllDownloadedCommand => _clearAllDownloadedCommand ??= new DelegateCommand(ExecuteClearAllDownloadedCommand);
/// <summary>
/// 清空下载完成列表事件
/// </summary>
private async void ExecuteClearAllDownloadedCommand()
/// <summary>
/// 清空下载完成列表事件
/// </summary>
private async void ExecuteClearAllDownloadedCommand()
{
try
{
var alertService = new AlertService(DialogService);
var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"));
@@ -118,53 +123,110 @@ namespace DownKyi.ViewModels.DownloadManager
return;
}
// 使用Clear()不能触发NotifyCollectionChangedAction.Remove事件
// 因此遍历删除
// DownloadingList中元素被删除后不能继续遍历
await Task.Run(() =>
{
var list = DownloadedList.ToList();
foreach (var item in list)
{
App.PropertyChangeAsync(() =>
{
App.DownloadedList?.Remove(item);
});
}
});
_downloadStorageService.ClearDownloaded();
App.PropertyChangeAsync(() => { App.DownloadedList.Clear(); });
}
#endregion
private async void SetDialogService()
catch (Exception e)
{
try
{
await Task.Run(() =>
{
var list = Dispatcher.UIThread.Invoke(()=>DownloadedList.ToList());
foreach (var item in list)
{
if (item != null && item.DialogService == null)
{
item.DialogService = DialogService;
}
}
});
}
catch (Exception e)
{
Console.PrintLine("SetDialogService()发生异常: {0}", e);
LogManager.Error($"{Tag}.SetDialogService()", e);
}
var alertService = new AlertService(DialogService);
await alertService.ShowError(e.Message);
}
public override void OnNavigatedFrom(NavigationContext navigationContext)
{
base.OnNavigatedFrom(navigationContext);
SetDialogService();
}
}
}
// 打开视频事件
private DelegateCommand<DownloadedItem>? _openVideoCommand;
public DelegateCommand<DownloadedItem> OpenVideoCommand => _openVideoCommand ??= new DelegateCommand<DownloadedItem>(ExecuteOpenVideoCommand);
/// <summary>
/// 打开视频事件
/// </summary>
private void ExecuteOpenVideoCommand(DownloadedItem downloadedItem)
{
if (downloadedItem.DownloadBase == null)
{
return;
}
var videoPath = $"{downloadedItem.DownloadBase.FilePath}.mp4";
var fileInfo = new FileInfo(videoPath);
if (File.Exists(fileInfo.FullName))
{
PlatformHelper.Open(fileInfo.FullName);
}
else
{
//eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipAddDownloadingZero"));
EventAggregator.GetEvent<MessageEvent>().Publish("没有找到视频文件,可能被删除或移动!");
}
}
// 打开文件夹事件
private DelegateCommand<DownloadedItem>? _openFolderCommand;
public DelegateCommand<DownloadedItem> OpenFolderCommand => _openFolderCommand ??= new DelegateCommand<DownloadedItem>(ExecuteOpenFolderCommand);
private static readonly IReadOnlyDictionary<string, string[]> FileSuffixMap = new Dictionary<string, string[]>
{
{ "downloadVideo", new[] { ".mp4", ".flv" } },
{ "downloadAudio", new[] { ".aac", ".mp3" } },
{ "downloadCover", new[] { ".jpg" } },
{ "downloadDanmaku", new[] { ".ass" } },
{ "downloadSubtitle", new[] { ".srt" } }
};
/// <summary>
/// 打开文件夹事件
/// </summary>
private void ExecuteOpenFolderCommand(DownloadedItem downloadedItem)
{
if (downloadedItem.DownloadBase == null)
{
return;
}
var downLoadContents = downloadedItem.DownloadBase.NeedDownloadContent.Where(e => e.Value == true).Select(e => e.Key);
var fileSuffixes = downLoadContents
.Where(content => FileSuffixMap.ContainsKey(content))
.SelectMany(content => FileSuffixMap[content])
.ToArray();
if (fileSuffixes.Length <= 0) return;
foreach (var suffix in fileSuffixes)
{
var videoPath = $"{downloadedItem.DownloadBase.FilePath}{suffix}";
var fileInfo = new FileInfo(videoPath);
if (!File.Exists(fileInfo.FullName) || fileInfo.DirectoryName == null) continue;
PlatformHelper.OpenFolder(fileInfo.DirectoryName);
return;
}
EventAggregator.GetEvent<MessageEvent>().Publish("没有找到视频文件,可能被删除或移动!");
}
// 删除事件
private DelegateCommand<DownloadedItem>? _removeVideoCommand;
public DelegateCommand<DownloadedItem> RemoveVideoCommand => _removeVideoCommand ??= new DelegateCommand<DownloadedItem>(ExecuteRemoveVideoCommand);
/// <summary>
/// 删除事件
/// </summary>
private async void ExecuteRemoveVideoCommand(DownloadedItem downloadedItem)
{
var alertService = new AlertService(DialogService);
var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2);
if (result != ButtonResult.OK)
{
return;
}
App.DownloadedList.Remove(downloadedItem);
_downloadStorageService.RemoveDownloaded(downloadedItem);
}
#endregion
}

View File

@@ -23,7 +23,7 @@ namespace DownKyi.ViewModels.DownloadManager
#region
private ObservableCollection<DownloadingItem> _downloadingList;
private ObservableCollection<DownloadingItem> _downloadingList = new();
public ObservableCollection<DownloadingItem> DownloadingList
{
@@ -38,14 +38,6 @@ namespace DownKyi.ViewModels.DownloadManager
{
// 初始化DownloadingList
DownloadingList = App.DownloadingList;
DownloadingList.CollectionChanged += (sender, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
SetDialogService();
}
};
SetDialogService();
}
#region
@@ -163,41 +155,31 @@ namespace DownKyi.ViewModels.DownloadManager
var list = DownloadingList.ToList();
foreach (var item in list)
{
App.PropertyChangeAsync(() => { App.DownloadingList?.Remove(item); });
App.PropertyChangeAsync(() => { App.DownloadingList.Remove(item); });
}
});
}
// 下载列表删除事件
private DelegateCommand<DownloadingItem>? _deleteCommand;
public DelegateCommand<DownloadingItem> DeleteCommand => _deleteCommand ??= new DelegateCommand<DownloadingItem>(ExecuteDeleteCommand);
/// <summary>
/// 下载列表删除事件
/// </summary>
private async void ExecuteDeleteCommand(DownloadingItem downloadingItem)
{
var alertService = new AlertService(DialogService);
var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2);
if (result != ButtonResult.OK)
{
return;
}
App.DownloadingList.Remove(downloadingItem);
}
#endregion
private async void SetDialogService()
{
try
{
await Task.Run(() =>
{
var list = DownloadingList.ToList();
foreach (var item in list)
{
if (item != null && item.DialogService == null)
{
item.DialogService = DialogService;
}
}
});
}
catch (Exception e)
{
Console.PrintLine("SetDialogService()发生异常: {0}", e);
LogManager.Error($"{Tag}.SetDialogService()", e);
}
}
public override void OnNavigatedFrom(NavigationContext navigationContext)
{
base.OnNavigatedFrom(navigationContext);
SetDialogService();
}
}
}

View File

@@ -85,7 +85,8 @@
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding OpenFolderCommand}"
Command="{Binding ElementName=DownloadedList,Path=((vmdm:ViewDownloadFinishedViewModel)DataContext).OpenFolderCommand}"
CommandParameter="{Binding }"
Theme="{StaticResource ImageBtnStyle}"
ToolTip.Tip="{DynamicResource OpenFolder}">
<ContentControl Width="20" Height="20">
@@ -102,7 +103,8 @@
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding OpenVideoCommand}"
Command="{Binding ElementName=DownloadedList,Path=((vmdm:ViewDownloadFinishedViewModel)DataContext).OpenVideoCommand}"
CommandParameter="{Binding }"
Theme="{StaticResource ImageBtnStyle}"
ToolTip.Tip="{DynamicResource OpenVideo}">
<ContentControl Width="20" Height="20">
@@ -119,7 +121,8 @@
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding RemoveVideoCommand}"
Command="{Binding ElementName=DownloadedList,Path=((vmdm:ViewDownloadFinishedViewModel)DataContext).RemoveVideoCommand}"
CommandParameter="{Binding }"
Theme="{StaticResource ImageBtnStyle}"
ToolTip.Tip="{DynamicResource DeleteDownload}">
<ContentControl Width="20" Height="20">
@@ -144,6 +147,7 @@
<Grid RowDefinitions="*,1,50">
<ListBox
Name="DownloadedList"
Grid.Row="0"
BorderThickness="0"
ItemContainerTheme="{StaticResource DownloadedStyle}"

View File

@@ -163,7 +163,8 @@
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding DeleteCommand}"
Command="{Binding ElementName=DownloadingList,Path=((vmdm:ViewDownloadingViewModel)DataContext).DeleteCommand}"
CommandParameter="{Binding}"
Theme="{StaticResource ImageBtnStyle}"
ToolTip.Tip="{DynamicResource DeleteDownload}">
<ContentControl Width="20" Height="20">
@@ -189,6 +190,7 @@
RowDefinitions="*,1,50"
IsVisible="{Binding DownloadingList.Count}">
<ListBox
Name="DownloadingList"
Grid.Row="0"
BorderThickness="0"
ItemContainerTheme="{StaticResource DownloadingStyle}"