Merge pull request #919 from ONLYOFFICE/feature/recalculate-user-quota

Feature/recalculate user quota
This commit is contained in:
Pavel Bannov 2022-11-02 14:08:06 +03:00 committed by GitHub
commit 186f7e14b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 316 additions and 71 deletions

View File

@ -0,0 +1,43 @@
// (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.Api.Core.Model;
public class TaskProgressDto
{
public bool IsCompleted { get; set; }
public int Progress { get; set; }
public static TaskProgressDto GetSample()
{
return new TaskProgressDto
{
IsCompleted = true,
Progress = 0
};
}
}

View File

@ -101,12 +101,8 @@ class DbQuotaService : IQuotaService
using var coreDbContext = _dbContextFactory.CreateDbContext();
using var tx = coreDbContext.Database.BeginTransaction();
AddQuota(coreDbContext, Guid.Empty);
if (row.UserId != Guid.Empty)
{
AddQuota(coreDbContext, row.UserId);
}
AddQuota(coreDbContext, row.UserId);
tx.Commit();
});

View File

@ -76,6 +76,7 @@ public class TenantQuotaController : IQuotaController
CurrentSize += size;
}
SetTenantQuotaRow(module, domain, size, dataTag, true, Guid.Empty);
SetTenantQuotaRow(module, domain, size, dataTag, true, _authContext.CurrentAccount.ID);
}
@ -87,6 +88,7 @@ public class TenantQuotaController : IQuotaController
CurrentSize += size;
}
SetTenantQuotaRow(module, domain, size, dataTag, true, Guid.Empty);
SetTenantQuotaRow(module, domain, size, dataTag, true, _authContext.CurrentAccount.ID);
}

View File

@ -27,7 +27,7 @@
namespace ASC.Web.Files;
[Scope]
public class FilesSpaceUsageStatManager : SpaceUsageStatManager
public class FilesSpaceUsageStatManager : SpaceUsageStatManager, IUserSpaceUsage
{
private readonly IDbContextFactory<FilesDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager;
@ -37,6 +37,9 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
private readonly CommonLinkUtility _commonLinkUtility;
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly PathProvider _pathProvider;
private readonly IDaoFactory _daoFactory;
private readonly GlobalFolder _globalFolder;
private readonly FileMarker _fileMarker;
public FilesSpaceUsageStatManager(
IDbContextFactory<FilesDbContext> dbContextFactory,
@ -46,7 +49,10 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
DisplayUserSettingsHelper displayUserSettingsHelper,
CommonLinkUtility commonLinkUtility,
GlobalFolderHelper globalFolderHelper,
PathProvider pathProvider)
PathProvider pathProvider,
IDaoFactory daoFactory,
GlobalFolder globalFolder,
FileMarker fileMarker)
{
_dbContextFactory = dbContextFactory;
_tenantManager = tenantManager;
@ -56,6 +62,9 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
_commonLinkUtility = commonLinkUtility;
_globalFolderHelper = globalFolderHelper;
_pathProvider = pathProvider;
_daoFactory = daoFactory;
_globalFolder = globalFolder;
_fileMarker = fileMarker;
}
public override ValueTask<List<UsageSpaceStatItem>> GetStatDataAsync()
@ -107,31 +116,8 @@ public class FilesSpaceUsageStatManager : SpaceUsageStatManager
.ToListAsync();
}
}
[Scope]
public class FilesUserSpaceUsage : IUserSpaceUsage
{
private readonly IDbContextFactory<FilesDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager;
private readonly GlobalFolder _globalFolder;
private readonly FileMarker _fileMarker;
private readonly IDaoFactory _daoFactory;
public FilesUserSpaceUsage(
IDbContextFactory<FilesDbContext> dbContextFactory,
TenantManager tenantManager,
GlobalFolder globalFolder,
FileMarker fileMarker,
IDaoFactory daoFactory)
{
_dbContextFactory = dbContextFactory;
_tenantManager = tenantManager;
_globalFolder = globalFolder;
_fileMarker = fileMarker;
_daoFactory = daoFactory;
}
public async Task<long> GetUserSpaceUsageAsync(Guid userId)
{
var tenantId = _tenantManager.GetCurrentTenant().Id;
@ -143,4 +129,15 @@ public class FilesUserSpaceUsage : IUserSpaceUsage
.Where(r => r.TenantId == tenantId && r.CreateBy == userId && (r.ParentId == my || r.ParentId == trash))
.SumAsync(r => r.ContentLength);
}
public async Task RecalculateUserQuota(int TenantId, Guid userId)
{
_tenantManager.SetCurrentTenant(TenantId);
var size = await GetUserSpaceUsageAsync(userId);
_tenantManager.SetTenantQuotaRow(
new TenantQuotaRow { Tenant = TenantId, Path = $"/{FileConstant.ModuleId}/", Counter = size, Tag = WebItemManager.DocumentsProductID.ToString(), UserId = userId, LastModified = DateTime.UtcNow },
false);
}
}

