Merge branch 'develop' into feature/integration-system

This commit is contained in:
Ilya Oleshko 2022-06-30 10:36:09 +03:00
commit 9d8a3c0104
35 changed files with 747 additions and 404 deletions

View File

@ -61,8 +61,8 @@ public class CustomEndpointDataSource : EndpointDataSource
void AddEndpoints(IReadOnlyDictionary<string, object> defaults = null, RouteValueDictionary policies = null)
{
var order = constraintRouteAttr != null ? r.Order : r.Order + 2;
endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText, defaults, policies), order, r.Metadata, r.DisplayName));
endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText + ".{format}", defaults, policies), order - 1, r.Metadata, r.DisplayName));
endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText, defaults, policies), order + 1, r.Metadata, r.DisplayName));
endpoints.Add(new RouteEndpoint(r.RequestDelegate, RoutePatternFactory.Parse(r.RoutePattern.RawText + ".{format}", defaults, policies), order, r.Metadata, r.DisplayName));
}
}).ToList();

View File

@ -31,7 +31,7 @@ public class TempPath
{
private readonly string _tempFolder;
public TempPath(IConfiguration configuration)
public TempPath(IHostEnvironment hostEnvironment, IConfiguration configuration)
{
var rootFolder = AppContext.BaseDirectory;
if (string.IsNullOrEmpty(rootFolder))
@ -39,7 +39,7 @@ public class TempPath
rootFolder = Assembly.GetEntryAssembly().Location;
}
_tempFolder = configuration["temp"] ?? Path.Combine("..", "Data", "temp");
_tempFolder = configuration["web:temp"] ?? CrossPlatform.PathCombine(hostEnvironment.ContentRootPath, "temp");
if (!Path.IsPathRooted(_tempFolder))
{
_tempFolder = Path.GetFullPath(Path.Combine(rootFolder, _tempFolder));
@ -56,7 +56,7 @@ public class TempPath
return _tempFolder;
}
public string GetTempFileName()
public string GetTempFileName(string ext = "")
{
FileStream f = null;
string path;
@ -66,6 +66,11 @@ public class TempPath
{
path = Path.Combine(_tempFolder, Path.GetRandomFileName());
if (!string.IsNullOrEmpty(ext))
{
path = Path.ChangeExtension(path, ext);
}
try
{
using (f = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read))

View File

@ -54,7 +54,12 @@ public class TempStream
public Stream Create()
{
return new FileStream(_tempPath.GetTempFileName(), FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose);
return Create(out _);
}
public Stream Create(out string path, string ext = "")
{
path = _tempPath.GetTempFileName(ext);
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose);
}
}

View File

@ -86,6 +86,7 @@ global using Microsoft.Extensions.Caching.Memory;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using Microsoft.Extensions.Primitives;

View File

@ -287,6 +287,8 @@ public interface IFileDao<T>
Task<Stream> GetThumbnailAsync(File<T> file, int width, int height);
Task<Stream> GetThumbnailAsync(T fileId, int width, int height);
Task<IEnumerable<FileWithShare>> GetFeedsAsync(int tenant, DateTime from, DateTime to);
Task<IEnumerable<int>> GetTenantsWithFeedsAsync(DateTime fromTime);

View File

@ -1465,6 +1465,12 @@ internal class FileDao : AbstractDao, IFileDao<int>
await _globalStore.GetStore().SaveAsync(string.Empty, GetUniqFilePath(file, thumnailName), thumbnail, thumnailName);
}
public async Task<Stream> GetThumbnailAsync(int fileId, int width, int height)
{
var file = await GetFileAsync(fileId);
return await GetThumbnailAsync(file, width, height);
}
public async Task<Stream> GetThumbnailAsync(File<int> file, int width, int height)
{
var thumnailName = GetThumnailName(width, height);

View File

@ -192,6 +192,7 @@ internal abstract class BoxDaoBase : ThirdPartyProviderDao<BoxProviderInfo>
file.ModifiedOn = boxFile.ModifiedAt.HasValue ? _tenantUtil.DateTimeFromUtc(boxFile.ModifiedAt.Value.UtcDateTime) : default;
file.NativeAccessor = boxFile;
file.Title = MakeFileTitle(boxFile);
file.ThumbnailStatus = Thumbnail.Created;
return file;
}

View File

@ -550,6 +550,12 @@ internal class BoxFileDao : BoxDaoBase, IFileDao<string>
return false;
}
public override Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
var boxFileId = MakeBoxId(_boxDaoSelector.ConvertId(fileId));
return ProviderInfo.GetThumbnailAsync(boxFileId, width, height);
}
#region chunking
private File<string> RestoreIds(File<string> file)

