From 740abfa602554c466f07608d49202c08a53a0922 Mon Sep 17 00:00:00 2001
From: flyself <1432593898@qq.com>
Date: Thu, 6 Jan 2022 00:12:57 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=88=97=E8=A1=A8=E7=9A=84?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=AE=BE=E8=AE=A1=EF=BC=9B=E5=BC=82?=
=?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=EF=BC=9B=E6=95=B4=E7=90=86=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Aria2cNet/Server/AriaServer.cs | 8 +-
.../BiliApi/VideoStream/PlayStreamType.cs | 9 +
src/DownKyi.Core/DownKyi.Core.csproj | 5 +
src/DownKyi.Core/Settings/SettingsManager.cs | 2 +-
src/DownKyi.Core/Storage/Constant.cs | 4 +
src/DownKyi.Core/Storage/Database/CoverDb.cs | 26 +-
src/DownKyi.Core/Storage/Database/DbHelper.cs | 4 +-
.../Database/Download/DownloadBaseDb.cs | 11 +
.../Storage/Database/Download/DownloadDb.cs | 113 +++++++
.../Storage/Database/Download/DownloadedDb.cs | 11 +
.../Database/Download/DownloadingDb.cs | 11 +
src/DownKyi.Core/Storage/Database/HeaderDb.cs | 22 +-
src/DownKyi.Core/Storage/StorageManager.cs | 20 ++
src/DownKyi/App.xaml.cs | 38 ++-
src/DownKyi/Converter/CountConverter.cs | 21 ++
src/DownKyi/DownKyi.csproj | 28 +-
src/DownKyi/Images/VectorImage.cs | 16 +-
src/DownKyi/Languages/Default.xaml | 6 +
src/DownKyi/Models/DownloadBase.cs | 75 +++++
src/DownKyi/Models/Downloaded.cs | 30 ++
src/DownKyi/Models/Downloading.cs | 47 +++
src/DownKyi/Resources/nodata02.png | Bin 0 -> 11421 bytes
src/DownKyi/Services/BangumiInfoService.cs | 24 +-
src/DownKyi/Services/CheeseInfoService.cs | 7 +-
.../Services/Download/AriaDownloadService.cs | 116 ++++---
.../Services/Download/DownloadService.cs | 2 +-
.../Services/Download/IDownloadService.cs | 2 +-
src/DownKyi/Services/FavoritesService.cs | 2 +-
src/DownKyi/Services/IFavoritesService.cs | 2 +-
src/DownKyi/Services/IInfoService.cs | 2 +-
src/DownKyi/Services/Utils.cs | 2 +-
src/DownKyi/Services/VideoInfoService.cs | 14 +-
src/DownKyi/Utils/DictionaryResource.cs | 6 +-
src/DownKyi/ViewModels/BaseViewModel.cs | 2 +
.../DownloadManager/DownloadBaseItem.cs | 122 ++++++++
.../DownloadManager/DownloadedItem.cs | 122 ++++++++
.../DownloadManager/DownloadingItem.cs | 165 ++++++++++
.../ViewDownloadFinishedViewModel.cs | 50 ++-
.../ViewDownloadingViewModel.cs | 14 +-
.../ViewModels/PageViewModels/Favorites.cs | 88 ++++++
.../PageViewModels/FavoritesMedia.cs | 125 ++++++++
.../ViewModels/PageViewModels/TabHeader.cs | 37 +++
.../PageViewModels/VideoInfoView.cs | 111 +++++++
.../ViewModels/PageViewModels/VideoPage.cs | 75 +++++
.../ViewModels/PageViewModels/VideoQuality.cs | 36 +++
.../ViewModels/PageViewModels/VideoSection.cs | 31 ++
.../Settings/DisplayFileNamePart.cs | 17 +
.../ViewModels/Settings/ViewVideoViewModel.cs | 1 -
.../ViewDownloadManagerViewModel.cs | 14 +-
.../ViewPublicFavoritesViewModel.cs | 42 +--
.../ViewModels/ViewSettingsViewModel.cs | 14 +-
.../ViewModels/ViewToolboxViewModel.cs | 14 +-
.../ViewModels/ViewVideoDetailViewModel.cs | 41 ++-
.../DownloadManager/ViewDownloadFinished.xaml | 296 +++++++++++++++++-
.../DownloadManager/ViewDownloading.xaml | 59 +++-
55 files changed, 1936 insertions(+), 226 deletions(-)
create mode 100644 src/DownKyi.Core/BiliApi/VideoStream/PlayStreamType.cs
create mode 100644 src/DownKyi.Core/Storage/Database/Download/DownloadBaseDb.cs
create mode 100644 src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs
create mode 100644 src/DownKyi.Core/Storage/Database/Download/DownloadedDb.cs
create mode 100644 src/DownKyi.Core/Storage/Database/Download/DownloadingDb.cs
create mode 100644 src/DownKyi/Converter/CountConverter.cs
create mode 100644 src/DownKyi/Models/DownloadBase.cs
create mode 100644 src/DownKyi/Models/Downloaded.cs
create mode 100644 src/DownKyi/Models/Downloading.cs
create mode 100644 src/DownKyi/Resources/nodata02.png
create mode 100644 src/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs
create mode 100644 src/DownKyi/ViewModels/DownloadManager/DownloadedItem.cs
create mode 100644 src/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/Favorites.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/FavoritesMedia.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/TabHeader.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/VideoInfoView.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/VideoPage.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/VideoQuality.cs
create mode 100644 src/DownKyi/ViewModels/PageViewModels/VideoSection.cs
create mode 100644 src/DownKyi/ViewModels/Settings/DisplayFileNamePart.cs
diff --git a/src/DownKyi.Core/Aria2cNet/Server/AriaServer.cs b/src/DownKyi.Core/Aria2cNet/Server/AriaServer.cs
index 6089865..c78853d 100644
--- a/src/DownKyi.Core/Aria2cNet/Server/AriaServer.cs
+++ b/src/DownKyi.Core/Aria2cNet/Server/AriaServer.cs
@@ -28,14 +28,16 @@ namespace DownKyi.Core.Aria2cNet.Server
ListenPort = config.ListenPort;
// aria目录
string ariaDir = Environment.CurrentDirectory + "\\aria\\";
+ //string ariaDir = StorageManager.GetAriaDir();
// 会话文件
#if DEBUG
- string sessionFile = ariaDir + "aira.session";
+ string sessionFile = Path.Combine(ariaDir, "aira.session");
+
#else
- string sessionFile = ariaDir + "aira.session.gz";
+ string sessionFile =Path.Combine(ariaDir, "aira.session.gz");
#endif
// 日志文件
- string logFile = ariaDir + "aira.log";
+ string logFile = Path.Combine(ariaDir, "aira.log");
// 自动保存会话文件的时间间隔
int saveSessionInterval = 30;
diff --git a/src/DownKyi.Core/BiliApi/VideoStream/PlayStreamType.cs b/src/DownKyi.Core/BiliApi/VideoStream/PlayStreamType.cs
new file mode 100644
index 0000000..e54edbb
--- /dev/null
+++ b/src/DownKyi.Core/BiliApi/VideoStream/PlayStreamType.cs
@@ -0,0 +1,9 @@
+namespace DownKyi.Core.BiliApi.VideoStream
+{
+ public enum PlayStreamType
+ {
+ VIDEO = 1, // 普通视频
+ BANGUMI, // 番剧、电影、电视剧等
+ CHEESE, // 课程
+ }
+}
diff --git a/src/DownKyi.Core/DownKyi.Core.csproj b/src/DownKyi.Core/DownKyi.Core.csproj
index 7e57e38..52019d0 100644
--- a/src/DownKyi.Core/DownKyi.Core.csproj
+++ b/src/DownKyi.Core/DownKyi.Core.csproj
@@ -174,6 +174,7 @@
+
@@ -239,6 +240,10 @@
+
+
+
+
diff --git a/src/DownKyi.Core/Settings/SettingsManager.cs b/src/DownKyi.Core/Settings/SettingsManager.cs
index b511e84..4530a94 100644
--- a/src/DownKyi.Core/Settings/SettingsManager.cs
+++ b/src/DownKyi.Core/Settings/SettingsManager.cs
@@ -63,7 +63,7 @@ namespace DownKyi.Core.Settings
///
private bool SetSettings()
{
- string json = JsonConvert.SerializeObject(appSettings, Formatting.Indented);
+ string json = JsonConvert.SerializeObject(appSettings);
try
{
diff --git a/src/DownKyi.Core/Storage/Constant.cs b/src/DownKyi.Core/Storage/Constant.cs
index 2fe9aa4..633ece9 100644
--- a/src/DownKyi.Core/Storage/Constant.cs
+++ b/src/DownKyi.Core/Storage/Constant.cs
@@ -10,6 +10,9 @@ namespace DownKyi.Core.Storage
// 根目录
private static string Root { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/Downkyi";
+ // Aria
+ public static string Aria { get; } = $"{Root}/Aria";
+
// 日志
public static string Logs { get; } = $"{Root}/Logs";
@@ -17,6 +20,7 @@ namespace DownKyi.Core.Storage
public static string Database { get; } = $"{Root}/Storage";
// 历史(搜索、下载) (加密)
+ public static string Download { get; } = $"{Database}/Download.db";
public static string History { get; } = $"{Database}/History.db";
// 配置
diff --git a/src/DownKyi.Core/Storage/Database/CoverDb.cs b/src/DownKyi.Core/Storage/Database/CoverDb.cs
index 15ba8ea..fa53abb 100644
--- a/src/DownKyi.Core/Storage/Database/CoverDb.cs
+++ b/src/DownKyi.Core/Storage/Database/CoverDb.cs
@@ -8,6 +8,7 @@ namespace DownKyi.Core.Storage.Database
{
private const string key = "b5018ecc-09d1-4da2-aa49-4625e41e623e";
private readonly DbHelper dbHelper = new DbHelper(StorageManager.GetCoverIndex(), key);
+ private readonly string tableName = "cover";
public CoverDb()
{
@@ -30,7 +31,7 @@ namespace DownKyi.Core.Storage.Database
{
try
{
- string sql = $"insert into cover values ({cover.Avid}, '{cover.Bvid}', {cover.Cid}, '{cover.Url}', '{cover.Md5}')";
+ string sql = $"insert into {tableName} values ({cover.Avid}, '{cover.Bvid}', {cover.Cid}, '{cover.Url}', '{cover.Md5}')";
dbHelper.ExecuteNonQuery(sql);
}
catch (Exception e)
@@ -48,7 +49,7 @@ namespace DownKyi.Core.Storage.Database
{
try
{
- string sql = $"update cover set avid={cover.Avid}, bvid='{cover.Bvid}', cid={cover.Cid}, md5='{cover.Md5}' where url glob '{cover.Url}'";
+ string sql = $"update {tableName} set avid={cover.Avid}, bvid='{cover.Bvid}', cid={cover.Cid}, md5='{cover.Md5}' where url glob '{cover.Url}'";
dbHelper.ExecuteNonQuery(sql);
}
catch (Exception e)
@@ -56,7 +57,7 @@ namespace DownKyi.Core.Storage.Database
Utils.Debugging.Console.PrintLine("Update()发生异常: {0}", e);
LogManager.Error("CoverDb", e);
}
-
+
}
///
@@ -65,7 +66,7 @@ namespace DownKyi.Core.Storage.Database
///
public List QueryAll()
{
- string sql = $"select * from cover";
+ string sql = $"select * from {tableName}";
return Query(sql);
}
@@ -76,17 +77,10 @@ namespace DownKyi.Core.Storage.Database
///
public Cover QueryByUrl(string url)
{
- string sql = $"select * from cover where url glob '{url}'";
- var query = Query(sql);
+ string sql = $"select * from {tableName} where url glob '{url}'";
+ List query = Query(sql);
- if (query.Count > 0)
- {
- return query[0];
- }
- else
- {
- return null;
- }
+ return query.Count > 0 ? query[0] : null;
}
///
@@ -96,7 +90,7 @@ namespace DownKyi.Core.Storage.Database
///
public Cover QueryByMd5(string md5)
{
- string sql = $"select * from cover where md5 glob '{md5}'";
+ string sql = $"select * from {tableName} where md5 glob '{md5}'";
var query = Query(sql);
if (query.Count > 0)
@@ -141,7 +135,7 @@ namespace DownKyi.Core.Storage.Database
///
private void CreateTable()
{
- string sql = "create table if not exists cover (avid unsigned big int, bvid varchar(20), cid unsigned big int, url varchar(255) unique, md5 varchar(32) unique)";
+ string sql = $"create table if not exists {tableName} (avid unsigned big int, bvid varchar(20), cid unsigned big int, url varchar(255) unique, md5 varchar(32) unique)";
dbHelper.ExecuteNonQuery(sql);
}
diff --git a/src/DownKyi.Core/Storage/Database/DbHelper.cs b/src/DownKyi.Core/Storage/Database/DbHelper.cs
index d2c67af..8b6db35 100644
--- a/src/DownKyi.Core/Storage/Database/DbHelper.cs
+++ b/src/DownKyi.Core/Storage/Database/DbHelper.cs
@@ -64,7 +64,7 @@ namespace DownKyi.Core.Storage.Database
/// 执行一条SQL语句
///
///
- public void ExecuteNonQuery(string sql)
+ public void ExecuteNonQuery(string sql, Action action = null)
{
lock (conn)
{
@@ -74,6 +74,8 @@ namespace DownKyi.Core.Storage.Database
using (var command = conn.CreateCommand())
{
command.CommandText = sql;
+ // 添加参数
+ action?.Invoke(command.Parameters);
command.ExecuteNonQuery();
}
tr.Commit();
diff --git a/src/DownKyi.Core/Storage/Database/Download/DownloadBaseDb.cs b/src/DownKyi.Core/Storage/Database/Download/DownloadBaseDb.cs
new file mode 100644
index 0000000..2d56986
--- /dev/null
+++ b/src/DownKyi.Core/Storage/Database/Download/DownloadBaseDb.cs
@@ -0,0 +1,11 @@
+namespace DownKyi.Core.Storage.Database.Download
+{
+ public class DownloadBaseDb : DownloadDb
+ {
+ public DownloadBaseDb()
+ {
+ tableName = "download_base";
+ CreateTable();
+ }
+ }
+}
diff --git a/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs b/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs
new file mode 100644
index 0000000..eff2c98
--- /dev/null
+++ b/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs
@@ -0,0 +1,113 @@
+using DownKyi.Core.Logging;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SQLite;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+
+namespace DownKyi.Core.Storage.Database.Download
+{
+ public class DownloadDb
+ {
+ private const string key = "bdb8eb69-3698-4af9-b722-9312d0fba623";
+ private readonly DbHelper dbHelper = new DbHelper(StorageManager.GetDownload(), key);
+ protected string tableName = "download";
+
+ ///
+ /// 关闭数据库连接
+ ///
+ public void Close()
+ {
+ dbHelper.Close();
+ }
+
+ ///
+ /// 插入新的数据
+ ///
+ ///
+ public void Insert(string uuid, object obj)
+ {
+ // 定义一个流
+ Stream stream = new MemoryStream();
+ // 定义一个格式化器
+ BinaryFormatter formatter = new BinaryFormatter();
+ // 序列化
+ formatter.Serialize(stream, obj);
+
+ byte[] array = null;
+ array = new byte[stream.Length];
+
+ //将二进制流写入数组
+ stream.Position = 0;
+ stream.Read(array, 0, (int)stream.Length);
+
+ //关闭流
+ stream.Close();
+
+ try
+ {
+ string sql = $"insert into {tableName}(id, data) values (@id, @data)";
+ dbHelper.ExecuteNonQuery(sql, new Action((para) =>
+ {
+ para.Add("@id", DbType.String).Value = uuid;
+ para.Add("@data", DbType.Binary).Value = array;
+ }));
+ }
+ catch (Exception e)
+ {
+ Utils.Debugging.Console.PrintLine("Insert()发生异常: {0}", e);
+ LogManager.Error("DownloadingDb", e);
+ }
+ }
+
+ ///
+ /// 查询所有数据
+ ///
+ ///
+ ///
+ public Dictionary QueryAll()
+ {
+ string sql = $"select * from {tableName}";
+ return Query(sql);
+ }
+
+ ///
+ /// 查询数据
+ ///
+ ///
+ ///
+ private Dictionary Query(string sql)
+ {
+ Dictionary objects = new Dictionary();
+
+ dbHelper.ExecuteQuery(sql, reader =>
+ {
+ while (reader.Read())
+ {
+ // 读取字节数组
+ byte[] array = (byte[])reader["data"];
+ // 定义一个流
+ MemoryStream stream = new MemoryStream(null);
+ //定义一个格式化器
+ BinaryFormatter formatter = new BinaryFormatter();
+ // 反序列化
+ object obj = formatter.Deserialize(stream);
+
+ objects.Add((string)reader["id"], obj);
+ }
+ });
+ return objects;
+ }
+
+
+ ///
+ /// 如果表不存在则创建表
+ ///
+ protected void CreateTable()
+ {
+ string sql = $"create table if not exists {tableName} (id varchar(255) unique, data blob)";
+ dbHelper.ExecuteNonQuery(sql);
+ }
+ }
+}
diff --git a/src/DownKyi.Core/Storage/Database/Download/DownloadedDb.cs b/src/DownKyi.Core/Storage/Database/Download/DownloadedDb.cs
new file mode 100644
index 0000000..7f69652
--- /dev/null
+++ b/src/DownKyi.Core/Storage/Database/Download/DownloadedDb.cs
@@ -0,0 +1,11 @@
+namespace DownKyi.Core.Storage.Database.Download
+{
+ public class DownloadedDb : DownloadDb
+ {
+ public DownloadedDb()
+ {
+ tableName = "downloaded";
+ CreateTable();
+ }
+ }
+}
diff --git a/src/DownKyi.Core/Storage/Database/Download/DownloadingDb.cs b/src/DownKyi.Core/Storage/Database/Download/DownloadingDb.cs
new file mode 100644
index 0000000..8569318
--- /dev/null
+++ b/src/DownKyi.Core/Storage/Database/Download/DownloadingDb.cs
@@ -0,0 +1,11 @@
+namespace DownKyi.Core.Storage.Database.Download
+{
+ public class DownloadingDb : DownloadDb
+ {
+ public DownloadingDb()
+ {
+ tableName = "downloading";
+ CreateTable();
+ }
+ }
+}
diff --git a/src/DownKyi.Core/Storage/Database/HeaderDb.cs b/src/DownKyi.Core/Storage/Database/HeaderDb.cs
index 67d3d06..6fe2645 100644
--- a/src/DownKyi.Core/Storage/Database/HeaderDb.cs
+++ b/src/DownKyi.Core/Storage/Database/HeaderDb.cs
@@ -8,6 +8,7 @@ namespace DownKyi.Core.Storage.Database
{
private const string key = "7c1f1f40-7cdf-4d11-ad28-f0137a3c5308";
private readonly DbHelper dbHelper = new DbHelper(StorageManager.GetHeaderIndex(), key);
+ private readonly string tableName = "header";
public HeaderDb()
{
@@ -30,7 +31,7 @@ namespace DownKyi.Core.Storage.Database
{
try
{
- string sql = $"insert into header values ({header.Mid}, '{header.Name}', '{header.Url}', '{header.Md5}')";
+ string sql = $"insert into {tableName} values ({header.Mid}, '{header.Name}', '{header.Url}', '{header.Md5}')";
dbHelper.ExecuteNonQuery(sql);
}
catch (Exception e)
@@ -48,7 +49,7 @@ namespace DownKyi.Core.Storage.Database
{
try
{
- string sql = $"update header set name='{header.Name}', url='{header.Url}', md5='{header.Md5}' where mid={header.Mid}";
+ string sql = $"update {tableName} set name='{header.Name}', url='{header.Url}', md5='{header.Md5}' where mid={header.Mid}";
dbHelper.ExecuteNonQuery(sql);
}
catch (Exception e)
@@ -64,7 +65,7 @@ namespace DownKyi.Core.Storage.Database
///
public List QueryAll()
{
- string sql = $"select * from header";
+ string sql = $"select * from {tableName}";
return Query(sql);
}
@@ -75,17 +76,10 @@ namespace DownKyi.Core.Storage.Database
///
public Header QueryByMid(long mid)
{
- string sql = $"select * from header where mid={mid}";
- var query = Query(sql);
+ string sql = $"select * from {tableName} where mid={mid}";
+ List query = Query(sql);
- if (query.Count > 0)
- {
- return query[0];
- }
- else
- {
- return null;
- }
+ return query.Count > 0 ? query[0] : null;
}
@@ -120,7 +114,7 @@ namespace DownKyi.Core.Storage.Database
///
private void CreateTable()
{
- string sql = "create table if not exists header (mid unsigned big int unique, name varchar(255), url varchar(255), md5 varchar(32))";
+ string sql = $"create table if not exists {tableName} (mid unsigned big int unique, name varchar(255), url varchar(255), md5 varchar(32))";
dbHelper.ExecuteNonQuery(sql);
}
diff --git a/src/DownKyi.Core/Storage/StorageManager.cs b/src/DownKyi.Core/Storage/StorageManager.cs
index 7531ef3..d8671ab 100644
--- a/src/DownKyi.Core/Storage/StorageManager.cs
+++ b/src/DownKyi.Core/Storage/StorageManager.cs
@@ -4,6 +4,26 @@ namespace DownKyi.Core.Storage
{
public static class StorageManager
{
+ ///
+ /// 获取历史记录的文件路径
+ ///
+ ///
+ public static string GetAriaDir()
+ {
+ CreateDirectory(Constant.Aria);
+ return Constant.Aria;
+ }
+
+ ///
+ /// 获取历史记录的文件路径
+ ///
+ ///
+ public static string GetDownload()
+ {
+ CreateDirectory(Constant.Database);
+ return Constant.Download;
+ }
+
///
/// 获取历史记录的文件路径
///
diff --git a/src/DownKyi/App.xaml.cs b/src/DownKyi/App.xaml.cs
index d9b8f33..ed5b1d0 100644
--- a/src/DownKyi/App.xaml.cs
+++ b/src/DownKyi/App.xaml.cs
@@ -1,4 +1,4 @@
-using DownKyi.Models;
+using DownKyi.Core.Settings;
using DownKyi.Services.Download;
using DownKyi.Utils;
using DownKyi.ViewModels;
@@ -13,7 +13,9 @@ using DownKyi.Views.Settings;
using DownKyi.Views.Toolbox;
using Prism.Ioc;
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Linq;
using System.Windows;
namespace DownKyi
@@ -104,5 +106,39 @@ namespace DownKyi
Current.Dispatcher.Invoke(callback);
}
+ ///
+ /// 下载完成列表排序
+ ///
+ ///
+ public static void SortDownloadedList(DownloadFinishedSort finishedSort)
+ {
+ List list = DownloadedList.ToList();
+ switch (finishedSort)
+ {
+ case DownloadFinishedSort.DOWNLOAD:
+ // 按下载先后排序
+ list.Sort((x, y) => { return x.Downloaded.FinishedTimestamp.CompareTo(y.Downloaded.FinishedTimestamp); });
+ break;
+ case DownloadFinishedSort.NUMBER:
+ // 按序号排序
+ list.Sort((x, y) =>
+ {
+ int compare = x.MainTitle.CompareTo(y.MainTitle);
+ return compare == 0 ? x.Order.CompareTo(y.Order) : compare;
+ });
+ break;
+ default:
+ break;
+ }
+
+ // 更新下载完成列表
+ // 如果有更好的方法再重写
+ DownloadedList.Clear();
+ foreach (DownloadedItem item in list)
+ {
+ DownloadedList.Add(item);
+ }
+ }
+
}
}
diff --git a/src/DownKyi/Converter/CountConverter.cs b/src/DownKyi/Converter/CountConverter.cs
new file mode 100644
index 0000000..d38fe78
--- /dev/null
+++ b/src/DownKyi/Converter/CountConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace DownKyi.Converter
+{
+ public class CountConverter : IValueConverter
+ {
+ public int Count { get; set; }
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return ((int)value) > Count;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DownKyi/DownKyi.csproj b/src/DownKyi/DownKyi.csproj
index f309c28..4458ad1 100644
--- a/src/DownKyi/DownKyi.csproj
+++ b/src/DownKyi/DownKyi.csproj
@@ -79,6 +79,7 @@
MSBuild:Compile
Designer
+
@@ -87,20 +88,19 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
@@ -118,6 +118,9 @@
+
+
+
@@ -447,6 +450,7 @@
PreserveNewest
+
PreserveNewest
diff --git a/src/DownKyi/Images/VectorImage.cs b/src/DownKyi/Images/VectorImage.cs
index 921d09a..8e8fb9d 100644
--- a/src/DownKyi/Images/VectorImage.cs
+++ b/src/DownKyi/Images/VectorImage.cs
@@ -7,29 +7,29 @@ namespace DownKyi.Images
private double width;
public double Width
{
- get { return width; }
- set { SetProperty(ref width, value); }
+ get => width;
+ set => SetProperty(ref width, value);
}
private double height;
public double Height
{
- get { return height; }
- set { SetProperty(ref height, value); }
+ get => height;
+ set => SetProperty(ref height, value);
}
private string data;
public string Data
{
- get { return data; }
- set { SetProperty(ref data, value); }
+ get => data;
+ set => SetProperty(ref data, value);
}
private string fill;
public string Fill
{
- get { return fill; }
- set { SetProperty(ref fill, value); }
+ get => fill;
+ set => SetProperty(ref fill, value);
}
}
diff --git a/src/DownKyi/Languages/Default.xaml b/src/DownKyi/Languages/Default.xaml
index 71c49b6..565c157 100644
--- a/src/DownKyi/Languages/Default.xaml
+++ b/src/DownKyi/Languages/Default.xaml
@@ -91,6 +91,12 @@
全部开始
全部删除
+ 已下载
+ 个视频!
+ 按下载先后排序
+ 按序号排序
+ 清空所有记录
+
按回车键应用设置
diff --git a/src/DownKyi/Models/DownloadBase.cs b/src/DownKyi/Models/DownloadBase.cs
new file mode 100644
index 0000000..53b3514
--- /dev/null
+++ b/src/DownKyi/Models/DownloadBase.cs
@@ -0,0 +1,75 @@
+using DownKyi.Core.BiliApi.BiliUtils;
+using System;
+using System.Collections.Generic;
+
+namespace DownKyi.Models
+{
+ [Serializable]
+ public class DownloadBase
+ {
+ public DownloadBase()
+ {
+ // 唯一id
+ Uuid = Guid.NewGuid().ToString("N");
+
+ // 初始化需要下载的内容
+ NeedDownloadContent = new Dictionary
+ {
+ { "downloadAudio", true },
+ { "downloadVideo", true },
+ { "downloadDanmaku", true },
+ { "downloadSubtitle", true },
+ { "downloadCover", true }
+ };
+ }
+
+ // 此条下载项的id
+ public string Uuid { get; }
+
+ // 需要下载的内容
+ public Dictionary NeedDownloadContent { get; private set; }
+
+ // 视频的id
+ public string Bvid { get; set; }
+ public long Avid { get; set; }
+ public long Cid { get; set; }
+ public long EpisodeId { get; set; }
+
+ // 视频封面的url
+ public string CoverUrl { get; set; }
+
+ // 视频page的封面的url
+ public string PageCoverUrl { get; set; }
+
+ // 分区id
+ public int ZoneId { get; set; }
+
+ // 视频序号
+ public int Order { get; set; }
+
+ // 视频主标题
+ public string MainTitle { get; set; }
+
+ // 视频标题
+ public string Name { get; set; }
+
+ // 时长
+ public string Duration { get; set; }
+
+ // 视频编码名称,AVC、HEVC
+ public string VideoCodecName { get; set; }
+
+ // 视频画质
+ public Quality Resolution { get; set; }
+
+ // 音频编码
+ public Quality AudioCodec { get; set; }
+
+ // 文件路径,不包含扩展名,所有内容均以此路径下载
+ public string FilePath { get; set; }
+
+ // 文件大小
+ public string FileSize { get; set; }
+
+ }
+}
diff --git a/src/DownKyi/Models/Downloaded.cs b/src/DownKyi/Models/Downloaded.cs
new file mode 100644
index 0000000..4c31142
--- /dev/null
+++ b/src/DownKyi/Models/Downloaded.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace DownKyi.Models
+{
+ [Serializable]
+ public class Downloaded// : DownloadBase
+ {
+ public Downloaded() : base()
+ {
+ }
+
+ // 下载速度
+ public string MaxSpeedDisplay { get; set; }
+
+ // 完成时间戳
+ public long FinishedTimestamp { get; set; }
+ public void SetFinishedTimestamp(long finishedTimestamp)
+ {
+ FinishedTimestamp = finishedTimestamp;
+
+ DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区
+ DateTime dateTime = startTime.AddSeconds(finishedTimestamp);
+ FinishedTime = dateTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 完成时间
+ public string FinishedTime { get; set; }
+
+ }
+}
diff --git a/src/DownKyi/Models/Downloading.cs b/src/DownKyi/Models/Downloading.cs
new file mode 100644
index 0000000..7147489
--- /dev/null
+++ b/src/DownKyi/Models/Downloading.cs
@@ -0,0 +1,47 @@
+using DownKyi.Core.BiliApi.VideoStream;
+using System;
+using System.Collections.Generic;
+
+namespace DownKyi.Models
+{
+ [Serializable]
+ public class Downloading// : DownloadBase
+ {
+ public Downloading() : base()
+ {
+ // 初始化下载的文件列表
+ DownloadFiles = new List();
+ }
+
+ // Aria相关
+ public string Gid { get; set; }
+
+ // 下载的文件
+ public List DownloadFiles { get; private set; }
+
+ // 视频类别
+ public PlayStreamType PlayStreamType { get; set; }
+
+ // 下载状态
+ public DownloadStatus DownloadStatus { get; set; }
+
+ // 正在下载内容(音频、视频、弹幕、字幕、封面)
+ public string DownloadContent { get; set; }
+
+ // 下载状态显示
+ public string DownloadStatusTitle { get; set; }
+
+ // 下载进度
+ public float Progress { get; set; }
+
+ // 已下载大小/文件大小
+ public string DownloadingFileSize { get; set; }
+
+ // 下载的最高速度
+ public long MaxSpeed { get; set; }
+
+ // 下载速度
+ public string SpeedDisplay { get; set; }
+
+ }
+}
diff --git a/src/DownKyi/Resources/nodata02.png b/src/DownKyi/Resources/nodata02.png
new file mode 100644
index 0000000000000000000000000000000000000000..301f3dfb03e696ff8b77e76aa755d81aea411040
GIT binary patch
literal 11421
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