Skip to content

Commit

Permalink
Merge pull request #750 from gnattu/server
Browse files Browse the repository at this point in the history
Feat: 实现JSON API服务器
  • Loading branch information
nilaoda committed Oct 26, 2023
2 parents cd34fa1 + 5d6623c commit 0090521
Show file tree
Hide file tree
Showing 8 changed files with 882 additions and 442 deletions.
2 changes: 1 addition & 1 deletion BBDown/BBDown.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down
194 changes: 194 additions & 0 deletions BBDown/BBDownApiServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
using BBDown.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
namespace BBDown;

public class BBDownApiServer
{
private WebApplication? app;
private List<DownloadTask> runningTasks = [];
private List<DownloadTask> finishedTasks = [];

public void SetUpServer()
{
if (app is not null) return;
var builder = WebApplication.CreateSlimBuilder();
builder.Services.ConfigureHttpJsonOptions((options) =>
{
options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(options.SerializerOptions.TypeInfoResolver, AppJsonSerializerContext.Default);
});
builder.Services.AddCors((options) =>
{
options.AddPolicy("AllowAnyOrigin",
policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
app = builder.Build();
app.UseCors("AllowAnyOrigin");
var taskStatusApi = app.MapGroup("/get-tasks");
taskStatusApi.MapGet("/", handler: () => Results.Json(new DownloadTaskCollection(runningTasks, finishedTasks), AppJsonSerializerContext.Default.DownloadTaskCollection));
taskStatusApi.MapGet("/running", handler: () => Results.Json(runningTasks, AppJsonSerializerContext.Default.ListDownloadTask));
taskStatusApi.MapGet("/finished", handler: () => Results.Json(finishedTasks, AppJsonSerializerContext.Default.ListDownloadTask));
taskStatusApi.MapGet("/{id}", (string id) =>
{
var task = finishedTasks.FirstOrDefault(a => a.Aid == id);
var rtask = runningTasks.FirstOrDefault(a => a.Aid == id);
if (rtask is not null) task = rtask;
if (task is null)
{
return Results.NotFound();
}
else
{
return Results.Json(task, AppJsonSerializerContext.Default.DownloadTask);
}
});
app.MapPost("/add-task", (MyOptionBindingResult<MyOption> bindingResult) =>
{
if (!bindingResult.IsValid)
{
//var exception = bindingResult.Exception;
return Results.BadRequest("输入有误");
}
var req = bindingResult.Result;
AddDownloadTaskAsync(req);

Check warning on line 71 in BBDown/BBDownApiServer.cs

View workflow job for this annotation

GitHub Actions / build-win-x64-arm64

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 71 in BBDown/BBDownApiServer.cs

View workflow job for this annotation

GitHub Actions / build-win-x64-arm64

Possible null reference argument for parameter 'option' in 'Task BBDownApiServer.AddDownloadTaskAsync(MyOption option)'.

Check warning on line 71 in BBDown/BBDownApiServer.cs

View workflow job for this annotation

GitHub Actions / build-mac-x64-arm64

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 71 in BBDown/BBDownApiServer.cs

View workflow job for this annotation

GitHub Actions / build-mac-x64-arm64

Possible null reference argument for parameter 'option' in 'Task BBDownApiServer.AddDownloadTaskAsync(MyOption option)'.
return Results.Ok();
});
var finishedRemovalApi = app.MapGroup("remove-finished");
finishedRemovalApi.MapGet("/", () => { finishedTasks.RemoveAll(t => true); return Results.Ok(); });
finishedRemovalApi.MapGet("/failed", () => { finishedTasks.RemoveAll(t => !t.IsSuccessful); return Results.Ok(); });
finishedRemovalApi.MapGet("/{id}", (string id) => { finishedTasks.RemoveAll(t => t.Aid == id); return Results.Ok(); });
}

public void Run(string url)
{
if (app is null) return;
bool result = Uri.TryCreate(url, UriKind.Absolute, out Uri? uriResult)
&& uriResult.Scheme == Uri.UriSchemeHttp;
if (!result)
{
Console.BackgroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine($"{url}不是合法的http URL,url示例:http://0.0.0.0:5000");
Console.WriteLine("如果您需要https,请额外配置反向代理");
Console.ResetColor();
Console.WriteLine();
Thread.Sleep(1);
Environment.Exit(1);
}
app.Run(url);
}

private async Task AddDownloadTaskAsync(MyOption option)
{
var aid = await BBDownUtil.GetAvIdAsync(option.Url);
if (runningTasks.Any(task => task.Aid == aid)) return;
var task = new DownloadTask(aid, option.Url, DateTimeOffset.Now.ToUnixTimeSeconds());
runningTasks.Add(task);
try
{
var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, aidOri, delay) = Program.SetUpWork(option);
var (fetchedAid, vInfo, apiType) = await Program.GetVideoInfoAsync(option, aidOri, input);
task.Title = vInfo.Title;
task.Pic = vInfo.Pic;
task.VideoPubTime = vInfo.PubTime;
await Program.DownloadPageAsync(option, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku,
input, savePathFormat, lang, fetchedAid, delay, apiType, task);
task.IsSuccessful = true;
}
catch (Exception e)
{
Console.BackgroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine($"{aid}下载失败");
var msg = Config.DEBUG_LOG ? e.ToString() : e.Message;
Console.Write($"{msg}{Environment.NewLine}请尝试升级到最新版本后重试!");
Console.ResetColor();
Console.WriteLine();
}
task.TaskFinishTime = DateTimeOffset.Now.ToUnixTimeSeconds();
if (task.IsSuccessful)
{
task.Progress = 1f;
task.DownloadSpeed = (double)(task.TotalDownloadedBytes / (task.TaskFinishTime - task.TaskCreateTime));
}
runningTasks.Remove(task);
finishedTasks.Add(task);
}
}

public record DownloadTask(string Aid, string Url, long TaskCreateTime)
{
[JsonInclude]
public string? Title = null;
[JsonInclude]
public string? Pic = null;
[JsonInclude]
public long? VideoPubTime = null;
[JsonInclude]
public long? TaskFinishTime = null;
[JsonInclude]
public double Progress = 0f;
[JsonInclude]
public double DownloadSpeed = 0f;
[JsonInclude]
public double TotalDownloadedBytes = 0f;
[JsonInclude]
public bool IsSuccessful = false;
};
public record DownloadTaskCollection(List<DownloadTask> Running, List<DownloadTask> Finished);

record struct MyOptionBindingResult<T>(T? Result, Exception? Exception)
{
public bool IsValid => Exception is null;

public static async ValueTask<MyOptionBindingResult<MyOption>> BindAsync(HttpContext httpContext)
{
try
{
var item = await httpContext.Request.ReadFromJsonAsync(SourceGenerationContext.Default.MyOption);

if (item is null) return new(default, new NoNullAllowedException());

return new(item, null);
}
catch (Exception ex)
{
return new(default, ex);
}
}
}

[JsonSerializable(typeof(ProblemDetails))]
[JsonSerializable(typeof(ValidationProblemDetails))]
[JsonSerializable(typeof(HttpValidationProblemDetails))]
[JsonSerializable(typeof(DownloadTask))]
[JsonSerializable(typeof(List<DownloadTask>))]
[JsonSerializable(typeof(DownloadTaskCollection))]
public partial class AppJsonSerializerContext : JsonSerializerContext
{

}

[JsonSerializable(typeof(MyOption))]
internal partial class SourceGenerationContext : JsonSerializerContext
{

}
5 changes: 3 additions & 2 deletions BBDown/BBDownDownloadUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class DownloadConfig
public string Aria2cArgs { get; set; } = string.Empty;
public bool ForceHttp { get; set; } = false;
public bool MultiThread { get; set; } = false;
public DownloadTask? RelatedTask { get; set; } = null;
}

private static async Task RangeDownloadToTmpAsync(int id, string url, string tmpName, long fromPosition, long? toPosition, Action<int, long, long> onProgress, bool failOnRangeNotSupported = false)
Expand Down Expand Up @@ -88,7 +89,7 @@ public static async Task DownloadFile(string url, string path, DownloadConfig co
reDown:
try
{
using var progress = new ProgressBar();
using var progress = new ProgressBar(config.RelatedTask);
await RangeDownloadToTmpAsync(0, url, tmpName, 0, null, (_, downloaded, total) => progress.Report((double)downloaded / total, downloaded));
File.Move(tmpName, path, true);
}
Expand Down Expand Up @@ -125,7 +126,7 @@ public static async Task MultiThreadDownloadFileAsync(string url, string path, D
ConcurrentDictionary<int, long> clipProgress = new();
foreach (var i in allClips) clipProgress[i.index] = 0;

using var progress = new ProgressBar();
using var progress = new ProgressBar(config.RelatedTask);
progress.Report(0);
await Parallel.ForEachAsync(allClips, async (clip, _) =>
{
Expand Down
Loading

0 comments on commit 0090521

Please sign in to comment.