View File

@ -31,7 +31,7 @@ public class ProductEntryPoint : Product
{
internal const string ProductPath = "/products/files/";
//public FilesSpaceUsageStatManager FilesSpaceUsageStatManager { get; }
private readonly FilesSpaceUsageStatManager _filesSpaceUsageStatManager;
private readonly CoreBaseSettings _coreBaseSettings;
private readonly AuthContext _authContext;
private readonly UserManager _userManager;
@ -42,7 +42,7 @@ public class ProductEntryPoint : Product
public ProductEntryPoint() { }
public ProductEntryPoint(
// FilesSpaceUsageStatManager filesSpaceUsageStatManager,
FilesSpaceUsageStatManager filesSpaceUsageStatManager,
CoreBaseSettings coreBaseSettings,
AuthContext authContext,
UserManager userManager,
@ -50,7 +50,7 @@ public class ProductEntryPoint : Product
// SubscriptionManager subscriptionManager
)
{
// FilesSpaceUsageStatManager = filesSpaceUsageStatManager;
_filesSpaceUsageStatManager = filesSpaceUsageStatManager;
_coreBaseSettings = coreBaseSettings;
_authContext = authContext;
_userManager = userManager;
@ -83,7 +83,7 @@ public class ProductEntryPoint : Product
LargeIconFileName = "images/files.svg",
DefaultSortOrder = 10,
//SubscriptionManager = SubscriptionManager,
//SpaceUsageStatManager = FilesSpaceUsageStatManager,
SpaceUsageStatManager = _filesSpaceUsageStatManager,
AdminOpportunities = adminOpportunities,
UserOpportunities = userOpportunities,
CanNotBeDisabled = true,

View File

@ -0,0 +1,185 @@
// (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.Web.Files;
[Singletone(Additional = typeof(UsersQuotaOperationExtension))]
public class UsersQuotaSyncOperation
{
public const string CUSTOM_DISTRIBUTED_TASK_QUEUE_NAME = "userQuotaOperation";
private readonly DistributedTaskQueue _progressQueue;
private readonly IServiceProvider _serviceProvider;
public void RecalculateQuota(Tenant tenant)
{
var item = _progressQueue.GetAllTasks<UsersQuotaSyncJob>().FirstOrDefault(t => t.TenantId == tenant.Id);
if (item != null && item.IsCompleted)
{
_progressQueue.DequeueTask(item.Id);
item = null;
}
if (item == null)
{
item = _serviceProvider.GetRequiredService<UsersQuotaSyncJob>();
item.InitJob(tenant);
_progressQueue.EnqueueTask(item.RunJobAsync, item);
}
}
public TaskProgressDto CheckRecalculateQuota(Tenant tenant)
{
var item = _progressQueue.GetAllTasks<UsersQuotaSyncJob>().FirstOrDefault(t => t.TenantId == tenant.Id);
var progress = new TaskProgressDto();
if (item == null)
{
progress.IsCompleted = true;
return progress;
}
progress.IsCompleted = item.IsCompleted;
progress.Progress = (int)item.Percentage;
if (item.IsCompleted)
{
_progressQueue.DequeueTask(item.Id);
}
return progress;
}
public UsersQuotaSyncOperation(IServiceProvider serviceProvider, IDistributedTaskQueueFactory queueFactory)
{
_serviceProvider = serviceProvider;
_progressQueue = queueFactory.CreateQueue(CUSTOM_DISTRIBUTED_TASK_QUEUE_NAME);
}
public static class UsersQuotaOperationExtension
{
public static void Register(DIHelper services)
{
services.TryAdd<UsersQuotaSyncJob>();
}
}
}
public class UsersQuotaSyncJob : DistributedTaskProgress
{
private readonly IServiceScopeFactory _serviceScopeFactory;
protected readonly IDbContextFactory<FilesDbContext> _dbContextFactory;
private int? _tenantId;
public int TenantId
{
get
{
return _tenantId ?? this[nameof(_tenantId)];
}
private set
{
_tenantId = value;
this[nameof(_tenantId)] = value;
}
}
public UsersQuotaSyncJob(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void InitJob(Tenant tenant)
{
TenantId = tenant.Id;
}
public async Task RunJobAsync(DistributedTask _, CancellationToken cancellationToken)
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var _tenantManager = scope.ServiceProvider.GetRequiredService<TenantManager>();
var _userManager = scope.ServiceProvider.GetRequiredService<UserManager>();
var _authentication = scope.ServiceProvider.GetRequiredService<AuthManager>();
var _securityContext = scope.ServiceProvider.GetRequiredService<SecurityContext>();
var _webItemManagerSecurity = scope.ServiceProvider.GetRequiredService<WebItemManagerSecurity>();
_tenantManager.SetCurrentTenant(TenantId);
var users = _userManager.GetUsers();
var webItems = _webItemManagerSecurity.GetItems(Web.Core.WebZones.WebZoneType.All, ItemAvailableState.All);
foreach (var user in users)
{
if (cancellationToken.IsCancellationRequested)
{
Status = DistributedTaskStatus.Canceled;
break;
}
Percentage += 1.0 * 100 / users.Length;
PublishChanges();
var account = _authentication.GetAccountByID(TenantId, user.Id);
_securityContext.AuthenticateMe(account);
foreach (var item in webItems)
{
IUserSpaceUsage manager;
if (item.ID == WebItemManager.DocumentsProductID)
{
manager = item.Context.SpaceUsageStatManager as IUserSpaceUsage;
if (manager == null)
{
continue;
}
await manager.RecalculateUserQuota(TenantId, user.Id);
}
}
}
}
catch (Exception ex)
{
Status = DistributedTaskStatus.Failted;
Exception = ex;
}
finally
{
IsCompleted = true;
}
PublishChanges();
}
}

View File

@ -54,6 +54,7 @@ global using AppLimit.CloudComputing.SharpBox.StorageProvider.DropBox;
global using ASC.Api.Collections;
global using ASC.Api.Core;
global using ASC.Api.Core.Model;
global using ASC.Api.Core.Security;
global using ASC.Api.Utils;
global using ASC.AuditTrail;
@ -150,6 +151,7 @@ global using ASC.Web.Files.Services.WCFService.FileOperations;
global using ASC.Web.Files.ThirdPartyApp;
global using ASC.Web.Files.Utils;
global using ASC.Web.Studio.Core;
global using ASC.Web.Studio.Core.Quota;
global using ASC.Web.Studio.Core.Notify;
global using ASC.Web.Studio.Utility;

View File

@ -250,12 +250,12 @@ public class GlobalStore
[Scope]
public class GlobalSpace
{
private readonly FilesUserSpaceUsage _filesUserSpaceUsage;
private readonly FilesSpaceUsageStatManager _filesSpaceUsageStatManager;
private readonly AuthContext _authContext;
public GlobalSpace(FilesUserSpaceUsage filesUserSpaceUsage, AuthContext authContext)
public GlobalSpace(FilesSpaceUsageStatManager filesSpaceUsageStatManager, AuthContext authContext)
{
_filesUserSpaceUsage = filesUserSpaceUsage;
_filesSpaceUsageStatManager = filesSpaceUsageStatManager;
_authContext = authContext;
}
@ -266,7 +266,7 @@ public class GlobalSpace
public Task<long> GetUserUsedSpaceAsync(Guid userId)
{
return _filesUserSpaceUsage.GetUserSpaceUsageAsync(userId);
return _filesSpaceUsageStatManager.GetUserSpaceUsageAsync(userId);
}
}

View File

@ -35,13 +35,13 @@ public class ApiProductEntryPoint : ProductEntryPoint
}
public ApiProductEntryPoint(
//FilesSpaceUsageStatManager filesSpaceUsageStatManager,
FilesSpaceUsageStatManager filesSpaceUsageStatManager,
CoreBaseSettings coreBaseSettings,
AuthContext authContext,
UserManager userManager,
NotifyConfiguration notifyConfiguration
//SubscriptionManager subscriptionManager
) : base(coreBaseSettings, authContext, userManager, notifyConfiguration)
) : base(filesSpaceUsageStatManager, coreBaseSettings, authContext, userManager, notifyConfiguration)
{
}

