DocSpace-buildtools/common/ASC.Data.Storage/GoogleCloud/GoogleCloudStorage.cs

853 lines
31 KiB
C#
Raw Normal View History

2019-06-04 14:43:20 +00:00
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
2019-08-15 12:04:42 +00:00
using System.Globalization;
using System.IO;
2019-06-04 14:43:20 +00:00
using System.Linq;
2019-08-15 12:04:42 +00:00
using System.Net.Http;
2022-06-29 15:40:35 +00:00
using System.Net.Http.Headers;
2022-01-10 09:24:21 +00:00
using System.Security.Cryptography;
2020-01-21 12:44:05 +00:00
using System.Text;
2019-08-15 12:04:42 +00:00
using System.Threading;
2021-05-21 13:26:42 +00:00
using System.Threading.Tasks;
2019-08-15 12:04:42 +00:00
using System.Web;
2020-01-21 12:44:05 +00:00
2020-12-04 10:52:07 +00:00
using ASC.Common;
2019-10-17 15:55:35 +00:00
using ASC.Common.Logging;
2019-09-20 14:04:06 +00:00
using ASC.Core;
2019-06-04 14:43:20 +00:00
using ASC.Data.Storage.Configuration;
2019-09-23 12:20:08 +00:00
using ASC.Security.Cryptography;
2020-01-21 12:44:05 +00:00
2019-06-04 14:43:20 +00:00
using Google.Apis.Auth.OAuth2;
2019-08-15 12:04:42 +00:00
using Google.Cloud.Storage.V1;
2020-01-21 12:44:05 +00:00
2019-10-18 08:48:27 +00:00
using Microsoft.AspNetCore.Http;
2019-10-17 15:55:35 +00:00
using Microsoft.Extensions.Options;
2020-01-21 12:44:05 +00:00
2020-06-25 11:38:34 +00:00
using static Google.Cloud.Storage.V1.UrlSigner;
2019-06-04 14:43:20 +00:00
using MimeMapping = ASC.Common.Web.MimeMapping;
namespace ASC.Data.Storage.GoogleCloud
{
2020-12-04 10:52:07 +00:00
[Scope]
2019-06-04 14:43:20 +00:00
public class GoogleCloudStorage : BaseStorage
{
2019-08-15 13:16:39 +00:00
private string _subDir = string.Empty;
2019-09-20 14:04:06 +00:00
private Dictionary<string, PredefinedObjectAcl> _domainsAcl;
private PredefinedObjectAcl _moduleAcl;
2019-06-04 14:43:20 +00:00
private string _bucket = "";
2020-01-21 12:44:05 +00:00
private string _json = "";
2019-06-04 14:43:20 +00:00
private Uri _bucketRoot;
private Uri _bucketSSlRoot;
2019-08-15 12:04:42 +00:00
2019-06-04 14:43:20 +00:00
private bool _lowerCasing = true;
2021-11-24 19:34:39 +00:00
2019-10-17 15:55:35 +00:00
public GoogleCloudStorage(
2021-05-17 11:35:00 +00:00
TempStream tempStream,
2019-10-17 15:55:35 +00:00
TenantManager tenantManager,
PathUtils pathUtils,
EmailValidationKeyProvider emailValidationKeyProvider,
2019-10-18 08:48:27 +00:00
IHttpContextAccessor httpContextAccessor,
2022-01-13 11:19:39 +00:00
IOptionsMonitor<ILog> options,
IHttpClientFactory clientFactory) : base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, options, clientFactory)
2019-06-04 14:43:20 +00:00
{
}
2019-09-20 14:04:06 +00:00
public override IDataStore Configure(string tenant, Handler handlerConfig, Module moduleConfig, IDictionary<string, string> props)
2019-06-04 14:43:20 +00:00
{
_tenant = tenant;
2019-09-20 14:04:06 +00:00
if (moduleConfig != null)
{
_modulename = moduleConfig.Name;
_dataList = new DataList(moduleConfig);
2019-06-04 14:43:20 +00:00
2019-09-20 14:04:06 +00:00
_domainsExpires = moduleConfig.Domain.Where(x => x.Expires != TimeSpan.Zero).ToDictionary(x => x.Name, y => y.Expires);
2019-06-04 14:43:20 +00:00
2019-09-20 14:04:06 +00:00
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
2019-06-04 14:43:20 +00:00
2019-09-20 14:04:06 +00:00
_domainsAcl = moduleConfig.Domain.ToDictionary(x => x.Name, y => GetGoogleCloudAcl(y.Acl));
_moduleAcl = GetGoogleCloudAcl(moduleConfig.Acl);
}
else
{
_modulename = string.Empty;
_dataList = null;
2019-06-04 14:43:20 +00:00
2019-09-20 14:04:06 +00:00
_domainsExpires = new Dictionary<string, TimeSpan> { { string.Empty, TimeSpan.Zero } };
_domainsAcl = new Dictionary<string, PredefinedObjectAcl>();
_moduleAcl = PredefinedObjectAcl.PublicRead;
}
2019-06-04 14:43:20 +00:00
2019-08-15 12:04:42 +00:00
_bucket = props["bucket"];
2019-06-04 14:43:20 +00:00
_bucketRoot = props.ContainsKey("cname") && Uri.IsWellFormedUriString(props["cname"], UriKind.Absolute)
? new Uri(props["cname"], UriKind.Absolute)
2022-01-14 13:12:37 +00:00
: new Uri("https://storage.googleapis.com/" + _bucket + "/", UriKind.Absolute);
2019-06-04 14:43:20 +00:00
_bucketSSlRoot = props.ContainsKey("cnamessl") &&
Uri.IsWellFormedUriString(props["cnamessl"], UriKind.Absolute)
? new Uri(props["cnamessl"], UriKind.Absolute)
2022-01-14 13:12:37 +00:00
: new Uri("https://storage.googleapis.com/" + _bucket + "/", UriKind.Absolute);
2019-08-15 12:04:42 +00:00
2022-01-18 14:43:46 +00:00
if (props.TryGetValue("lower", out var value))
2019-06-04 14:43:20 +00:00
{
2022-01-18 14:43:46 +00:00
bool.TryParse(value, out _lowerCasing);
2019-06-04 14:43:20 +00:00
}
2020-01-21 12:44:05 +00:00
_json = props["json"];
2019-06-04 14:43:20 +00:00
2022-01-18 14:43:46 +00:00
props.TryGetValue("subdir", out _subDir);
2019-06-04 14:43:20 +00:00
return this;
}
private StorageClient GetStorage()
{
2020-01-21 12:44:05 +00:00
var credential = GoogleCredential.FromJson(_json);
2019-06-04 14:43:20 +00:00
return StorageClient.Create(credential);
}
2022-01-25 09:29:11 +00:00
private Task<StorageClient> GetStorageAsync()
{
var credential = GoogleCredential.FromJson(_json);
return StorageClient.CreateAsync(credential);
}
2019-06-04 14:43:20 +00:00
private string MakePath(string domain, string path)
{
string result;
path = path.TrimStart('\\', '/').TrimEnd('/').Replace('\\', '/');
2019-08-15 13:16:39 +00:00
if (!string.IsNullOrEmpty(_subDir))
2019-06-04 14:43:20 +00:00
{
if (_subDir.Length == 1 && (_subDir[0] == '/' || _subDir[0] == '\\'))
result = path;
else
2022-01-14 13:12:37 +00:00
result = $"{_subDir}/{path}"; // Ignory all, if _subDir is not null
2019-06-04 14:43:20 +00:00
}
else//Key combined from module+domain+filename
2022-01-14 13:12:37 +00:00
result = $"{_tenant}/{_modulename}/{domain}/{path}";
2019-06-04 14:43:20 +00:00
result = result.Replace("//", "/").TrimStart('/');
if (_lowerCasing)
{
result = result.ToLowerInvariant();
}
return result;
}
public static long DateToUnixTimestamp(DateTime date)
{
var ts = date - new DateTime(1970, 1, 1, 0, 0, 0);
return (long)ts.TotalSeconds;
}
2022-02-16 12:57:37 +00:00
public override Task<Uri> GetInternalUriAsync(string domain, string path, TimeSpan expire, IEnumerable<string> headers)
2019-06-04 14:43:20 +00:00
{
if (expire == TimeSpan.Zero || expire == TimeSpan.MinValue || expire == TimeSpan.MaxValue)
{
expire = GetExpire(domain);
}
if (expire == TimeSpan.Zero || expire == TimeSpan.MinValue || expire == TimeSpan.MaxValue)
{
2022-02-16 12:57:37 +00:00
return Task.FromResult(GetUriShared(domain, path));
2019-06-04 14:43:20 +00:00
}
2022-02-16 12:57:37 +00:00
return InternalGetInternalUriAsync(domain, path, expire);
}
private async Task<Uri> InternalGetInternalUriAsync(string domain, string path, TimeSpan expire)
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2020-01-21 12:44:05 +00:00
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(_json ?? ""));
var preSignedURL = await UrlSigner.FromServiceAccountData(stream).SignAsync(_bucket, MakePath(domain, path), expire, HttpMethod.Get);
2019-06-04 14:43:20 +00:00
return MakeUri(preSignedURL);
}
public Uri GetUriShared(string domain, string path)
{
2019-10-18 08:48:27 +00:00
return new Uri(SecureHelper.IsSecure(HttpContextAccessor.HttpContext, Options) ? _bucketSSlRoot : _bucketRoot, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
}
private Uri MakeUri(string preSignedURL)
{
var uri = new Uri(preSignedURL);
var signedPart = uri.PathAndQuery.TrimStart('/');
return new Uri(uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) ? _bucketSSlRoot : _bucketRoot, signedPart);
}
2022-01-25 09:29:11 +00:00
public override Task<System.IO.Stream> GetReadStreamAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return GetReadStreamAsync(domain, path, 0);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override async Task<System.IO.Stream> GetReadStreamAsync(string domain, string path, int offset)
2021-05-21 13:26:42 +00:00
{
var tempStream = TempStream.Create();
var storage = GetStorage();
await storage.DownloadObjectAsync(_bucket, MakePath(domain, path), tempStream);
if (offset > 0)
tempStream.Seek(offset, SeekOrigin.Begin);
tempStream.Position = 0;
return tempStream;
}
2022-01-25 09:29:11 +00:00
public override Task<Uri> SaveAsync(string domain, string path, System.IO.Stream stream)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return SaveAsync(domain, path, stream, string.Empty, string.Empty);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override Task<Uri> SaveAsync(string domain, string path, System.IO.Stream stream, Configuration.ACL acl)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return SaveAsync(domain, path, stream, null, null, acl);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
protected override Task<Uri> SaveWithAutoAttachmentAsync(string domain, string path, System.IO.Stream stream, string attachmentFileName)
2019-06-04 14:43:20 +00:00
{
2022-01-14 13:12:37 +00:00
var contentDisposition = $"attachment; filename={HttpUtility.UrlPathEncode(attachmentFileName)};";
2020-11-17 10:00:01 +00:00
if (attachmentFileName.Any(c => c >= 0 && c <= 127))
2019-06-04 14:43:20 +00:00
{
2022-01-14 13:12:37 +00:00
contentDisposition = $"attachment; filename*=utf-8''{HttpUtility.UrlPathEncode(attachmentFileName)};";
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
return SaveAsync(domain, path, stream, null, contentDisposition);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override Task<Uri> SaveAsync(string domain, string path, System.IO.Stream stream, string contentType, string contentDisposition)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return SaveAsync(domain, path, stream, contentType, contentDisposition, ACL.Auto);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override Task<Uri> SaveAsync(string domain, string path, System.IO.Stream stream, string contentEncoding, int cacheDays)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return SaveAsync(domain, path, stream, string.Empty, string.Empty, ACL.Auto, contentEncoding, cacheDays);
2019-06-04 14:43:20 +00:00
}
2022-06-28 16:39:10 +00:00
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
2022-01-25 09:29:11 +00:00
public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
2019-06-04 14:43:20 +00:00
string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5)
{
2021-05-17 11:35:00 +00:00
var buffered = TempStream.GetBuffered(stream);
2019-06-04 14:43:20 +00:00
2022-06-28 16:39:10 +00:00
if (EnableQuotaCheck(domain))
2019-06-04 14:43:20 +00:00
{
QuotaController.QuotaUsedCheck(buffered.Length);
}
var mime = string.IsNullOrEmpty(contentType)
? MimeMapping.GetMimeMapping(Path.GetFileName(path))
: contentType;
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2019-08-15 13:03:57 +00:00
var uploadObjectOptions = new UploadObjectOptions
2019-06-04 14:43:20 +00:00
{
PredefinedAcl = acl == ACL.Auto ? GetDomainACL(domain) : GetGoogleCloudAcl(acl)
};
buffered.Position = 0;
2022-01-25 09:29:11 +00:00
var uploaded = await storage.UploadObjectAsync(_bucket, MakePath(domain, path), mime, buffered, uploadObjectOptions);
2019-06-04 14:43:20 +00:00
uploaded.ContentEncoding = contentEncoding;
2019-08-15 13:16:39 +00:00
uploaded.CacheControl = string.Format("public, maxage={0}", (int)TimeSpan.FromDays(cacheDays).TotalSeconds);
2019-06-04 14:43:20 +00:00
if (uploaded.Metadata == null)
2019-08-15 13:16:39 +00:00
uploaded.Metadata = new Dictionary<string, string>();
2019-06-04 14:43:20 +00:00
uploaded.Metadata["Expires"] = DateTime.UtcNow.Add(TimeSpan.FromDays(cacheDays)).ToString("R");
if (!string.IsNullOrEmpty(contentDisposition))
{
uploaded.ContentDisposition = contentDisposition;
}
else if (mime == "application/octet-stream")
{
uploaded.ContentDisposition = "attachment";
}
storage.UpdateObject(uploaded);
// InvalidateCloudFront(MakePath(domain, path));
QuotaUsedAdd(domain, buffered.Length);
2022-01-25 09:29:11 +00:00
return await GetUriAsync(domain, path);
2019-06-04 14:43:20 +00:00
}
private void InvalidateCloudFront(params string[] paths)
{
throw new NotImplementedException();
}
private PredefinedObjectAcl GetGoogleCloudAcl(ACL acl)
{
2020-10-16 13:21:59 +00:00
return PredefinedObjectAcl.PublicRead;
//return acl switch
//{
// ACL.Read => PredefinedObjectAcl.PublicRead,
// _ => PredefinedObjectAcl.PublicRead,
//};
2019-06-04 14:43:20 +00:00
}
private PredefinedObjectAcl GetDomainACL(string domain)
{
if (GetExpire(domain) != TimeSpan.Zero)
{
return PredefinedObjectAcl.Private;
}
2022-01-18 14:43:46 +00:00
if (_domainsAcl.TryGetValue(domain, out var value))
2019-06-04 14:43:20 +00:00
{
2022-01-18 14:43:46 +00:00
return value;
2019-06-04 14:43:20 +00:00
}
return _moduleAcl;
}
2022-01-25 09:29:11 +00:00
public override async Task DeleteAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var key = MakePath(domain, path);
2022-01-25 09:29:11 +00:00
var size = await GetFileSizeAsync(domain, path);
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
await storage.DeleteObjectAsync(_bucket, key);
2019-06-04 14:43:20 +00:00
QuotaUsedDelete(domain, size);
}
2022-01-25 09:29:11 +00:00
public override async Task DeleteFilesAsync(string domain, string folderPath, string pattern, bool recursive)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
IAsyncEnumerable<Google.Apis.Storage.v1.Data.Object> objToDel;
2019-06-04 14:43:20 +00:00
if (recursive)
objToDel = storage
2022-01-25 09:29:11 +00:00
.ListObjectsAsync(_bucket, MakePath(domain, folderPath))
2019-06-04 14:43:20 +00:00
.Where(x => Wildcard.IsMatch(pattern, Path.GetFileName(x.Name)));
else
2022-01-25 09:29:11 +00:00
objToDel = AsyncEnumerable.Empty<Google.Apis.Storage.v1.Data.Object>();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
await foreach (var obj in objToDel)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
await storage.DeleteObjectAsync(_bucket, obj.Name);
2019-06-04 14:43:20 +00:00
QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
2022-01-25 09:29:11 +00:00
}
2019-06-04 14:43:20 +00:00
2022-02-16 12:57:37 +00:00
public override Task DeleteFilesAsync(string domain, List<string> paths)
2019-06-04 14:43:20 +00:00
{
2022-02-16 12:57:37 +00:00
if (paths.Count == 0) return Task.CompletedTask;
2019-06-04 14:43:20 +00:00
2022-02-16 12:57:37 +00:00
return InternalDeleteFilesAsync(domain, paths);
}
private async Task InternalDeleteFilesAsync(string domain, List<string> paths)
{
2019-06-04 14:43:20 +00:00
var keysToDel = new List<string>();
long quotaUsed = 0;
foreach (var path in paths)
{
try
{
var key = MakePath(domain, path);
if (QuotaController != null)
{
2022-01-25 09:29:11 +00:00
quotaUsed += await GetFileSizeAsync(domain, path);
2019-06-04 14:43:20 +00:00
}
keysToDel.Add(key);
}
catch (FileNotFoundException)
{
}
}
2022-01-18 13:54:24 +00:00
if (keysToDel.Count == 0) return;
2019-06-04 14:43:20 +00:00
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
foreach (var e in keysToDel)
{
await storage.DeleteObjectAsync(_bucket, e);
}
2019-06-04 14:43:20 +00:00
if (quotaUsed > 0)
{
QuotaUsedDelete(domain, quotaUsed);
}
}
2022-01-25 09:29:11 +00:00
public override async Task DeleteFilesAsync(string domain, string folderPath, DateTime fromDate, DateTime toDate)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
var objToDel = GetObjectsAsync(domain, folderPath, true)
2019-06-04 14:43:20 +00:00
.Where(x => x.Updated >= fromDate && x.Updated <= toDate);
2022-01-25 09:29:11 +00:00
await foreach (var obj in objToDel)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
await storage.DeleteObjectAsync(_bucket, obj.Name);
2019-06-04 14:43:20 +00:00
QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
2022-01-25 09:29:11 +00:00
public override async Task MoveDirectoryAsync(string srcdomain, string srcdir, string newdomain, string newdir)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var srckey = MakePath(srcdomain, srcdir);
var dstkey = MakePath(newdomain, newdir);
var objects = storage.ListObjects(_bucket, srckey);
foreach (var obj in objects)
{
2022-01-25 09:29:11 +00:00
await storage.CopyObjectAsync(_bucket, srckey, _bucket, dstkey, new CopyObjectOptions
2019-06-04 14:43:20 +00:00
{
DestinationPredefinedAcl = GetDomainACL(newdomain)
});
2022-01-25 09:29:11 +00:00
await storage.DeleteObjectAsync(_bucket, srckey);
2019-06-04 14:43:20 +00:00
}
}
2022-01-25 09:29:11 +00:00
public override async Task<Uri> MoveAsync(string srcdomain, string srcpath, string newdomain, string newpath, bool quotaCheckFileSize = true)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var srcKey = MakePath(srcdomain, srcpath);
var dstKey = MakePath(newdomain, newpath);
2022-01-25 09:29:11 +00:00
var size = await GetFileSizeAsync(srcdomain, srcpath);
2019-06-04 14:43:20 +00:00
storage.CopyObject(_bucket, srcKey, _bucket, dstKey, new CopyObjectOptions
{
DestinationPredefinedAcl = GetDomainACL(newdomain)
});
2022-01-25 09:29:11 +00:00
await DeleteAsync(srcdomain, srcpath);
2019-06-04 14:43:20 +00:00
QuotaUsedDelete(srcdomain, size);
2021-05-21 13:26:42 +00:00
QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
return await GetUriAsync(newdomain, newpath);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override Task<Uri> SaveTempAsync(string domain, out string assignedPath, System.IO.Stream stream)
2019-06-04 14:43:20 +00:00
{
assignedPath = Guid.NewGuid().ToString();
2022-01-25 09:29:11 +00:00
return SaveAsync(domain, assignedPath, stream);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override IAsyncEnumerable<string> ListDirectoriesRelativeAsync(string domain, string path, bool recursive)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return GetObjectsAsync(domain, path, recursive)
.Select(x => x.Name.Substring(MakePath(domain, path + "/").Length));
2019-06-04 14:43:20 +00:00
}
private IEnumerable<Google.Apis.Storage.v1.Data.Object> GetObjects(string domain, string path, bool recursive)
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var items = storage.ListObjects(_bucket, MakePath(domain, path));
if (recursive) return items;
return items.Where(x => x.Name.IndexOf('/', MakePath(domain, path + "/").Length) == -1);
}
2022-01-25 09:29:11 +00:00
private IAsyncEnumerable<Google.Apis.Storage.v1.Data.Object> GetObjectsAsync(string domain, string path, bool recursive)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
var items = storage.ListObjectsAsync(_bucket, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
if (recursive) return items;
return items.Where(x => x.Name.IndexOf('/', MakePath(domain, path + "/").Length) == -1);
}
public override IAsyncEnumerable<string> ListFilesRelativeAsync(string domain, string path, string pattern, bool recursive)
{
return GetObjectsAsync(domain, path, recursive).Where(x => Wildcard.IsMatch(pattern, Path.GetFileName(x.Name)))
.Select(x => x.Name.Substring(MakePath(domain, path + "/").Length).TrimStart('/'));
2021-05-21 13:26:42 +00:00
}
public override async Task<bool> IsFileAsync(string domain, string path)
{
var storage = GetStorage();
var objects = await storage.ListObjectsAsync(_bucket, MakePath(domain, path)).ReadPageAsync(1);
2022-01-18 13:54:24 +00:00
return objects.Any();
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override Task<bool> IsDirectoryAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return IsFileAsync(domain, path);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override async Task DeleteDirectoryAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var objToDel = storage
2022-01-25 09:29:11 +00:00
.ListObjectsAsync(_bucket, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
await foreach (var obj in objToDel)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
await storage.DeleteObjectAsync(_bucket, obj.Name);
2019-06-04 14:43:20 +00:00
QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
2022-01-25 09:29:11 +00:00
public override async Task<long> GetFileSizeAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
var obj = await storage.GetObjectAsync(_bucket, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
if (obj.Size.HasValue)
return Convert.ToInt64(obj.Size.Value);
return 0;
}
2022-01-25 09:29:11 +00:00
public override async Task<long> GetDirectorySizeAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var objToDel = storage
2022-01-25 09:29:11 +00:00
.ListObjectsAsync(_bucket, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
long result = 0;
2022-01-25 09:29:11 +00:00
await foreach (var obj in objToDel)
2019-06-04 14:43:20 +00:00
{
if (obj.Size.HasValue)
result += Convert.ToInt64(obj.Size.Value);
}
return result;
}
2022-01-25 09:29:11 +00:00
public override async Task<long> ResetQuotaAsync(string domain)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var objects = storage
2022-01-25 09:29:11 +00:00
.ListObjectsAsync(_bucket, MakePath(domain, string.Empty));
2019-06-04 14:43:20 +00:00
if (QuotaController != null)
{
long size = 0;
2022-01-25 09:29:11 +00:00
await foreach (var obj in objects)
2019-06-04 14:43:20 +00:00
{
if (obj.Size.HasValue)
size += Convert.ToInt64(obj.Size.Value);
}
QuotaController.QuotaUsedSet(_modulename, domain, _dataList.GetData(domain), size);
return size;
}
return 0;
}
2022-01-25 09:29:11 +00:00
public override async Task<long> GetUsedQuotaAsync(string domain)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var objects = storage
2022-01-25 09:29:11 +00:00
.ListObjectsAsync(_bucket, MakePath(domain, string.Empty));
2019-06-04 14:43:20 +00:00
long result = 0;
2022-01-25 09:29:11 +00:00
await foreach (var obj in objects)
2019-06-04 14:43:20 +00:00
{
if (obj.Size.HasValue)
result += Convert.ToInt64(obj.Size.Value);
}
return result;
}
2022-01-25 09:29:11 +00:00
public override async Task<Uri> CopyAsync(string srcdomain, string srcpath, string newdomain, string newpath)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
var size = await GetFileSizeAsync(srcdomain, srcpath);
2019-06-04 14:43:20 +00:00
2019-08-15 13:35:18 +00:00
var options = new CopyObjectOptions
{
DestinationPredefinedAcl = GetDomainACL(newdomain)
};
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
await storage.CopyObjectAsync(_bucket, MakePath(srcdomain, srcpath), _bucket, MakePath(newdomain, newpath), options);
2019-06-04 14:43:20 +00:00
QuotaUsedAdd(newdomain, size);
2022-01-25 09:29:11 +00:00
return await GetUriAsync(newdomain, newpath);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override async Task CopyDirectoryAsync(string srcdomain, string srcdir, string newdomain, string newdir)
2019-06-04 14:43:20 +00:00
{
var srckey = MakePath(srcdomain, srcdir);
var dstkey = MakePath(newdomain, newdir);
//List files from src
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
var objects = storage.ListObjectsAsync(_bucket, srckey);
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
await foreach (var obj in objects)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
await storage.CopyObjectAsync(_bucket, srckey, _bucket, dstkey, new CopyObjectOptions
2019-06-04 14:43:20 +00:00
{
DestinationPredefinedAcl = GetDomainACL(newdomain)
});
QuotaUsedAdd(newdomain, Convert.ToInt64(obj.Size));
}
}
2022-01-25 09:29:11 +00:00
public override async Task<string> SavePrivateAsync(string domain, string path, System.IO.Stream stream, DateTime expires)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2021-05-17 11:35:00 +00:00
var buffered = TempStream.GetBuffered(stream);
2019-06-04 14:43:20 +00:00
2019-08-15 13:03:57 +00:00
var uploadObjectOptions = new UploadObjectOptions
2019-06-04 14:43:20 +00:00
{
PredefinedAcl = PredefinedObjectAcl.BucketOwnerFullControl
};
buffered.Position = 0;
2022-01-25 09:29:11 +00:00
var uploaded = await storage.UploadObjectAsync(_bucket, MakePath(domain, path), "application/octet-stream", buffered, uploadObjectOptions);
2019-06-04 14:43:20 +00:00
2019-08-15 13:16:39 +00:00
uploaded.CacheControl = string.Format("public, maxage={0}", (int)TimeSpan.FromDays(5).TotalSeconds);
2019-06-04 14:43:20 +00:00
uploaded.ContentDisposition = "attachment";
if (uploaded.Metadata == null)
2019-08-15 13:16:39 +00:00
uploaded.Metadata = new Dictionary<string, string>();
2019-06-04 14:43:20 +00:00
uploaded.Metadata["Expires"] = DateTime.UtcNow.Add(TimeSpan.FromDays(5)).ToString("R");
uploaded.Metadata.Add("private-expire", expires.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture));
2022-01-25 09:29:11 +00:00
await storage.UpdateObjectAsync(uploaded);
2019-06-04 14:43:20 +00:00
2020-01-21 12:44:05 +00:00
using var mStream = new MemoryStream(Encoding.UTF8.GetBytes(_json ?? ""));
2022-01-25 09:29:11 +00:00
var preSignedURL = await FromServiceAccountData(mStream).SignAsync(RequestTemplate.FromBucket(_bucket).WithObjectName(MakePath(domain, path)), UrlSigner.Options.FromExpiration(expires));
2019-06-04 14:43:20 +00:00
//TODO: CNAME!
return preSignedURL;
}
2022-01-25 09:29:11 +00:00
public override async Task DeleteExpiredAsync(string domain, string path, TimeSpan oldThreshold)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
var objects = storage.ListObjectsAsync(_bucket, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
2022-01-25 09:29:11 +00:00
await foreach (var obj in objects)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
var objInfo = await storage.GetObjectAsync(_bucket, MakePath(domain, path), null);
2019-06-04 14:43:20 +00:00
var privateExpireKey = objInfo.Metadata["private-expire"];
if (string.IsNullOrEmpty(privateExpireKey)) continue;
2019-08-15 13:05:50 +00:00
if (!long.TryParse(privateExpireKey, out var fileTime)) continue;
2019-06-04 14:43:20 +00:00
if (DateTime.UtcNow <= DateTime.FromFileTimeUtc(fileTime)) continue;
2022-01-25 09:29:11 +00:00
await storage.DeleteObjectAsync(_bucket, MakePath(domain, path));
2019-06-04 14:43:20 +00:00
}
}
#region chunking
2022-01-25 09:29:11 +00:00
public override async Task<string> InitiateChunkedUploadAsync(string domain, string path)
2019-06-04 14:43:20 +00:00
{
2019-08-16 08:44:03 +00:00
using var storage = GetStorage();
2019-06-04 14:43:20 +00:00
var tempUploader = storage.CreateObjectUploader(_bucket, MakePath(domain, path), null, new MemoryStream());
2022-01-25 09:29:11 +00:00
var sessionUri = await tempUploader.InitiateSessionAsync();
2019-06-04 14:43:20 +00:00
return sessionUri.ToString();
}
2022-01-25 09:29:11 +00:00
public override async Task<string> UploadChunkAsync(string domain,
string path,
string uploadUri,
Stream stream,
long defaultChunkSize,
int chunkNumber,
long chunkLength)
2019-06-04 14:43:20 +00:00
{
2019-08-15 13:03:57 +00:00
var bytesRangeStart = Convert.ToString((chunkNumber - 1) * defaultChunkSize);
var bytesRangeEnd = Convert.ToString((chunkNumber - 1) * defaultChunkSize + chunkLength - 1);
2019-06-04 14:43:20 +00:00
2019-08-15 13:03:57 +00:00
var totalBytes = "*";
2019-06-04 14:43:20 +00:00
if (chunkLength != defaultChunkSize)
totalBytes = Convert.ToString((chunkNumber - 1) * defaultChunkSize + chunkLength);
2022-06-29 15:40:35 +00:00
2021-10-12 10:14:33 +00:00
var request = new HttpRequestMessage();
request.RequestUri = new Uri(uploadUri);
request.Method = HttpMethod.Put;
request.Content = new StreamContent(stream);
2022-06-29 15:40:35 +00:00
request.Content.Headers.ContentRange = new ContentRangeHeaderValue(Convert.ToInt64(bytesRangeStart),
Convert.ToInt64(bytesRangeEnd),
Convert.ToInt64(totalBytes));
2021-11-24 19:34:39 +00:00
2019-06-04 14:43:20 +00:00
2022-01-20 09:28:48 +00:00
const int MAX_RETRIES = 100;
2019-06-04 14:43:20 +00:00
int millisecondsTimeout;
2019-08-15 13:03:57 +00:00
for (var i = 0; i < MAX_RETRIES; i++)
2019-06-04 14:43:20 +00:00
{
2022-01-10 09:24:21 +00:00
millisecondsTimeout = Math.Min(Convert.ToInt32(Math.Pow(2, i)) + RandomNumberGenerator.GetInt32(1000), 32 * 1000);
2019-06-04 14:43:20 +00:00
try
{
2022-01-13 11:19:39 +00:00
var httpClient = ClientFactory.CreateClient();
2022-01-25 09:29:11 +00:00
using var response = await httpClient.SendAsync(request);
2019-06-04 14:43:20 +00:00
break;
}
2021-10-12 10:14:33 +00:00
catch (HttpRequestException ex)
2019-06-04 14:43:20 +00:00
{
2021-10-12 10:14:33 +00:00
var status = (int)ex.StatusCode;
2019-06-04 14:43:20 +00:00
if (status == 408 || status == 500 || status == 502 || status == 503 || status == 504)
{
Thread.Sleep(millisecondsTimeout);
continue;
}
2020-11-17 10:00:01 +00:00
if (status != 308)
2021-06-16 15:00:27 +00:00
throw;
2019-06-04 14:43:20 +00:00
break;
}
2020-11-17 10:47:17 +00:00
catch
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
await AbortChunkedUploadAsync(domain, path, uploadUri);
2020-11-17 10:47:17 +00:00
throw;
2019-06-04 14:43:20 +00:00
}
}
2019-08-15 13:16:39 +00:00
return string.Empty;
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override async Task<Uri> FinalizeChunkedUploadAsync(string domain, string path, string uploadUri, Dictionary<int, string> eTags)
2019-06-04 14:43:20 +00:00
{
if (QuotaController != null)
{
2022-01-25 09:29:11 +00:00
var size = await GetFileSizeAsync(domain, path);
2019-06-04 14:43:20 +00:00
QuotaUsedAdd(domain, size);
}
2022-01-25 09:29:11 +00:00
return await GetUriAsync(domain, path);
2019-06-04 14:43:20 +00:00
}
2022-01-25 09:29:11 +00:00
public override Task AbortChunkedUploadAsync(string domain, string path, string uploadUri)
2019-06-04 14:43:20 +00:00
{
2022-01-25 09:29:11 +00:00
return Task.CompletedTask;
2019-06-04 14:43:20 +00:00
}
public override bool IsSupportChunking { get { return true; } }
#endregion
public override string GetUploadForm(string domain, string directoryPath, string redirectTo, long maxUploadSize, string contentType, string contentDisposition, string submitLabel)
{
throw new NotImplementedException();
}
2022-01-25 09:29:11 +00:00
public override Task<string> GetUploadedUrlAsync(string domain, string directoryPath)
2019-06-04 14:43:20 +00:00
{
throw new NotImplementedException();
}
public override string GetUploadUrl()
{
throw new NotImplementedException();
}
public override string GetPostParams(string domain, string directoryPath, long maxUploadSize, string contentType, string contentDisposition)
{
throw new NotImplementedException();
}
}
}