View File

@ -147,6 +147,12 @@ internal class BoxProviderInfo : IProviderInfo
{
return _boxProviderInfoHelper.CacheResetAsync(BoxRootId, ID, boxPath, isFile);
}
internal async Task<Stream> GetThumbnailAsync(string boxFileId, int width, int height)
{
var storage = await StorageAsync;
return await _boxProviderInfoHelper.GetThumbnailAsync(storage, boxFileId, width, height);
}
}
[Scope]
@ -332,4 +338,9 @@ public class BoxProviderInfoHelper
await _cacheNotify.PublishAsync(new BoxCacheItem { IsFile = isFile ?? false, IsFileExists = isFile.HasValue, Key = key }, CacheNotifyAction.Remove).ConfigureAwait(false);
}
}
internal async Task<Stream> GetThumbnailAsync(BoxStorage storage, string boxFileId, int width, int height)
{
return await storage.GetThumbnailAsync(boxFileId, width, height).ConfigureAwait(false);
}
}

View File

@ -270,4 +270,13 @@ internal class BoxStorage
//todo: without chunked uploader:
return Math.Min(max, MaxChunkedUploadFileSize);
}
public async Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
var boxRepresentation = new BoxRepresentationRequest();
boxRepresentation.FileId = fileId;
boxRepresentation.XRepHints = $"[jpg?dimensions=320x320]";
return await _boxClient.FilesManager.GetRepresentationContentAsync(boxRepresentation);
}
}

View File

@ -196,6 +196,7 @@ internal abstract class DropboxDaoBase : ThirdPartyProviderDao<DropboxProviderIn
file.ModifiedOn = _tenantUtil.DateTimeFromUtc(dropboxFile.ServerModified);
file.NativeAccessor = dropboxFile;
file.Title = MakeFileTitle(dropboxFile);
file.ThumbnailStatus = Thumbnail.Created;
return file;
}

View File

@ -185,6 +185,7 @@ internal class DropboxFileDao : DropboxDaoBase, IFileDao<string>
}
//Get only files
var items = await GetDropboxItemsAsync(parentId, false).ConfigureAwait(false);
var files = items.Select(item => ToFile(item.AsFile));
@ -548,6 +549,11 @@ internal class DropboxFileDao : DropboxDaoBase, IFileDao<string>
return false;
}
public override Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
return ProviderInfo.GetThumbnailsAsync(_dropboxDaoSelector.ConvertId(fileId), width, height);
}
#region chunking
private File<string> RestoreIds(File<string> file)

View File

@ -132,9 +132,15 @@ internal class DropboxProviderInfo : IProviderInfo
{
return _dropboxProviderInfoHelper.CacheResetAsync(ID, dropboxPath, isFile);
}
internal async Task<Stream> GetThumbnailsAsync(string filePath, int width, int height)
{
var storage = await StorageAsync;
return await _dropboxProviderInfoHelper.GetThumbnailsAsync(storage, filePath, width, height);
}
}
[Scope]
[Scope(Additional = typeof(DropboxStorageDisposableWrapperExtention))]
internal class DropboxStorageDisposableWrapper : IDisposable
{
public DropboxStorage Storage { get; private set; }
@ -163,7 +169,7 @@ internal class DropboxStorageDisposableWrapper : IDisposable
public async Task<DropboxStorage> InternalCreateStorageAsync(OAuth20Token token, int id)
{
var dropboxStorage = _serviceProvider.GetService<DropboxStorage>();
var dropboxStorage = _serviceProvider.GetRequiredService<DropboxStorage>();
await CheckTokenAsync(token, id).ConfigureAwait(false);
@ -311,4 +317,17 @@ public class DropboxProviderInfoHelper
await _cacheNotify.PublishAsync(new DropboxCacheItem { IsFile = isFile ?? false, IsFileExists = isFile.HasValue, Key = key }, CacheNotifyAction.Remove).ConfigureAwait(false);
}
}
internal Task<Stream> GetThumbnailsAsync(DropboxStorage storage, string filePath, int width, int height)
{
return storage.GetThumbnailsAsync(filePath, width, height);
}
}
public static class DropboxStorageDisposableWrapperExtention
{
public static void Register(DIHelper dIHelper)
{
dIHelper.TryAdd<DropboxStorage>();
}
}

View File