View File

@ -33,7 +33,7 @@ public class UserController : PeopleControllerBase
private Tenant Tenant => _apiContext.Tenant;
private readonly ICache _cache;
private readonly TenantManager _tenantManager;
private readonly TenantManager _tenantManager;
private readonly CookiesManager _cookiesManager;
private readonly CoreBaseSettings _coreBaseSettings;
private readonly CustomNamingPeople _customNamingPeople;
@ -59,15 +59,16 @@ public class UserController : PeopleControllerBase
private readonly SetupInfo _setupInfo;
private readonly SettingsManager _settingsManager;
private readonly RoomLinkService _roomLinkService;
private readonly FileSecurity _fileSecurity;
private readonly IQuotaService _quotaService;
private readonly FileSecurity _fileSecurity;
private readonly IQuotaService _quotaService;
private readonly CountRoomAdminChecker _countRoomAdminChecker;
private readonly UsersQuotaSyncOperation _usersQuotaSyncOperation;
private readonly CountUserChecker _countUserChecker;
private readonly UsersInRoomChecker _usersInRoomChecker;
public UserController(
ICache cache,
TenantManager tenantManager,
TenantManager tenantManager,
CookiesManager cookiesManager,
CoreBaseSettings coreBaseSettings,
CustomNamingPeople customNamingPeople,
@ -98,8 +99,9 @@ public class UserController : PeopleControllerBase
IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor,
SettingsManager settingsManager,
RoomLinkService roomLinkService,
RoomLinkService roomLinkService,
FileSecurity fileSecurity,
UsersQuotaSyncOperation usersQuotaSyncOperation,
CountRoomAdminChecker countRoomAdminChecker,
CountUserChecker activeUsersChecker,
UsersInRoomChecker usersInRoomChecker,
@ -107,7 +109,7 @@ public class UserController : PeopleControllerBase
: base(userManager, permissionContext, apiContext, userPhotoManager, httpClientFactory, httpContextAccessor)
{
_cache = cache;
_tenantManager = tenantManager;
_tenantManager = tenantManager;
_cookiesManager = cookiesManager;
_coreBaseSettings = coreBaseSettings;
_customNamingPeople = customNamingPeople;
@ -133,11 +135,12 @@ public class UserController : PeopleControllerBase
_setupInfo = setupInfo;
_settingsManager = settingsManager;
_roomLinkService = roomLinkService;
_fileSecurity = fileSecurity;
_fileSecurity = fileSecurity;
_countRoomAdminChecker = countRoomAdminChecker;
_countUserChecker = activeUsersChecker;
_usersInRoomChecker = usersInRoomChecker;
_quotaService = quotaService;
_usersQuotaSyncOperation = usersQuotaSyncOperation;
}
[HttpPost("active")]
@ -203,27 +206,27 @@ public class UserController : PeopleControllerBase
{
_apiContext.AuthByClaim();
_permissionContext.DemandPermissions(Constants.Action_AddRemoveUser);
_permissionContext.DemandPermissions(Constants.Action_AddRemoveUser);
var options = inDto.FromInviteLink ? await _roomLinkService.GetOptionsAsync(inDto.Key, inDto.Email, inDto.Type) : null;
if (options != null && !options.IsCorrect)
{
{
throw new SecurityException(FilesCommonResource.ErrorMessage_InvintationLink);
}
inDto.Type = options != null ? options.EmployeeType : inDto.Type;
var user = new UserInfo();
var byEmail = options?.LinkType == LinkType.InvintationByEmail;
if (byEmail)
{
{
user = _userManager.GetUserByEmail(inDto.Email);
if (user == Constants.LostUser || user.ActivationStatus != EmployeeActivationStatus.Pending)
{
{
throw new SecurityException(FilesCommonResource.ErrorMessage_InvintationLink);
}
}
@ -270,26 +273,26 @@ public class UserController : PeopleControllerBase
if (inDto.Files != _userPhotoManager.GetDefaultPhotoAbsoluteWebPath())
{
await UpdatePhotoUrl(inDto.Files, user);
}
}
if (options != null && options.LinkType == LinkType.InvintationToRoom)
{
var success = int.TryParse(options.RoomId, out var id);
if (success)
{
{
await _usersInRoomChecker.CheckAppend();
await _fileSecurity.ShareAsync(id, FileEntryType.Folder, user.Id, options.Share);
}
else
{
{
await _usersInRoomChecker.CheckAppend();
await _fileSecurity.ShareAsync(options.RoomId, FileEntryType.Folder, user.Id, options.Share);
}
}
var messageAction = inDto.IsUser ? MessageAction.GuestCreated : MessageAction.UserCreated;
_messageService.Send(messageAction, _messageTarget.Create(user.Id), user.DisplayUserName(false, _displayUserSettingsHelper));
_messageService.Send(messageAction, _messageTarget.Create(user.Id), user.DisplayUserName(false, _displayUserSettingsHelper));
return await _employeeFullDtoHelper.GetFull(user);
}
@ -494,7 +497,7 @@ public class UserController : PeopleControllerBase
return await _employeeFullDtoHelper.GetFull(user);
}
[AllowNotPayment]
[Authorize(AuthenticationSchemes = "confirm", Roles = "LinkInvite,Everyone")]
[HttpGet("{username}", Order = 1)]
@ -700,7 +703,7 @@ public class UserController : PeopleControllerBase
_userManager.IsUser(user) ? EmployeeType.User : EmployeeType.RoomAdmin;
_studioNotifyService.SendDocSpaceInvite(user.Email, _roomLinkService.GetInvitationLink(user.Email, type, _authContext.CurrentAccount.ID));
}
}
else
{
_studioNotifyService.SendEmailActivationInstructions(user, user.Email);
@ -732,7 +735,7 @@ public class UserController : PeopleControllerBase
_settingsManager.SaveForCurrentUser(darkThemeSettings);
return darkThemeSettings;
}
}
[AllowNotPayment]
[HttpGet("@self")]
@ -965,14 +968,14 @@ public class UserController : PeopleControllerBase
var canBeGuestFlag = !user.IsOwner(Tenant) && !_userManager.IsDocSpaceAdmin(user) && user.GetListAdminModules(_webItemSecurity, _webItemManager).Count == 0 && !user.IsMe(_authContext);
if (inDto.IsUser && !_userManager.IsUser(user) && canBeGuestFlag)
{
{
await _countUserChecker.CheckAppend();
_userManager.AddUserIntoGroup(user.Id, Constants.GroupUser.ID);
_webItemSecurityCache.ClearCache(Tenant.Id);
}
if (!self && !inDto.IsUser && _userManager.IsUser(user))
{
{
await _countRoomAdminChecker.CheckAppend();
_userManager.RemoveUserFromGroup(user.Id, Constants.GroupUser.ID);
_webItemSecurityCache.ClearCache(Tenant.Id);
@ -1010,11 +1013,11 @@ public class UserController : PeopleControllerBase
{
case EmployeeStatus.Active:
if (user.Status == EmployeeStatus.Terminated)
{
{
if (!_userManager.IsUser(user))
{
{
await _countRoomAdminChecker.CheckAppend();
}
}
else
{
await _countUserChecker.CheckAppend();
@ -1079,8 +1082,21 @@ public class UserController : PeopleControllerBase
{
yield return await _employeeFullDtoHelper.GetFull(user);
}
}
}
[HttpGet("recalculatequota")]
public void RecalculateQuota()
{
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
_usersQuotaSyncOperation.RecalculateQuota(_tenantManager.GetCurrentTenant());
}
[HttpGet("checkrecalculatequota")]
public TaskProgressDto CheckRecalculateQuota()
{
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return _usersQuotaSyncOperation.CheckRecalculateQuota(_tenantManager.GetCurrentTenant());
}
[HttpPut("quota")]
public async IAsyncEnumerable<EmployeeFullDto> UpdateUserQuota(UpdateMembersQuotaRequestDto inDto)
{
@ -1094,7 +1110,7 @@ public class UserController : PeopleControllerBase
if (inDto.Quota != -1)
{
var usedSpace = Math.Max(0,
_quotaService.FindUserQuotaRows(
_quotaService.FindUserQuotaRows(
_tenantManager.GetCurrentTenant().Id,
user.Id
)

View File

@ -31,6 +31,7 @@ global using System.ServiceModel.Security;
global using System.Web;
global using ASC.Api.Core;
global using ASC.Api.Core.Model;
global using ASC.Api.Core.Convention;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Utils;
@ -73,6 +74,7 @@ global using ASC.Web.Core.PublicResources;
global using ASC.Web.Core.Quota;
global using ASC.Web.Core.Users;
global using ASC.Web.Core.Utility;
global using ASC.Web.Files;
global using ASC.Web.Files.Classes;
global using ASC.Web.Studio.Core;
global using ASC.Web.Studio.Core.Notify;

View File

@ -43,4 +43,6 @@ public abstract class SpaceUsageStatManager
public interface IUserSpaceUsage
{
Task<long> GetUserSpaceUsageAsync(Guid userId);
Task RecalculateUserQuota(int tenantId, Guid userId);
}