Skip to content

Commit 25895f0

Browse files
committed
feat: 跨设备资源目录导入
1 parent 201665e commit 25895f0

12 files changed

Lines changed: 328 additions & 51 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Microsoft.AspNetCore.Mvc.Filters;
2+
using Microsoft.AspNetCore.Mvc.ModelBinding;
3+
4+
namespace MaiChartManager.Attributes;
5+
6+
// https://code-maze.com/aspnetcore-upload-large-files/
7+
8+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
9+
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
10+
{
11+
public void OnResourceExecuting(ResourceExecutingContext context)
12+
{
13+
var factories = context.ValueProviderFactories;
14+
factories.RemoveType<FormValueProviderFactory>();
15+
factories.RemoveType<FormFileValueProviderFactory>();
16+
factories.RemoveType<JQueryFormValueProviderFactory>();
17+
}
18+
public void OnResourceExecuted(ResourceExecutedContext context)
19+
{
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Mvc.Filters;
3+
4+
namespace MaiChartManager.Attributes;
5+
6+
// https://code-maze.com/aspnetcore-upload-large-files/
7+
8+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
9+
public class MultipartFormDataAttribute : ActionFilterAttribute
10+
{
11+
public override void OnActionExecuting(ActionExecutingContext context)
12+
{
13+
var request = context.HttpContext.Request;
14+
if (request.HasFormContentType
15+
&& request.ContentType.StartsWith("multipart/form-data", StringComparison.OrdinalIgnoreCase))
16+
{
17+
return;
18+
}
19+
context.Result = new StatusCodeResult(StatusCodes.Status415UnsupportedMediaType);
20+
}
21+
}

MaiChartManager/Controllers/AssetDirController.cs

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
using System.IO.Compression;
2+
using MaiChartManager.Attributes;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.AspNetCore.WebUtilities;
5+
using Microsoft.Net.Http.Headers;
26
using Microsoft.VisualBasic.FileIO;
37

48
namespace MaiChartManager.Controllers;
59

610
[ApiController]
711
[Route("MaiChartManagerServlet/[action]Api")]
8-
public class AssetDirController(StaticSettings settings, ILogger<AssetDirController> logger)
12+
public class AssetDirController(StaticSettings settings, ILogger<AssetDirController> logger) : ControllerBase
913
{
1014
[HttpPost]
1115
public void CreateAssetDir([FromBody] string dir)
@@ -32,7 +36,7 @@ public record GetAssetDirTxtValueRequest(string DirName, string FileName);
3236
[HttpPost]
3337
public string GetAssetDirTxtValue([FromBody] GetAssetDirTxtValueRequest req)
3438
{
35-
return File.ReadAllText(Path.Combine(StaticSettings.StreamingAssets, req.DirName, req.FileName));
39+
return System.IO.File.ReadAllText(Path.Combine(StaticSettings.StreamingAssets, req.DirName, req.FileName));
3640
}
3741

3842
[HttpDelete]
@@ -46,7 +50,7 @@ public record PutAssetDirTxtValueRequest(string DirName, string FileName, string
4650
[HttpPut]
4751
public void PutAssetDirTxtValue([FromBody] PutAssetDirTxtValueRequest req)
4852
{
49-
File.WriteAllText(Path.Combine(StaticSettings.StreamingAssets, req.DirName, req.FileName), req.Content);
53+
System.IO.File.WriteAllText(Path.Combine(StaticSettings.StreamingAssets, req.DirName, req.FileName), req.Content);
5054
}
5155

5256
[HttpPost]
@@ -75,35 +79,70 @@ public void RequestLocalImportDir()
7579

7680
if (!StaticSettings.ADirRegex().IsMatch(destName) || StaticSettings.AssetsDirs.Contains(destName))
7781
{
78-
var id = 0;
79-
// 找到下一个未被使用的名称
80-
foreach (var dir in StaticSettings.AssetsDirs)
81-
{
82-
var strId = StaticSettings.ADirRegex().Match(dir).Groups[1].Value;
83-
var num = int.Parse(strId);
84-
if (num > id) id = num;
85-
}
82+
destName = settings.GetFreeAssetDir();
83+
}
84+
85+
var dest = Path.Combine(StaticSettings.StreamingAssets, destName);
86+
logger.LogInformation("Src: {src} Dest: {dest}", src, dest);
87+
FileSystem.CopyDirectory(src, dest, UIOption.AllDialogs);
88+
settings.ScanGenre();
89+
settings.ScanVersionList();
90+
settings.ScanAssetBundles();
91+
settings.ScanSoundData();
92+
settings.ScanMovieData();
93+
}
94+
95+
public record UploadAssetDirResult(string DirName);
8696

87-
id++;
88-
if (id > 999)
97+
[HttpPost]
98+
// https://code-maze.com/aspnetcore-upload-large-files/
99+
[DisableRequestSizeLimit]
100+
[DisableFormValueModelBinding]
101+
[Route("{destName}")]
102+
// 看起来就算用 IAsyncEnumerable 获取文件,还是会等所以文件都上传完了再调用这个方法
103+
// 而且不知道为什么上传到一半会 Network Error
104+
public async Task<UploadAssetDirResult> UploadAssetDir(string? destName)
105+
{
106+
logger.LogInformation("UploadAssetDir");
107+
108+
if (destName is null || !StaticSettings.ADirRegex().IsMatch(destName) || StaticSettings.AssetsDirs.Contains(destName))
109+
{
110+
destName = settings.GetFreeAssetDir();
111+
}
112+
113+
var dest = Path.Combine(StaticSettings.StreamingAssets, destName);
114+
115+
// https://stackoverflow.com/questions/36437282/dealing-with-large-file-uploads-on-asp-net-core-1-0
116+
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
117+
var reader = new MultipartReader(boundary!, Request.Body);
118+
var section = await reader.ReadNextSectionAsync();
119+
120+
while (section != null)
121+
{
122+
if (ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
89123
{
90-
id = 999;
91-
while (StaticSettings.AssetsDirs.Contains($"A{id:000}"))
124+
if (contentDisposition.DispositionType.Equals("form-data") && !string.IsNullOrEmpty(contentDisposition.FileName.Value))
92125
{
93-
id--;
126+
var fileName = contentDisposition.FileName.Value;
127+
await using var stream = section.Body;
128+
// 处理文件流
129+
logger.LogInformation("UploadAssetDir: {destName} {file}", destName, fileName);
130+
var filePath = Path.Combine(dest, fileName.TrimStart('/', '\\'));
131+
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
132+
await using var fileStream = new FileStream(filePath, FileMode.Create);
133+
await stream.CopyToAsync(fileStream);
94134
}
95135
}
96136

97-
destName = $"A{id:000}";
137+
section = await reader.ReadNextSectionAsync();
98138
}
99139

100-
var dest = Path.Combine(StaticSettings.StreamingAssets, destName);
101-
logger.LogInformation("Src: {src} Dest: {dest}", src, dest);
102-
FileSystem.CopyDirectory(src, dest, UIOption.AllDialogs);
103140
settings.ScanGenre();
104141
settings.ScanVersionList();
105142
settings.ScanAssetBundles();
106143
settings.ScanSoundData();
107144
settings.ScanMovieData();
145+
146+
return new UploadAssetDirResult(destName);
108147
}
109148
}

MaiChartManager/Controllers/ModController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace MaiChartManager.Controllers;
66

77
[ApiController]
88
[Route("MaiChartManagerServlet/[action]Api")]
9-
public class ModController(StaticSettings settings, ILogger<StaticSettings> logger) : ControllerBase
9+
public class ModController(StaticSettings settings, ILogger<ModController> logger) : ControllerBase
1010
{
1111
[HttpGet]
1212
public bool IsMelonInstalled()

MaiChartManager/Front/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@vitejs/plugin-vue-jsx": "^4.0.1",
2929
"@vueuse/core": "^11.0.1",
3030
"@zip.js/zip.js": "^2.7.52",
31+
"axios": "^1.7.7",
3132
"change-case": "^5.4.4",
3233
"color": "^4.2.3",
3334
"naive-ui": "^2.39.0",

MaiChartManager/Front/pnpm-lock.yaml

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MaiChartManager/Front/src/client/apiGen.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ export interface UXConfig {
208208
execOnEntry?: string | null;
209209
}
210210

211+
export interface UploadAssetDirResult {
212+
dirName?: string | null;
213+
}
214+
211215
export interface VersionXml {
212216
assetDir?: string | null;
213217
/** @format int32 */
@@ -653,6 +657,21 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
653657
...params,
654658
}),
655659

660+
/**
661+
* No description
662+
*
663+
* @tags AssetDir
664+
* @name UploadAssetDir
665+
* @request POST:/MaiChartManagerServlet/UploadAssetDirApi/{destName}
666+
*/
667+
UploadAssetDir: (destName: string, params: RequestParams = {}) =>
668+
this.request<UploadAssetDirResult, any>({
669+
path: `/MaiChartManagerServlet/UploadAssetDirApi/${destName}`,
670+
method: "POST",
671+
format: "json",
672+
...params,
673+
}),
674+
656675
/**
657676
* No description
658677
*

0 commit comments

Comments
 (0)