@ -24,8 +24,11 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ThumbnailSize = Dropbox.Api.Files.ThumbnailSize;
namespace ASC.Files.Thirdparty.Dropbox;
[Scope]
internal class DropboxStorage : IDisposable
{
public bool IsOpened { get; private set; }
@ -135,10 +138,38 @@ internal class DropboxStorage : IDisposable
public async Task<List<Metadata>> GetItemsAsync(string folderPath)
{
var data = await _dropboxClient.Files.ListFolderAsync(folderPath);
return new List<Metadata>(data.Entries);
}
public async Task<Stream> GetThumbnailsAsync(string filePath, int width, int height)
{
try
{
var path = new PathOrLink.Path(filePath);
var size = Convert(width, height);
var arg = new ThumbnailV2Arg(path, size: size);
var responce = await _dropboxClient.Files.GetThumbnailV2Async(arg);
return await responce.GetContentAsStreamAsync();
}
catch
{
return null;
}
}
private ThumbnailSize Convert(int width, int height)
{
if (width > 368)
{
return ThumbnailSize.W480h320.Instance;
}
else
{
return ThumbnailSize.W256h256.Instance;
}
}
public Task<Stream> DownloadStreamAsync(string filePath, int offset = 0)
{
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(filePath);

View File

@ -215,6 +215,7 @@ internal abstract class GoogleDriveDaoBase : ThirdPartyProviderDao<GoogleDrivePr
file.ModifiedOn = driveFile.ModifiedTime.HasValue ? _tenantUtil.DateTimeFromUtc(driveFile.ModifiedTime.Value) : default;
file.NativeAccessor = driveFile;
file.Title = MakeFileTitle(driveFile);
file.ThumbnailStatus = Thumbnail.Created;
return file;
}

View File

@ -552,6 +552,12 @@ internal class GoogleDriveFileDao : GoogleDriveDaoBase, IFileDao<string>
return false;
}
public override Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
return ProviderInfo.GetThumbnail(MakeDriveId(_googleDriveDaoSelector.ConvertId(fileId)), width, height);
}
#region chunking
private File<string> RestoreIds(File<string> file)
@ -706,5 +712,6 @@ internal class GoogleDriveFileDao : GoogleDriveDaoBase, IFileDao<string>
return Task.CompletedTask;
}
#endregion
}

View File

@ -158,6 +158,12 @@ internal class GoogleDriveProviderInfo : IProviderInfo
{
return _googleDriveProviderInfoHelper.CacheResetChildsAsync(ID, parentDriveId, childFolder);
}
internal async Task<Stream> GetThumbnail(string fileId, int width, int height)
{
var storage = await StorageAsync;
return await storage.GetThumbnail(fileId, width, height);
}
}
[Scope(Additional = typeof(GoogleDriveProviderInfoExtention))]

View File

@ -155,7 +155,6 @@ internal class GoogleDriveStorage : IDisposable
try
{
var request = _driveService.Files.Get(entryId);
request.Fields = GoogleLoginProvider.FilesFields;
return await request.ExecuteAsync();
@ -170,6 +169,21 @@ internal class GoogleDriveStorage : IDisposable
}
}
public async Task<Stream> GetThumbnail(string fileId, int width, int height)
{
try
{
var url = $"https://lh3.google.com/u/0/d/{fileId}=w{width}-h{height}-p-k-nu-iv1";
var httpClient = _driveService.HttpClient;
var response = await httpClient.GetAsync(url);
return await response.Content.ReadAsStreamAsync();
}
catch (Exception)
{
return null;
}
}
public List<DriveFile> GetEntries(string folderId, bool? folders = null)
{
var request = _driveService.Files.List();

View File

@ -77,7 +77,12 @@ internal abstract class ThirdPartyProviderDao
return Task.CompletedTask;
}
public Task<Stream> GetThumbnailAsync(File<string> file, int width, int height)
public virtual Task<Stream> GetThumbnailAsync(File<string> file, int width, int height)
{
return GetThumbnailAsync(file.Id, width, height);
}
public virtual Task<Stream> GetThumbnailAsync(string file, int width, int height)
{
return Task.FromResult<Stream>(null);
}
@ -406,7 +411,7 @@ internal abstract class ThirdPartyProviderDao<T> : ThirdPartyProviderDao, IDispo
var filtered = folders.Join(FilesDbContext.ThirdpartyIdMapping.ToAsyncEnumerable(), f => f.Id, m => m.Id, (folder, map) => new { folder, map.HashId })
.Join(FilesDbContext.TagLink.ToAsyncEnumerable(), r => r.HashId, t => t.EntryId, (result, tag) => new { result.folder, tag.TagId })
.Join(FilesDbContext.Tag.ToAsyncEnumerable(), r => r.TagId, t => t.Id, (result, tagInfo) => new {result.folder, tagInfo.Name })
.Join(FilesDbContext.Tag.ToAsyncEnumerable(), r => r.TagId, t => t.Id, (result, tagInfo) => new { result.folder, tagInfo.Name })
.Where(r => tagNames.Contains(r.Name))
.Select(r => r.folder);

