2020-01-16 05:06:25 +00:00
|
|
|
|
using System;
|
2020-02-01 05:41:09 +00:00
|
|
|
|
using System.Collections.Concurrent;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
using System.Collections.Generic;
|
2020-01-19 05:51:12 +00:00
|
|
|
|
using System.IO;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Security.Claims;
|
2020-01-19 05:51:12 +00:00
|
|
|
|
using System.Text;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2020-03-02 23:16:15 +00:00
|
|
|
|
using FluentFTP;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using MongoDB.Driver;
|
|
|
|
|
using MongoDB.Driver.Linq;
|
|
|
|
|
using Nettle;
|
2020-03-02 23:16:15 +00:00
|
|
|
|
using Org.BouncyCastle.Crypto.Engines;
|
2020-03-10 20:41:45 +00:00
|
|
|
|
using Wabbajack.BuildServer.Model.Models;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
using Wabbajack.BuildServer.Models;
|
2020-01-21 12:49:49 +00:00
|
|
|
|
using Wabbajack.BuildServer.Models.JobQueue;
|
|
|
|
|
using Wabbajack.BuildServer.Models.Jobs;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
using Wabbajack.Common;
|
2020-01-21 12:49:49 +00:00
|
|
|
|
using Wabbajack.Lib;
|
|
|
|
|
using Wabbajack.Lib.Downloaders;
|
2020-01-19 05:51:12 +00:00
|
|
|
|
using Path = Alphaleonis.Win32.Filesystem.Path;
|
2020-03-02 23:16:15 +00:00
|
|
|
|
using AlphaFile = Alphaleonis.Win32.Filesystem.File;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
|
|
|
|
|
namespace Wabbajack.BuildServer.Controllers
|
|
|
|
|
{
|
|
|
|
|
public class UploadedFiles : AControllerBase<UploadedFiles>
|
|
|
|
|
{
|
2020-02-01 05:41:09 +00:00
|
|
|
|
private static ConcurrentDictionary<string, AsyncLock> _writeLocks = new ConcurrentDictionary<string, AsyncLock>();
|
2020-01-21 12:49:49 +00:00
|
|
|
|
private AppSettings _settings;
|
2020-02-25 23:10:41 +00:00
|
|
|
|
|
2020-03-10 20:41:45 +00:00
|
|
|
|
public UploadedFiles(ILogger<UploadedFiles> logger, DBContext db, AppSettings settings, SqlService sql) : base(logger, db, sql)
|
2020-01-16 05:06:25 +00:00
|
|
|
|
{
|
2020-01-21 12:49:49 +00:00
|
|
|
|
_settings = settings;
|
2020-01-16 05:06:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-19 05:51:12 +00:00
|
|
|
|
[HttpPut]
|
|
|
|
|
[Route("upload_file/{Name}/start")]
|
|
|
|
|
public async Task<IActionResult> UploadFileStreaming(string Name)
|
|
|
|
|
{
|
|
|
|
|
var guid = Guid.NewGuid();
|
|
|
|
|
var key = Encoding.UTF8.GetBytes($"{Path.GetFileNameWithoutExtension(Name)}|{guid.ToString()}|{Path.GetExtension(Name)}").ToHex();
|
2020-02-01 05:41:09 +00:00
|
|
|
|
|
|
|
|
|
_writeLocks.GetOrAdd(key, new AsyncLock());
|
|
|
|
|
|
2020-02-25 23:10:41 +00:00
|
|
|
|
System.IO.File.Create(Path.Combine("public", "tmp_files", key)).Close();
|
2020-01-19 05:51:12 +00:00
|
|
|
|
Utils.Log($"Starting Ingest for {key}");
|
|
|
|
|
return Ok(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static private HashSet<char> HexChars = new HashSet<char>("abcdef1234567890");
|
|
|
|
|
[HttpPut]
|
|
|
|
|
[Route("upload_file/{Key}/data/{Offset}")]
|
|
|
|
|
public async Task<IActionResult> UploadFilePart(string Key, long Offset)
|
|
|
|
|
{
|
|
|
|
|
if (!Key.All(a => HexChars.Contains(a)))
|
|
|
|
|
return BadRequest("NOT A VALID FILENAME");
|
|
|
|
|
Utils.Log($"Writing at position {Offset} in ingest file {Key}");
|
2020-02-01 05:41:09 +00:00
|
|
|
|
|
|
|
|
|
var ms = new MemoryStream();
|
|
|
|
|
await Request.Body.CopyToAsync(ms);
|
|
|
|
|
ms.Position = 0;
|
|
|
|
|
|
|
|
|
|
long position;
|
|
|
|
|
using (var _ = await _writeLocks[Key].Wait())
|
2020-02-25 23:10:41 +00:00
|
|
|
|
await using (var file = System.IO.File.Open(Path.Combine("public", "tmp_files", Key), FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
|
2020-01-19 05:51:12 +00:00
|
|
|
|
{
|
|
|
|
|
file.Position = Offset;
|
2020-02-01 05:41:09 +00:00
|
|
|
|
await ms.CopyToAsync(file);
|
|
|
|
|
position = file.Position;
|
2020-01-19 05:51:12 +00:00
|
|
|
|
}
|
2020-02-01 05:41:09 +00:00
|
|
|
|
return Ok(position);
|
2020-01-19 05:51:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 23:16:15 +00:00
|
|
|
|
[Authorize]
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("clean_http_uploads")]
|
|
|
|
|
public async Task<IActionResult> CleanUploads()
|
|
|
|
|
{
|
|
|
|
|
var files = await Db.UploadedFiles.AsQueryable().OrderByDescending(f => f.UploadDate).ToListAsync();
|
|
|
|
|
var seen = new HashSet<string>();
|
|
|
|
|
var duplicate = new List<UploadedFile>();
|
|
|
|
|
|
|
|
|
|
foreach (var file in files)
|
|
|
|
|
{
|
|
|
|
|
if (seen.Contains(file.Name))
|
|
|
|
|
duplicate.Add(file);
|
|
|
|
|
else
|
|
|
|
|
seen.Add(file.Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var client = new FtpClient("storage.bunnycdn.com"))
|
|
|
|
|
{
|
|
|
|
|
client.Credentials = new NetworkCredential(_settings.BunnyCDN_User, _settings.BunnyCDN_Password);
|
|
|
|
|
await client.ConnectAsync();
|
|
|
|
|
|
|
|
|
|
foreach (var dup in duplicate)
|
|
|
|
|
{
|
|
|
|
|
var final_path = Path.Combine("public", "files", dup.MungedName);
|
|
|
|
|
Utils.Log($"Cleaning upload {final_path}");
|
|
|
|
|
|
|
|
|
|
if (AlphaFile.Exists(final_path))
|
|
|
|
|
AlphaFile.Delete(final_path);
|
|
|
|
|
|
|
|
|
|
if (await client.FileExistsAsync(dup.MungedName))
|
|
|
|
|
await client.DeleteFileAsync(dup.MungedName);
|
|
|
|
|
await Db.UploadedFiles.DeleteOneAsync(f => f.Id == dup.Id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(new {Remain = seen.ToArray(), Deleted = duplicate.ToArray()}.ToJSON(prettyPrint:true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-01-19 05:51:12 +00:00
|
|
|
|
[HttpPut]
|
2020-02-01 05:41:09 +00:00
|
|
|
|
[Route("upload_file/{Key}/finish/{xxHashAsHex}")]
|
|
|
|
|
public async Task<IActionResult> UploadFileFinish(string Key, string xxHashAsHex)
|
2020-01-16 05:06:25 +00:00
|
|
|
|
{
|
2020-02-01 05:41:09 +00:00
|
|
|
|
var expectedHash = xxHashAsHex.FromHex().ToBase64();
|
2020-01-16 05:06:25 +00:00
|
|
|
|
var user = User.FindFirstValue(ClaimTypes.Name);
|
2020-01-19 05:51:12 +00:00
|
|
|
|
if (!Key.All(a => HexChars.Contains(a)))
|
|
|
|
|
return BadRequest("NOT A VALID FILENAME");
|
|
|
|
|
var parts = Encoding.UTF8.GetString(Key.FromHex()).Split('|');
|
|
|
|
|
var final_name = $"{parts[0]}-{parts[1]}{parts[2]}";
|
|
|
|
|
var original_name = $"{parts[0]}{parts[2]}";
|
|
|
|
|
|
|
|
|
|
var final_path = Path.Combine("public", "files", final_name);
|
2020-02-25 23:10:41 +00:00
|
|
|
|
System.IO.File.Move(Path.Combine("public", "tmp_files", Key), final_path);
|
2020-01-19 05:51:12 +00:00
|
|
|
|
var hash = await final_path.FileHashAsync();
|
|
|
|
|
|
2020-02-01 05:41:09 +00:00
|
|
|
|
if (expectedHash != hash)
|
|
|
|
|
{
|
|
|
|
|
System.IO.File.Delete(final_path);
|
|
|
|
|
return BadRequest($"Bad Hash, Expected: {expectedHash} Got: {hash}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_writeLocks.TryRemove(Key, out var _);
|
2020-01-19 05:51:12 +00:00
|
|
|
|
var record = new UploadedFile
|
|
|
|
|
{
|
|
|
|
|
Id = parts[1],
|
|
|
|
|
Hash = hash,
|
|
|
|
|
Name = original_name,
|
|
|
|
|
Uploader = user,
|
2020-01-22 03:43:53 +00:00
|
|
|
|
Size = new FileInfo(final_path).Length,
|
|
|
|
|
CDNName = "wabbajackpush"
|
2020-01-19 05:51:12 +00:00
|
|
|
|
};
|
|
|
|
|
await Db.UploadedFiles.InsertOneAsync(record);
|
2020-01-21 12:49:49 +00:00
|
|
|
|
await Db.Jobs.InsertOneAsync(new Job
|
|
|
|
|
{
|
2020-01-22 03:43:53 +00:00
|
|
|
|
Priority = Job.JobPriority.High, Payload = new UploadToCDN {FileId = record.Id}
|
2020-01-21 12:49:49 +00:00
|
|
|
|
});
|
2020-01-22 03:43:53 +00:00
|
|
|
|
|
2020-01-21 12:49:49 +00:00
|
|
|
|
|
2020-01-19 05:51:12 +00:00
|
|
|
|
return Ok(record.Uri);
|
2020-01-16 05:06:25 +00:00
|
|
|
|
}
|
2020-01-19 05:51:12 +00:00
|
|
|
|
|
2020-01-16 05:06:25 +00:00
|
|
|
|
|
|
|
|
|
private static readonly Func<object, string> HandleGetListTemplate = NettleEngine.GetCompiler().Compile(@"
|
|
|
|
|
<html><body>
|
|
|
|
|
<table>
|
|
|
|
|
{{each $.files }}
|
|
|
|
|
<tr><td><a href='{{$.Link}}'>{{$.Name}}</a></td><td>{{$.Size}}</td><td>{{$.Date}}</td><td>{{$.Uploader}}</td></tr>
|
|
|
|
|
{{/each}}
|
|
|
|
|
</table>
|
|
|
|
|
</body></html>
|
|
|
|
|
");
|
|
|
|
|
|
2020-01-21 12:49:49 +00:00
|
|
|
|
|
2020-01-16 05:06:25 +00:00
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("uploaded_files")]
|
|
|
|
|
public async Task<ContentResult> UploadedFilesGet()
|
|
|
|
|
{
|
|
|
|
|
var files = await Db.UploadedFiles.AsQueryable().OrderByDescending(f => f.UploadDate).ToListAsync();
|
|
|
|
|
var response = HandleGetListTemplate(new
|
|
|
|
|
{
|
|
|
|
|
files = files.Select(file => new
|
|
|
|
|
{
|
|
|
|
|
Link = file.Uri,
|
|
|
|
|
Size = file.Size.ToFileSizeString(),
|
|
|
|
|
file.Name,
|
|
|
|
|
Date = file.UploadDate,
|
|
|
|
|
file.Uploader
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
return new ContentResult
|
|
|
|
|
{
|
|
|
|
|
ContentType = "text/html",
|
|
|
|
|
StatusCode = (int) HttpStatusCode.OK,
|
|
|
|
|
Content = response
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-03-02 23:16:15 +00:00
|
|
|
|
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("uploaded_files/list")]
|
|
|
|
|
[Authorize]
|
|
|
|
|
public async Task<IActionResult> ListMyFiles()
|
|
|
|
|
{
|
|
|
|
|
var user = User.FindFirstValue(ClaimTypes.Name);
|
|
|
|
|
Utils.Log($"List Uploaded Files {user}");
|
|
|
|
|
var files = await Db.UploadedFiles.AsQueryable().Where(f => f.Uploader == user).ToListAsync();
|
|
|
|
|
return Ok(files.OrderBy(f => f.UploadDate).Select(f => f.MungedName).ToArray().ToJSON(prettyPrint:true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpDelete]
|
|
|
|
|
[Route("uploaded_files/{name}")]
|
|
|
|
|
[Authorize]
|
|
|
|
|
public async Task<IActionResult> DeleteMyFile(string name)
|
|
|
|
|
{
|
|
|
|
|
var user = User.FindFirstValue(ClaimTypes.Name);
|
|
|
|
|
Utils.Log($"Delete Uploaded File {user} {name}");
|
|
|
|
|
var files = await Db.UploadedFiles.AsQueryable().Where(f => f.Uploader == user).ToListAsync();
|
|
|
|
|
|
|
|
|
|
var to_delete = files.First(f => f.MungedName == name);
|
|
|
|
|
|
|
|
|
|
if (AlphaFile.Exists(Path.Combine("public", "files", to_delete.MungedName)))
|
|
|
|
|
AlphaFile.Delete(Path.Combine("public", "files", to_delete.MungedName));
|
|
|
|
|
|
|
|
|
|
using (var client = new FtpClient("storage.bunnycdn.com"))
|
|
|
|
|
{
|
|
|
|
|
client.Credentials = new NetworkCredential(_settings.BunnyCDN_User, _settings.BunnyCDN_Password);
|
|
|
|
|
await client.ConnectAsync();
|
|
|
|
|
if (await client.FileExistsAsync(to_delete.MungedName))
|
|
|
|
|
await client.DeleteFileAsync(to_delete.MungedName);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result = await Db.UploadedFiles.DeleteOneAsync(f => f.Id == to_delete.Id);
|
|
|
|
|
if (result.DeletedCount == 1)
|
|
|
|
|
return Ok($"Deleted {name}");
|
|
|
|
|
return NotFound(name);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-19 05:51:12 +00:00
|
|
|
|
|
|
|
|
|
|
2020-01-16 05:06:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|