mirror of
https://github.com/yaobiao131/downkyicore.git
synced 2025-08-10 00:52:31 +00:00
refactor: 重构数据库并优化下载列表相关逻辑
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
10
DownKyi/Utils/DataAnnotations/JsonMapAttribute.cs
Normal file
10
DownKyi/Utils/DataAnnotations/JsonMapAttribute.cs
Normal 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 { }
|
||||
274
DownKyi/Utils/JsonMapCore.cs
Normal file
274
DownKyi/Utils/JsonMapCore.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user