View File

@ -192,6 +192,7 @@ internal abstract class OneDriveDaoBase : ThirdPartyProviderDao<OneDriveProvider
file.ModifiedOn = onedriveFile.LastModifiedDateTime.HasValue ? _tenantUtil.DateTimeFromUtc(onedriveFile.LastModifiedDateTime.Value.DateTime) : default;
file.NativeAccessor = onedriveFile;
file.Title = MakeItemTitle(onedriveFile);
file.ThumbnailStatus = Thumbnail.Created;
return file;
}

View File

@ -561,6 +561,12 @@ internal class OneDriveFileDao : OneDriveDaoBase, IFileDao<string>
return false;
}
public override Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
var oneDriveId = MakeOneDriveId(_oneDriveDaoSelector.ConvertId(fileId));
return ProviderInfo.GetThumbnailAsync(oneDriveId, width, height);
}
#region chunking
private File<string> RestoreIds(File<string> file)

View File

@ -123,6 +123,12 @@ internal class OneDriveProviderInfo : IProviderInfo
{
return _oneDriveProviderInfoHelper.CacheResetAsync(ID, onedriveId);
}
internal async Task<Stream> GetThumbnailAsync(string onedriveId, int width, int height)
{
var storage = await StorageAsync;
return await _oneDriveProviderInfoHelper.GetThumbnailAsync(storage, onedriveId, width, height);
}
}
[Scope(Additional = typeof(OneDriveProviderInfoExtention))]
@ -284,6 +290,11 @@ public class OneDriveProviderInfoHelper
_cacheItem.Remove("onedrive-" + i.Key);
}
}
internal async Task<Stream> GetThumbnailAsync(OneDriveStorage storage, string onedriveId, int width, int height)
{
return await storage.GetThumbnailAsync(onedriveId, width, height).ConfigureAwait(false);
}
}
public static class OneDriveProviderInfoExtention
{

View File

@ -354,6 +354,30 @@ internal class OneDriveStorage
var httpClient = _clientFactory.CreateClient();
using var response = await httpClient.SendAsync(request);
}
public async Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
var thumbnails = await OnedriveClient.Drive.Items[fileId].Thumbnails.Request().GetAsync();
if (thumbnails.Count > 0)
{
var url = thumbnails[0].Medium.Url;
url = url.Substring(0, url.IndexOf("?width"));
url = url + $"?width={width}&height={height}&cropmode=none";
var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Get
};
var httpClient = _clientFactory.CreateClient();
using var response = await httpClient.SendAsync(request);
var bytes = await response.Content.ReadAsByteArrayAsync();
return new MemoryStream(bytes);
}
else
{
return null;
}
}
}

View File

@ -520,5 +520,19 @@ internal class ProviderFileDao : ProviderDaoBase, IFileDao<string>
return file;
}
public override Task<Stream> GetThumbnailAsync(string fileId, int width, int height)
{
var selector = GetSelector(fileId);
var fileDao = selector.GetFileDao(fileId);
return fileDao.GetThumbnailAsync(fileId, width, height);
}
public override Task<Stream> GetThumbnailAsync(File<string> file, int width, int height)
{
var fileDao = GetFileDao(file);
return fileDao.GetThumbnailAsync(file, width, height);
}
#endregion
}

View File

@ -65,7 +65,7 @@ public class ThumbnailSettings
private string _formats;
public string Formats
{
get => _formats ?? ".pptx|.pptm|.ppt|.ppsx|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.otp|.gslides|.xlsx|.xlsm|.xls|.xltx|.xltm|.xlt|.ods|.fods|.ots|.gsheet|.csv|.docx|.docxf|.oform|.docm|.doc|.dotx|.dotm|.dot|.odt|.fodt|.ott|.gdoc|.txt|.rtf|.mht|.html|.htm|.fb2|.epub|.pdf|.djvu|.xps|.oxps|.bmp|.jpeg|.jpg|.png|.gif|.tiff|.tif|.ico";
get => _formats ?? ".pptx|.pptm|.ppt|.ppsx|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.otp|.gslides|.xlsx|.xlsm|.xls|.xltx|.xltm|.xlt|.ods|.fods|.ots|.gsheet|.csv|.docx|.docxf|.oform|.docm|.doc|.dotx|.dotm|.dot|.odt|.fodt|.ott|.gdoc|.txt|.rtf|.mht|.html|.htm|.fb2|.epub|.pdf|.djvu|.xps|.oxps";
set => _formats = value;
}

View File

