2016-11-14 12:17:07 +01:00
|
|
|
|
2024-11-24 17:08:50 +00:00
|
|
|
|
2016-11-14 12:17:07 +01:00
|
|
|
using System.Security.Claims;
|
2023-03-19 17:57:55 +00:00
|
|
|
using Microsoft.AspNetCore.Html;
|
|
|
|
|
using Microsoft.Extensions.FileProviders;
|
2016-11-30 16:45:37 +01:00
|
|
|
using Yavsc.Models;
|
|
|
|
|
using Yavsc.Models.FileSystem;
|
2019-01-30 11:14:32 +00:00
|
|
|
using Yavsc.Models.Streaming;
|
2017-01-13 16:31:40 +01:00
|
|
|
using Yavsc.ViewModels;
|
2024-11-24 17:08:50 +00:00
|
|
|
using SixLabors.ImageSharp;
|
|
|
|
|
using SixLabors.ImageSharp.Processing;
|
2025-02-14 00:20:35 +00:00
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Yavsc.Exceptions;
|
2025-02-17 23:56:28 +00:00
|
|
|
using Yavsc.Helpers;
|
|
|
|
|
using Yavsc.Abstract.Helpers;
|
|
|
|
|
namespace Yavsc.Server.Helpers
|
2016-05-17 18:22:18 +02:00
|
|
|
{
|
2019-08-21 17:23:58 +01:00
|
|
|
public static class FileSystemHelpers
|
2016-11-30 16:45:37 +01:00
|
|
|
{
|
2025-02-14 00:20:35 +00:00
|
|
|
public static async Task SaveAsAsync(this IFormFile formFile, string path)
|
|
|
|
|
{
|
|
|
|
|
if (formFile.Length > 0) {
|
|
|
|
|
using (Stream fileStream = new FileStream(path, FileMode.Create)) {
|
|
|
|
|
await formFile.CopyToAsync(fileStream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-04 18:05:21 +00:00
|
|
|
public static FileReceivedInfo ReceiveProSignature(this ClaimsPrincipal user, string billingCode, long estimateId, IFormFile formFile, string signType)
|
2016-11-30 16:45:37 +01:00
|
|
|
{
|
2025-03-04 18:05:21 +00:00
|
|
|
var item = new FileReceivedInfo(
|
|
|
|
|
Config.SiteSetup.Bills,
|
|
|
|
|
AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, estimateId));
|
2016-11-30 16:45:37 +01:00
|
|
|
|
2025-03-04 18:05:21 +00:00
|
|
|
var fi = new FileInfo(item.FullName);
|
|
|
|
|
if (fi.Exists) item.Overridden = true;
|
2016-09-05 00:24:43 +02:00
|
|
|
|
2018-03-26 19:27:29 +02:00
|
|
|
using (var org = formFile.OpenReadStream())
|
|
|
|
|
{
|
2024-11-24 17:08:50 +00:00
|
|
|
using Image image = Image.Load(org);
|
2025-03-04 18:05:21 +00:00
|
|
|
image.Save(fi.FullName);
|
2018-03-26 19:27:29 +02:00
|
|
|
}
|
|
|
|
|
return item;
|
2016-11-14 12:17:07 +01:00
|
|
|
}
|
2017-04-10 23:54:06 +02:00
|
|
|
|
2024-11-23 14:23:47 +00:00
|
|
|
public static string GetAvatarUri(this ApplicationUser user)
|
|
|
|
|
{
|
|
|
|
|
return $"/{Config.SiteSetup.Avatars}/{user.UserName}.png";
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 15:53:13 +00:00
|
|
|
public static string EnsureDestinationDirectory(
|
2016-11-30 16:45:37 +01:00
|
|
|
this ClaimsPrincipal user,
|
|
|
|
|
string subpath)
|
|
|
|
|
{
|
2018-03-26 19:27:29 +02:00
|
|
|
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.Identity.Name);
|
2025-11-16 15:53:13 +00:00
|
|
|
if (!string.IsNullOrWhiteSpace(subpath))
|
|
|
|
|
{
|
2018-01-16 13:35:54 +01:00
|
|
|
if (!subpath.IsValidYavscPath())
|
2016-11-30 16:45:37 +01:00
|
|
|
{
|
2017-04-11 01:20:00 +02:00
|
|
|
throw new InvalidPathException();
|
2016-11-30 16:45:37 +01:00
|
|
|
}
|
2017-04-11 01:20:00 +02:00
|
|
|
root = Path.Combine(root, subpath);
|
|
|
|
|
}
|
2017-09-02 17:09:09 +02:00
|
|
|
var di = new DirectoryInfo(root);
|
|
|
|
|
if (!di.Exists) di.Create();
|
2018-03-12 23:49:05 +01:00
|
|
|
return di.FullName;
|
2016-11-30 16:45:37 +01:00
|
|
|
}
|
2018-01-16 13:35:54 +01:00
|
|
|
|
2019-03-25 09:26:32 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// Deletes user file.
|
|
|
|
|
/// User info is modified, but not save in db.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="user"></param>
|
|
|
|
|
/// <param name="fileName"></param>
|
|
|
|
|
|
2017-05-28 00:42:27 +02:00
|
|
|
public static void DeleteUserFile(this ApplicationUser user, string fileName)
|
|
|
|
|
{
|
2018-03-26 21:31:20 +02:00
|
|
|
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
2017-05-28 00:42:27 +02:00
|
|
|
var fi = new FileInfo(Path.Combine(root, fileName));
|
|
|
|
|
if (!fi.Exists) return ;
|
|
|
|
|
fi.Delete();
|
|
|
|
|
user.DiskUsage -= fi.Length;
|
|
|
|
|
}
|
2019-09-04 01:25:36 +01:00
|
|
|
|
2019-09-05 21:01:44 +01:00
|
|
|
public static FsOperationInfo DeleteUserDirOrFile(this ApplicationUser user, string dirName)
|
2019-09-04 01:25:36 +01:00
|
|
|
{
|
|
|
|
|
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
|
|
|
|
if (string.IsNullOrEmpty(dirName))
|
2019-09-08 05:17:21 +01:00
|
|
|
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "specify a directory or file name"} ;
|
2019-09-04 01:25:36 +01:00
|
|
|
|
|
|
|
|
var di = new DirectoryInfo(Path.Combine(root, dirName));
|
2019-09-05 21:01:44 +01:00
|
|
|
if (!di.Exists) {
|
|
|
|
|
var fi = new FileInfo(Path.Combine(root, dirName));
|
2019-09-08 05:17:21 +01:00
|
|
|
if (!fi.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "non existent"} ;
|
2019-09-05 21:01:44 +01:00
|
|
|
fi.Delete();
|
|
|
|
|
user.DiskUsage -= fi.Length;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (di.GetDirectories().Length>0 || di.GetFiles().Length>0)
|
2019-09-08 05:17:21 +01:00
|
|
|
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "dir is not empty, refusing to remove it"} ;
|
2019-09-05 21:01:44 +01:00
|
|
|
di.Delete();
|
|
|
|
|
}
|
2019-09-04 01:25:36 +01:00
|
|
|
return new FsOperationInfo { Done = true };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static FsOperationInfo MoveUserDir(this ApplicationUser user, string fromDirName, string toDirName)
|
|
|
|
|
{
|
|
|
|
|
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
|
|
|
|
if (string.IsNullOrEmpty(fromDirName))
|
2019-09-08 05:17:21 +01:00
|
|
|
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest , ErrorMessage = "specify a dir name "} ;
|
2019-09-04 01:25:36 +01:00
|
|
|
|
|
|
|
|
var di = new DirectoryInfo(Path.Combine(root, fromDirName));
|
2019-09-08 05:17:21 +01:00
|
|
|
if (!di.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "fromDirName: non existent"} ;
|
2019-09-04 01:25:36 +01:00
|
|
|
|
|
|
|
|
|
2019-09-08 05:17:21 +01:00
|
|
|
if (string.IsNullOrEmpty(toDirName)) toDirName = ".";
|
2019-09-04 01:25:36 +01:00
|
|
|
var destPath = Path.Combine(root, toDirName);
|
|
|
|
|
|
|
|
|
|
|
2019-09-08 05:17:21 +01:00
|
|
|
var fout = new FileInfo(destPath);
|
|
|
|
|
if (fout.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "destination is a regular file" } ;
|
2019-09-04 01:25:36 +01:00
|
|
|
|
|
|
|
|
|
2019-09-08 05:17:21 +01:00
|
|
|
var dout = new DirectoryInfo(destPath);
|
2019-09-04 01:25:36 +01:00
|
|
|
if (dout.Exists) {
|
2019-09-08 05:17:21 +01:00
|
|
|
destPath = Path.Combine(destPath, dout.Name);
|
2019-09-04 01:25:36 +01:00
|
|
|
}
|
|
|
|
|
di.MoveTo(destPath);
|
|
|
|
|
return new FsOperationInfo { Done = true };
|
|
|
|
|
}
|
2019-09-08 05:17:21 +01:00
|
|
|
public static FsOperationInfo MoveUserFileToDir(this ApplicationUser user, string fileNameFrom, string fileNameDest)
|
|
|
|
|
{
|
|
|
|
|
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
|
|
|
|
var fi = new FileInfo(Path.Combine(root, fileNameFrom));
|
|
|
|
|
if (!fi.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.NotFound, ErrorMessage = "no file to move" } ;
|
|
|
|
|
string dest;
|
|
|
|
|
if (!string.IsNullOrEmpty(fileNameDest)) dest = Path.Combine(root, fileNameDest);
|
|
|
|
|
else dest = root;
|
|
|
|
|
var fo = new FileInfo(dest);
|
|
|
|
|
if (fo.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.DestExists , ErrorMessage = "destination file name is an existing file" } ;
|
|
|
|
|
var dout = new DirectoryInfo(dest);
|
|
|
|
|
if (!dout.Exists) dout.Create();
|
|
|
|
|
fi.MoveTo(Path.Combine(dout.FullName, fi.Name));
|
|
|
|
|
return new FsOperationInfo { Done = true };
|
|
|
|
|
}
|
2019-09-04 01:25:36 +01:00
|
|
|
public static FsOperationInfo MoveUserFile(this ApplicationUser user, string fileNameFrom, string fileNameDest)
|
|
|
|
|
{
|
|
|
|
|
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
|
|
|
|
var fi = new FileInfo(Path.Combine(root, fileNameFrom));
|
2019-09-08 05:17:21 +01:00
|
|
|
if (!fi.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.NotFound, ErrorMessage = "no file to move" } ;
|
2019-09-04 01:25:36 +01:00
|
|
|
var fo = new FileInfo(Path.Combine(root, fileNameDest));
|
2019-09-08 05:17:21 +01:00
|
|
|
if (fo.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.DestExists , ErrorMessage = "destination file name is an existing file" } ;
|
2019-09-04 01:25:36 +01:00
|
|
|
fi.MoveTo(fo.FullName);
|
|
|
|
|
return new FsOperationInfo { Done = true };
|
|
|
|
|
}
|
2019-03-31 00:16:53 +00:00
|
|
|
|
2019-08-25 01:47:46 +01:00
|
|
|
static string ParseFileNameFromDisposition(string disposition)
|
|
|
|
|
{
|
|
|
|
|
// form-data_ name=_file__ filename=_Constants.Private.cs_
|
|
|
|
|
var parts = disposition.Split(' ');
|
|
|
|
|
var filename = parts[2].Split('=')[1];
|
|
|
|
|
filename = filename.Substring(1,filename.Length-2);
|
|
|
|
|
return filename;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 00:16:53 +00:00
|
|
|
public static void AddQuota(this ApplicationUser user, int quota)
|
|
|
|
|
{
|
|
|
|
|
user.DiskQuota += quota;
|
|
|
|
|
}
|
2025-03-04 18:05:21 +00:00
|
|
|
public static FileReceivedInfo ReceiveUserFile(this ApplicationUser user, string root, IFormFile f, string destFileName = null)
|
2016-11-30 16:45:37 +01:00
|
|
|
{
|
2019-08-25 01:47:46 +01:00
|
|
|
return ReceiveUserFile(user, root, f.OpenReadStream(), destFileName ?? ParseFileNameFromDisposition(f.ContentDisposition), f.ContentType, CancellationToken.None);
|
2019-06-27 10:30:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-04 18:05:21 +00:00
|
|
|
public static FileReceivedInfo ReceiveUserFile(this ApplicationUser user, string root, Stream inputStream, string destFileName, string contentType, CancellationToken token)
|
2019-06-27 10:30:28 +01:00
|
|
|
{
|
|
|
|
|
// TODO lock user's disk usage for this scope,
|
|
|
|
|
// this process is not safe at concurrent access.
|
|
|
|
|
long usage = user.DiskUsage;
|
2016-11-30 16:45:37 +01:00
|
|
|
|
2025-03-04 18:05:21 +00:00
|
|
|
var item = new FileReceivedInfo
|
|
|
|
|
(root, AbstractFileSystemHelpers.FilterFileName(destFileName));
|
2019-06-27 10:30:28 +01:00
|
|
|
var fi = new FileInfo(Path.Combine(root, item.FileName));
|
|
|
|
|
if (fi.Exists)
|
|
|
|
|
{
|
2025-03-04 18:05:21 +00:00
|
|
|
item.Overridden = true;
|
2019-06-27 10:30:28 +01:00
|
|
|
usage -= fi.Length;
|
|
|
|
|
}
|
|
|
|
|
using (var dest = fi.OpenWrite())
|
|
|
|
|
{
|
|
|
|
|
using (inputStream)
|
|
|
|
|
{
|
|
|
|
|
const int blen = 1024;
|
|
|
|
|
byte[] buffer = new byte[blen];
|
|
|
|
|
int len = 0;
|
|
|
|
|
while (!token.IsCancellationRequested && (len=inputStream.Read(buffer, 0, blen))>0)
|
2016-11-30 16:45:37 +01:00
|
|
|
{
|
2019-06-27 10:30:28 +01:00
|
|
|
dest.Write(buffer, 0, len);
|
|
|
|
|
usage += len;
|
|
|
|
|
if (usage >= user.DiskQuota) break;
|
2016-11-30 16:45:37 +01:00
|
|
|
}
|
2019-03-31 00:16:53 +00:00
|
|
|
user.DiskUsage = usage;
|
2016-11-30 16:45:37 +01:00
|
|
|
dest.Close();
|
2019-06-27 10:30:28 +01:00
|
|
|
inputStream.Close();
|
2016-11-30 16:45:37 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-03-31 00:16:53 +00:00
|
|
|
if (usage >= user.DiskQuota) {
|
2025-03-04 18:05:21 +00:00
|
|
|
item.QuotaOffense = true;
|
2019-03-31 00:16:53 +00:00
|
|
|
}
|
2016-12-01 17:34:29 +01:00
|
|
|
user.DiskUsage = usage;
|
2016-11-30 16:45:37 +01:00
|
|
|
return item;
|
|
|
|
|
}
|
2019-06-27 10:30:28 +01:00
|
|
|
|
2018-01-16 13:35:54 +01:00
|
|
|
public static HtmlString FileLink(this RemoteFileInfo info, string username, string subpath)
|
2017-01-13 16:31:40 +01:00
|
|
|
{
|
2019-08-21 17:23:58 +01:00
|
|
|
return new HtmlString(
|
2024-02-25 18:05:10 +00:00
|
|
|
$"{Config.UserFilesOptions.RequestPath}/{username}/{subpath}/{info.Name}" );
|
2017-01-13 16:31:40 +01:00
|
|
|
}
|
2019-09-04 01:25:36 +01:00
|
|
|
|
|
|
|
|
public static RemoteFileInfo FileInfo(this ApplicationUser user, string path)
|
|
|
|
|
{
|
2024-02-25 18:05:10 +00:00
|
|
|
IFileInfo info = Config.UserFilesOptions.FileProvider.GetFileInfo($"{user.UserName}/{path}");
|
2019-09-04 01:25:36 +01:00
|
|
|
if (!info.Exists) return null;
|
|
|
|
|
return new RemoteFileInfo{ Name = info.Name, Size = info.Length, LastModified = info.LastModified.UtcDateTime };
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-04 18:05:21 +00:00
|
|
|
public static FileReceivedInfo ReceiveAvatar(this ApplicationUser user, IFormFile formFile)
|
2016-12-01 17:34:29 +01:00
|
|
|
{
|
2025-03-04 18:05:21 +00:00
|
|
|
var item = new FileReceivedInfo
|
|
|
|
|
(Config.AvatarsOptions.RequestPath.ToUriComponent(),
|
|
|
|
|
user.UserName + ".png");
|
2016-12-01 17:34:29 +01:00
|
|
|
using (var org = formFile.OpenReadStream())
|
|
|
|
|
{
|
2024-11-24 17:08:50 +00:00
|
|
|
using Image image = Image.Load(org);
|
|
|
|
|
image.Mutate(x=>x.Resize(128,128));
|
|
|
|
|
image.Save(Path.Combine(Config.SiteSetup.Avatars,item.FileName));
|
|
|
|
|
image.Mutate(x=>x.Resize(64,64));
|
|
|
|
|
image.Save(Path.Combine(Config.SiteSetup.Avatars,user.UserName + ".s.png"));
|
|
|
|
|
image.Mutate(x=>x.Resize(32,32));
|
|
|
|
|
image.Save(Path.Combine(Config.SiteSetup.Avatars,user.UserName + ".xs.png"));
|
2016-12-01 17:34:29 +01:00
|
|
|
}
|
2017-02-01 19:53:10 +01:00
|
|
|
user.Avatar = $"{item.DestDir}/{item.FileName}";
|
2016-12-01 17:34:29 +01:00
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 11:14:32 +00:00
|
|
|
public static string GetFileUrl (this LiveFlow flow)
|
|
|
|
|
{
|
|
|
|
|
// no server-side backup for this stream
|
2024-02-25 18:05:10 +00:00
|
|
|
return $"{Config.UserFilesOptions.RequestPath}/{flow.Owner.UserName}/live/"+GetFileName(flow);
|
2019-06-28 00:49:20 +01:00
|
|
|
}
|
2019-08-03 11:42:52 +02:00
|
|
|
|
2019-06-28 00:49:20 +01:00
|
|
|
public static string GetFileName (this LiveFlow flow)
|
|
|
|
|
{
|
2019-01-30 11:14:32 +00:00
|
|
|
var fileInfo = new FileInfo(flow.DifferedFileName);
|
|
|
|
|
var ext = fileInfo.Extension;
|
|
|
|
|
var namelen = flow.DifferedFileName.Length - ext.Length;
|
|
|
|
|
var basename = flow.DifferedFileName.Substring(0,namelen);
|
2019-06-28 00:49:20 +01:00
|
|
|
return $"{basename}-{flow.SequenceNumber}{ext}";
|
2019-01-30 11:14:32 +00:00
|
|
|
}
|
2023-03-19 17:57:55 +00:00
|
|
|
|
2016-12-01 17:34:29 +01:00
|
|
|
}
|
2016-05-17 18:22:18 +02:00
|
|
|
}
|