@ -1009,11 +1009,11 @@ public class FileHandlerService
}
else
{
await ThumbnailFile(context, q.FirstOrDefault() ?? "");
await ThumbnailFileFromThirdparty(context, q.FirstOrDefault() ?? "");
}
}
private async Task ThumbnailFile<T>(HttpContext context, T id)
private async Task ThumbnailFile(HttpContext context, int id)
{
try
{
@ -1036,7 +1036,7 @@ public class FileHandlerService
_ = int.TryParse(sizes[1], out height);
}
var fileDao = _daoFactory.GetFileDao<T>();
var fileDao = _daoFactory.GetFileDao<int>();
var file = int.TryParse(context.Request.Query[FilesLinkUtility.Version], out var version) && version > 0
? await fileDao.GetFileAsync(id, version)
: await fileDao.GetFileAsync(id);
@ -1101,6 +1101,65 @@ public class FileHandlerService
}
}
private async Task ThumbnailFileFromThirdparty(HttpContext context, string id)
{
try
{
var defaultSize = _thumbnailSettings.Sizes.FirstOrDefault();
if (defaultSize == null)
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
var width = defaultSize.Width;
var height = defaultSize.Height;
var size = context.Request.Query["size"].ToString() ?? "";
var sizes = size.Split('x');
if (sizes.Length == 2)
{
_ = int.TryParse(sizes[0], out width);
_ = int.TryParse(sizes[1], out height);
}
context.Response.Headers.Add("Content-Disposition", ContentDispositionUtil.GetHeaderValue("." + _global.ThumbnailExtension));
context.Response.ContentType = MimeMapping.GetMimeMapping("." + _global.ThumbnailExtension);
var fileDao = _daoFactory.GetFileDao<string>();
using (var stream = await fileDao.GetThumbnailAsync(id, width, height))
{
await stream.CopyToAsync(context.Response.Body);
}
}
catch (FileNotFoundException ex)
{
_logger.ErrorForUrl(context.Request.Url(), ex);
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
await context.Response.WriteAsync(ex.Message);
return;
}
catch (Exception ex)
{
_logger.ErrorForUrl(context.Request.Url(), ex);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync(ex.Message);
return;
}
try
{
await context.Response.Body.FlushAsync();
await context.Response.CompleteAsync();
}
catch (HttpException he)
{
_logger.ErrorThumbnail(he);
}
}
private static string GetEtag<T>(File<T> file)
{
return file.Id + ":" + file.Version + ":" + file.Title.GetHashCode() + ":" + file.ContentLength;

View File

@ -30,6 +30,9 @@ internal static partial class FFmpegServiceLogger
[LoggerMessage(Level = LogLevel.Error, Message = "FFmpeg/avconv was not found in PATH or 'files.ffmpeg' setting")]
public static partial void ErrorFFmpeg(this ILogger<FFmpegService> logger);
[LoggerMessage(Level = LogLevel.Error, Message = "File {file} not found")]
public static partial void ErrorFileNotFound(this ILogger<FFmpegService> logger, string file);
[LoggerMessage(Level = LogLevel.Information, Message = "FFmpeg found in {path}")]
public static partial void InformationFFmpegFoundIn(this ILogger<FFmpegService> logger, string path);
}

View File

@ -44,11 +44,25 @@ public class FFmpegService
}
}
private readonly List<string> _convertableMedia;
private readonly List<string> _fFmpegExecutables = new List<string>() { "ffmpeg", "avconv" };
private readonly string _fFmpegPath;
private readonly string _fFmpegArgs;
private readonly string _fFmpegThumbnailsArgs;
private readonly List<string> _fFmpegFormats;
private readonly ILogger<FFmpegService> _logger;
public bool IsConvertable(string extension)
{
return MustConvertable.Contains(extension.TrimStart('.'));
}
public bool ExistFormat(string extension)
{
return _fFmpegFormats.Contains(extension);
}
public Task<Stream> Convert(Stream inputStream, string inputFormat)
{
if (inputStream == null)
@ -68,24 +82,22 @@ public class FFmpegService
{
var startInfo = PrepareFFmpeg(inputFormat);
Process process;
using (process = new Process { StartInfo = startInfo })
{
process.Start();
using var process = Process.Start(startInfo);
await StreamCopyToAsync(inputStream, process.StandardInput.BaseStream, closeDst: true);
await inputStream.CopyToAsync(process.StandardInput.BaseStream);
await ProcessLog(process.StandardError.BaseStream);
return process.StandardOutput.BaseStream;
}
}
public FFmpegService(ILogger<FFmpegService> logger, IConfiguration configuration)
{
_logger = logger;
_fFmpegPath = configuration["files:ffmpeg:value"];
_fFmpegArgs = configuration["files:ffmpeg:args"] ?? "-i - -preset ultrafast -movflags frag_keyframe+empty_moov -f {0} -";
_fFmpegThumbnailsArgs = configuration["files:ffmpeg:thumbnails:args"] ?? "-ss 3 -i \"{0}\" -vf \"thumbnail\" -frames:v 1 -vsync vfr \"{1}\" -y";
_fFmpegFormats = configuration.GetSection("files:ffmpeg:thumbnails:formats").Get<List<string>>() ?? FileUtility.ExtsVideo;
_convertableMedia = (configuration.GetSection("files:ffmpeg:exts").Get<string[]>() ?? Array.Empty<string>()).ToList();
@ -120,13 +132,6 @@ public class FFmpegService
}
}
private readonly List<string> _convertableMedia;
private readonly List<string> _fFmpegExecutables = new List<string>() { "ffmpeg", "avconv" };
private readonly string _fFmpegPath;
private readonly string _fFmpegArgs;
private readonly ILogger<FFmpegService> _logger;
private ProcessStartInfo PrepareFFmpeg(string inputFormat)
{
if (!_convertableMedia.Contains(inputFormat.TrimStart('.')))
@ -134,6 +139,15 @@ public class FFmpegService
throw new ArgumentException("input format");
}
var startInfo = PrepareCommonFFmpeg();
startInfo.Arguments = string.Format(_fFmpegArgs, "mp4");
return startInfo;
}
private ProcessStartInfo PrepareCommonFFmpeg()
{
var startInfo = new ProcessStartInfo();
if (string.IsNullOrEmpty(_fFmpegPath))
@ -143,56 +157,15 @@ public class FFmpegService
}
startInfo.FileName = _fFmpegPath;
startInfo.WorkingDirectory = Path.GetDirectoryName(_fFmpegPath);
startInfo.Arguments = string.Format(_fFmpegArgs, "mp4");
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardError = true;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Normal;
return startInfo;
}
private static Task<int> StreamCopyToAsync(Stream srcStream, Stream dstStream, bool closeSrc = false, bool closeDst = false)
{
ArgumentNullException.ThrowIfNull(srcStream);
ArgumentNullException.ThrowIfNull(dstStream);
return StreamCopyToAsyncInternal(srcStream, dstStream, closeSrc, closeDst);
}
private static async Task<int> StreamCopyToAsyncInternal(Stream srcStream, Stream dstStream, bool closeSrc, bool closeDst)
{
const int bufs = 2048 * 4;
var buffer = new byte[bufs];
int readed;
var total = 0;
while ((readed = await srcStream.ReadAsync(buffer, 0, bufs)) > 0)
{
await dstStream.WriteAsync(buffer, 0, readed);
await dstStream.FlushAsync();
total += readed;
}
if (closeSrc)
{
srcStream.Dispose();
srcStream.Close();
}
if (closeDst)
{
await dstStream.FlushAsync();
dstStream.Dispose();
dstStream.Close();
}
return total;
}
private async Task ProcessLog(Stream stream)
{
using var reader = new StreamReader(stream, Encoding.UTF8);
@ -202,4 +175,15 @@ public class FFmpegService
_logger.Information(line);
}
}
public async Task CreateThumbnail(string sourcePath, string destPath)
{
var startInfo = PrepareCommonFFmpeg();
startInfo.Arguments = string.Format(_fFmpegThumbnailsArgs, sourcePath, destPath);
using var process = Process.Start(startInfo);
await ProcessLog(process.StandardError.BaseStream);
}
}

View File

@ -61,7 +61,7 @@ public class FilesControllerThirdparty : FilesController<string>
_documentServiceHelper = documentServiceHelper;
}
[HttpGet("file/app-{fileId}", Order = int.MaxValue)]
[HttpGet("file/app-{fileId}", Order = 1)]
public async Task<FileEntryDto> GetFileInfoThirdPartyAsync(string fileId)
{
fileId = "app-" + fileId;

View File

@ -104,7 +104,7 @@ public abstract class UploadController<T> : ApiControllerBase
/// <param name="keepConvertStatus" visible="false">Keep status conversation after finishing</param>
/// <category>Uploads</category>
/// <returns></returns>
[HttpPost("{folderId}/insert", Order = int.MaxValue)]
[HttpPost("{folderId}/insert", Order = 1)]
public Task<FileDto<T>> InsertFileAsync(T folderId, [FromForm][ModelBinder(BinderType = typeof(InsertFileModelBinder))] InsertFileRequestDto inDto)
{
return _filesControllerHelper.InsertFileAsync(folderId, inDto.Stream, inDto.Title, inDto.CreateNewIfExist, inDto.KeepConvertStatus);
@ -133,7 +133,7 @@ public abstract class UploadController<T> : ApiControllerBase
/// <param name="storeOriginalFileFlag" visible="false">If True, upload documents in original formats as well</param>
/// <param name="keepConvertStatus" visible="false">Keep status conversation after finishing</param>
/// <returns>Uploaded file</returns>
[HttpPost("{folderId}/upload", Order = int.MaxValue)]
[HttpPost("{folderId}/upload", Order = 1)]
public Task<object> UploadFileAsync(T folderId, [ModelBinder(BinderType = typeof(UploadModelBinder))] UploadRequestDto inDto)
{
return _filesControllerHelper.UploadFileAsync(folderId, inDto);

View File

@ -0,0 +1,54 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Expired;
[Singletone]
public class DeleteExpiredService : BackgroundService
{
private readonly CommonChunkedUploadSessionHolder _commonChunkedUploadSessionHolder;
private readonly TimeSpan _launchFrequency;
public DeleteExpiredService(
ILogger<DeleteExpiredService> log,
SetupInfo setupInfo,
TempPath tempPath,
GlobalStore globalStore,
IConfiguration configuration)
{
_launchFrequency = TimeSpan.Parse(configuration["files:deleteExpired"] ?? "1", CultureInfo.InvariantCulture);
_commonChunkedUploadSessionHolder = new CommonChunkedUploadSessionHolder(tempPath, log, globalStore.GetStore(false), FileConstant.StorageDomainTmp, setupInfo.ChunkUploadSize);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _commonChunkedUploadSessionHolder.DeleteExpiredAsync();
await Task.Delay(_launchFrequency, stoppingToken);
}
}
}

View File

@ -37,8 +37,8 @@ global using ASC.Common.Caching;
global using ASC.Common.DependencyInjection;
global using ASC.Common.Log;
global using ASC.Common.Mapping;
global using ASC.Common.Utils;
global using ASC.Core;
global using ASC.Core.ChunkedUploader;
global using ASC.Core.Common;
global using ASC.Core.Common.EF;
global using ASC.Core.Common.Hosting;
@ -60,16 +60,16 @@ global using ASC.Files.Core.IntegrationEvents.Events;
global using ASC.Files.Core.Log;
global using ASC.Files.Core.Resources;
global using ASC.Files.Core.Security;
global using ASC.Files.Expired;
global using ASC.Files.Service.Log;
global using ASC.Files.ThumbnailBuilder;
global using ASC.Thumbnail.IntegrationEvents.EventHandling;
global using ASC.Web.Core;
global using ASC.Web.Core.Files;
global using ASC.Web.Core.Users;
global using ASC.Web.Files.Classes;
global using ASC.Web.Files.Core;
global using ASC.Web.Files.Core.Search;
global using ASC.Web.Files.Services.DocumentService;
global using ASC.Web.Files.Services.FFmpegService;
global using ASC.Web.Files.Utils;
global using ASC.Web.Studio.Core;
@ -80,6 +80,5 @@ global using Microsoft.Extensions.Hosting.WindowsServices;
global using Microsoft.Extensions.Logging;
global using SixLabors.ImageSharp;
global using SixLabors.ImageSharp.Formats.Png;
global using static ASC.Web.Core.Files.DocumentService;

View File

@ -82,6 +82,9 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
services.AddHostedService<Launcher>();
diHelper.TryAdd<Launcher>();
services.AddHostedService<DeleteExpiredService>();
diHelper.TryAdd<DeleteExpiredService>();
diHelper.TryAdd<AuthManager>();
diHelper.TryAdd<BaseCommonLinkUtility>();
diHelper.TryAdd<FeedAggregateDataProvider>();

View File

@ -81,6 +81,13 @@ public class Builder<T>
private readonly IHttpClientFactory _clientFactory;
private readonly ThumbnailSettings _thumbnailSettings;
private readonly SocketManager _socketManager;
private readonly FFmpegService _fFmpegService;
private readonly TempPath _tempPath;
private readonly List<string> _imageFormatsCanBeCrop = new List<string>
{
".bmp", ".gif", ".jpeg", ".jpg", ".pbm", ".png", ".tiff", ".tga", ".webp",
};
public Builder(
ThumbnailSettings settings,
@ -92,6 +99,8 @@ public class Builder<T>
PathProvider pathProvider,
ILoggerProvider log,
IHttpClientFactory clientFactory,
FFmpegService fFmpegService,
TempPath tempPath,
SocketManager socketManager,
ThumbnailSettings thumbnailSettings)
{
@ -104,6 +113,8 @@ public class Builder<T>
_pathProvider = pathProvider;
_logger = log.CreateLogger("ASC.Files.ThumbnailBuilder");
_clientFactory = clientFactory;
_fFmpegService = fFmpegService;
_tempPath = tempPath;
_socketManager = socketManager;
_thumbnailSettings = thumbnailSettings;
}
@ -158,7 +169,7 @@ public class Builder<T>
var ext = FileUtility.GetFileExtension(file.Title);
if (!_config.FormatsArray.Contains(ext) || file.Encrypted || file.RootFolderType == FolderType.TRASH || file.ContentLength > _config.AvailableFileSize)
if (!CanCreateThumbnail(ext) || file.Encrypted || file.RootFolderType == FolderType.TRASH || file.ContentLength > _config.AvailableFileSize)
{
file.ThumbnailStatus = ASC.Files.Core.Thumbnail.NotRequired;
foreach (var size in _thumbnailSettings.Sizes)
@ -169,7 +180,14 @@ public class Builder<T>
return;
}
if (IsImage(file))
if (IsVideo(ext))
{
await MakeThumbnailFromVideo(fileDao, file);
}
else
{
if (IsImage(ext))
{
await CropImage(fileDao, file);
}
@ -177,6 +195,7 @@ public class Builder<T>
{
await MakeThumbnail(fileDao, file);
}
}
var newFile = await fileDao.GetFileStableAsync(file.Id);
@ -196,6 +215,29 @@ public class Builder<T>
}
}
private async Task MakeThumbnailFromVideo(IFileDao<T> fileDao, File<T> file)
{
var streamFile = await fileDao.GetFileStreamAsync(file);
var thumbPath = _tempPath.GetTempFileName("jpg");
var tempFilePath = _tempPath.GetTempFileName(Path.GetExtension(file.Title));
using (var fileStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite, System.IO.FileShare.Read))
{
await streamFile.CopyToAsync(fileStream);
}
await _fFmpegService.CreateThumbnail(tempFilePath, thumbPath);
using (var streamThumb = new FileStream(thumbPath, FileMode.Open, FileAccess.ReadWrite, System.IO.FileShare.Read))
{
await Crop(fileDao, file, streamThumb);
}
File.Delete(thumbPath);
File.Delete(tempFilePath);
}
private async Task MakeThumbnail(IFileDao<T> fileDao, File<T> file)
{
foreach (var w in _config.Sizes)
@ -314,11 +356,19 @@ public class Builder<T>
_logger.DebugMakeThumbnail4(file.Id.ToString());
}
private bool IsImage(File<T> file)
private bool CanCreateThumbnail(string extention)
{
var extension = FileUtility.GetFileExtension(file.Title);
return _config.FormatsArray.Contains(extention) || IsVideo(extention) || IsImage(extention);
}
return FileUtility.ExtsImage.Contains(extension);
private bool IsImage(string extention)
{
return _imageFormatsCanBeCrop.Contains(extention);
}
private bool IsVideo(string extention)
{
return _fFmpegService.ExistFormat(extention);
}
private async Task CropImage(IFileDao<T> fileDao, File<T> file)
@ -359,8 +409,7 @@ public class Builder<T>
private async ValueTask CropAsync(Image sourceImg, IFileDao<T> fileDao, File<T> file, int width, int height)
{
var targetSize = new Size(Math.Min(sourceImg.Width, width), Math.Min(sourceImg.Height, height));
using var targetImg = GetImageThumbnail(sourceImg, targetSize, width, height);
using var targetImg = GetImageThumbnail(sourceImg, width);
using var targetStream = new MemoryStream();
switch (_global.ThumbnailExtension)
{
@ -392,7 +441,7 @@ public class Builder<T>
await fileDao.SaveThumbnailAsync(file, targetStream, width, height);
}
private Image GetImageThumbnail(Image sourceBitmap, Size targetSize, int thumbnaillWidth, int thumbnaillHeight)
private Image GetImageThumbnail(Image sourceBitmap, int thumbnaillWidth)
{
return sourceBitmap.Clone(x => x.BackgroundColor(Color.White).Resize(thumbnaillWidth, 0));
}

View File

@ -144,7 +144,7 @@ public class AuthenticationController : ControllerBase
}
[AllowNotPayment]
[HttpPost("{code}", Order = int.MaxValue)]
[HttpPost("{code}", Order = 1)]
public AuthenticationTokenDto AuthenticateMeFromBodyWithCode(AuthRequestsDto inDto)
{
var tenant = _tenantManager.GetCurrentTenant().Id;