Merge branch 'develop' into feature/thirdparty-integrations
This commit is contained in:
commit
e45ecf03ea
@ -32,7 +32,7 @@ using ASC.Files.Thirdparty;
|
|||||||
|
|
||||||
namespace ASC.Files.Core
|
namespace ASC.Files.Core
|
||||||
{
|
{
|
||||||
[Scope(typeof(CachedProviderAccountDao), Additional = typeof(ProviderAccountDaoExtension))]
|
[Scope(typeof(ProviderAccountDao), Additional = typeof(ProviderAccountDaoExtension))]
|
||||||
public interface IProviderDao
|
public interface IProviderDao
|
||||||
{
|
{
|
||||||
IProviderInfo GetProviderInfo(int linkId);
|
IProviderInfo GetProviderInfo(int linkId);
|
||||||
|
@ -19,7 +19,7 @@ namespace ASC.Files.Core.EF
|
|||||||
|
|
||||||
public override object[] GetKeys()
|
public override object[] GetKeys()
|
||||||
{
|
{
|
||||||
return new object[] { Id };
|
return new object[] { HashId };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ namespace ASC.Files.Thirdparty.Box
|
|||||||
{
|
{
|
||||||
internal abstract class BoxDaoBase : ThirdPartyProviderDao<BoxProviderInfo>
|
internal abstract class BoxDaoBase : ThirdPartyProviderDao<BoxProviderInfo>
|
||||||
{
|
{
|
||||||
public override string Id { get => "box"; }
|
protected override string Id { get => "box"; }
|
||||||
|
|
||||||
public BoxDaoBase(
|
public BoxDaoBase(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
|
@ -183,7 +183,7 @@ namespace ASC.Files.Thirdparty.Box
|
|||||||
|
|
||||||
internal BoxStorage CreateStorage(OAuth20Token token, int id)
|
internal BoxStorage CreateStorage(OAuth20Token token, int id)
|
||||||
{
|
{
|
||||||
if (Storage != null) return Storage;
|
if (Storage != null && Storage.IsOpened) return Storage;
|
||||||
|
|
||||||
var boxStorage = new BoxStorage();
|
var boxStorage = new BoxStorage();
|
||||||
CheckToken(token, id);
|
CheckToken(token, id);
|
||||||
@ -199,7 +199,7 @@ namespace ASC.Files.Thirdparty.Box
|
|||||||
{
|
{
|
||||||
token = OAuth20TokenHelper.RefreshToken<BoxLoginProvider>(ConsumerFactory, token);
|
token = OAuth20TokenHelper.RefreshToken<BoxLoginProvider>(ConsumerFactory, token);
|
||||||
|
|
||||||
var dbDao = ServiceProvider.GetService<CachedProviderAccountDao>();
|
var dbDao = ServiceProvider.GetService<ProviderAccountDao>();
|
||||||
dbDao.UpdateProviderInfo(id, new AuthData(token: token.ToJson()));
|
dbDao.UpdateProviderInfo(id, new AuthData(token: token.ToJson()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* (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.Concurrent;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
using ASC.Common;
|
|
||||||
using ASC.Common.Caching;
|
|
||||||
using ASC.Common.Logging;
|
|
||||||
using ASC.Core;
|
|
||||||
using ASC.Core.Common.Configuration;
|
|
||||||
using ASC.Core.Common.EF;
|
|
||||||
using ASC.Core.Tenants;
|
|
||||||
using ASC.Files.Core;
|
|
||||||
using ASC.Files.Core.EF;
|
|
||||||
using ASC.Security.Cryptography;
|
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace ASC.Files.Thirdparty
|
|
||||||
{
|
|
||||||
[Singletone]
|
|
||||||
internal class CachedProviderAccountDaoNotify
|
|
||||||
{
|
|
||||||
public ConcurrentDictionary<string, IProviderInfo> Cache { get; private set; }
|
|
||||||
internal ICacheNotify<ProviderAccountCacheItem> CacheNotify { get; set; }
|
|
||||||
|
|
||||||
public CachedProviderAccountDaoNotify(ICacheNotify<ProviderAccountCacheItem> cacheNotify)
|
|
||||||
{
|
|
||||||
Cache = new ConcurrentDictionary<string, IProviderInfo>();
|
|
||||||
CacheNotify = cacheNotify;
|
|
||||||
cacheNotify.Subscribe((i) => RemoveFromCache(i.Key), CacheNotifyAction.Any);
|
|
||||||
}
|
|
||||||
private void RemoveFromCache(string key)
|
|
||||||
{
|
|
||||||
Cache.TryRemove(key, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Scope]
|
|
||||||
internal class CachedProviderAccountDao : ProviderAccountDao
|
|
||||||
{
|
|
||||||
private readonly ConcurrentDictionary<string, IProviderInfo> cache;
|
|
||||||
private readonly ICacheNotify<ProviderAccountCacheItem> cacheNotify;
|
|
||||||
|
|
||||||
private string _rootKey { get => TenantID.ToString(CultureInfo.InvariantCulture); }
|
|
||||||
|
|
||||||
public CachedProviderAccountDao(
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
TenantUtil tenantUtil,
|
|
||||||
TenantManager tenantManager,
|
|
||||||
InstanceCrypto instanceCrypto,
|
|
||||||
SecurityContext securityContext,
|
|
||||||
ConsumerFactory consumerFactory,
|
|
||||||
DbContextManager<FilesDbContext> dbContextManager,
|
|
||||||
IOptionsMonitor<ILog> options,
|
|
||||||
CachedProviderAccountDaoNotify cachedProviderAccountDaoNotify)
|
|
||||||
: base(serviceProvider, tenantUtil, tenantManager, instanceCrypto, securityContext, consumerFactory, dbContextManager, options)
|
|
||||||
{
|
|
||||||
cache = cachedProviderAccountDaoNotify.Cache;
|
|
||||||
cacheNotify = cachedProviderAccountDaoNotify.CacheNotify;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IProviderInfo GetProviderInfo(int linkId)
|
|
||||||
{
|
|
||||||
var key = _rootKey + linkId.ToString(CultureInfo.InvariantCulture);
|
|
||||||
if (!cache.TryGetValue(key, out var value))
|
|
||||||
{
|
|
||||||
value = base.GetProviderInfo(linkId);
|
|
||||||
cache.TryAdd(key, value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void RemoveProviderInfo(int linkId)
|
|
||||||
{
|
|
||||||
base.RemoveProviderInfo(linkId);
|
|
||||||
|
|
||||||
var key = _rootKey + linkId.ToString(CultureInfo.InvariantCulture);
|
|
||||||
cacheNotify.Publish(new ProviderAccountCacheItem { Key = key }, CacheNotifyAction.Any);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int UpdateProviderInfo(int linkId, string customerTitle, AuthData authData, FolderType folderType, Guid? userId = null)
|
|
||||||
{
|
|
||||||
var result = base.UpdateProviderInfo(linkId, customerTitle, authData, folderType, userId);
|
|
||||||
|
|
||||||
var key = _rootKey + linkId.ToString(CultureInfo.InvariantCulture);
|
|
||||||
cacheNotify.Publish(new ProviderAccountCacheItem { Key = key }, CacheNotifyAction.Any);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int UpdateProviderInfo(int linkId, AuthData authData)
|
|
||||||
{
|
|
||||||
var result = base.UpdateProviderInfo(linkId, authData);
|
|
||||||
|
|
||||||
var key = _rootKey + linkId.ToString(CultureInfo.InvariantCulture);
|
|
||||||
cacheNotify.Publish(new ProviderAccountCacheItem { Key = key }, CacheNotifyAction.Any);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -48,7 +48,7 @@ namespace ASC.Files.Thirdparty.Dropbox
|
|||||||
{
|
{
|
||||||
internal abstract class DropboxDaoBase : ThirdPartyProviderDao<DropboxProviderInfo>
|
internal abstract class DropboxDaoBase : ThirdPartyProviderDao<DropboxProviderInfo>
|
||||||
{
|
{
|
||||||
public override string Id { get => "dropbox"; }
|
protected override string Id { get => "dropbox"; }
|
||||||
|
|
||||||
public DropboxDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
public DropboxDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
||||||
{
|
{
|
||||||
|
@ -162,7 +162,7 @@ namespace ASC.Files.Thirdparty.Dropbox
|
|||||||
|
|
||||||
public DropboxStorage CreateStorage(OAuth20Token token)
|
public DropboxStorage CreateStorage(OAuth20Token token)
|
||||||
{
|
{
|
||||||
if (Storage != null) return Storage;
|
if (Storage != null && Storage.IsOpened) return Storage;
|
||||||
|
|
||||||
var dropboxStorage = new DropboxStorage();
|
var dropboxStorage = new DropboxStorage();
|
||||||
dropboxStorage.Open(token);
|
dropboxStorage.Open(token);
|
||||||
|
@ -49,7 +49,7 @@ namespace ASC.Files.Thirdparty.GoogleDrive
|
|||||||
{
|
{
|
||||||
internal abstract class GoogleDriveDaoBase : ThirdPartyProviderDao<GoogleDriveProviderInfo>
|
internal abstract class GoogleDriveDaoBase : ThirdPartyProviderDao<GoogleDriveProviderInfo>
|
||||||
{
|
{
|
||||||
public override string Id { get => "drive"; }
|
protected override string Id { get => "drive"; }
|
||||||
|
|
||||||
public GoogleDriveDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
public GoogleDriveDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
||||||
{
|
{
|
||||||
|
@ -196,7 +196,7 @@ namespace ASC.Files.Thirdparty.GoogleDrive
|
|||||||
|
|
||||||
public GoogleDriveStorage CreateStorage(OAuth20Token token, int id)
|
public GoogleDriveStorage CreateStorage(OAuth20Token token, int id)
|
||||||
{
|
{
|
||||||
if (Storage != null) return Storage;
|
if (Storage != null && Storage.IsOpened) return Storage;
|
||||||
|
|
||||||
var driveStorage = ServiceProvider.GetService<GoogleDriveStorage>();
|
var driveStorage = ServiceProvider.GetService<GoogleDriveStorage>();
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ namespace ASC.Files.Thirdparty.GoogleDrive
|
|||||||
{
|
{
|
||||||
token = OAuth20TokenHelper.RefreshToken<GoogleLoginProvider>(ConsumerFactory, token);
|
token = OAuth20TokenHelper.RefreshToken<GoogleLoginProvider>(ConsumerFactory, token);
|
||||||
|
|
||||||
var dbDao = ServiceProvider.GetService<CachedProviderAccountDao>();
|
var dbDao = ServiceProvider.GetService<ProviderAccountDao>();
|
||||||
var authData = new AuthData(token: token.ToJson());
|
var authData = new AuthData(token: token.ToJson());
|
||||||
dbDao.UpdateProviderInfo(id, authData);
|
dbDao.UpdateProviderInfo(id, authData);
|
||||||
}
|
}
|
||||||
|
@ -28,14 +28,14 @@ namespace ASC.Files.Thirdparty
|
|||||||
protected TenantUtil TenantUtil { get; }
|
protected TenantUtil TenantUtil { get; }
|
||||||
protected FilesDbContext FilesDbContext { get; }
|
protected FilesDbContext FilesDbContext { get; }
|
||||||
protected SetupInfo SetupInfo { get; }
|
protected SetupInfo SetupInfo { get; }
|
||||||
public ILog Log { get; }
|
protected ILog Log { get; }
|
||||||
protected FileUtility FileUtility { get; }
|
protected FileUtility FileUtility { get; }
|
||||||
|
|
||||||
public RegexDaoSelectorBase<T> DaoSelector { get; set; }
|
protected RegexDaoSelectorBase<T> DaoSelector { get; set; }
|
||||||
public T ProviderInfo { get; set; }
|
protected T ProviderInfo { get; set; }
|
||||||
public string PathPrefix { get; private set; }
|
protected string PathPrefix { get; private set; }
|
||||||
|
|
||||||
public abstract string Id { get; }
|
protected abstract string Id { get; }
|
||||||
|
|
||||||
public ThirdPartyProviderDao(
|
public ThirdPartyProviderDao(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
|
@ -47,7 +47,7 @@ namespace ASC.Files.Thirdparty.OneDrive
|
|||||||
{
|
{
|
||||||
internal abstract class OneDriveDaoBase : ThirdPartyProviderDao<OneDriveProviderInfo>
|
internal abstract class OneDriveDaoBase : ThirdPartyProviderDao<OneDriveProviderInfo>
|
||||||
{
|
{
|
||||||
public override string Id { get => "onedrive"; }
|
protected override string Id { get => "onedrive"; }
|
||||||
|
|
||||||
public OneDriveDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
public OneDriveDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
||||||
{
|
{
|
||||||
|
@ -157,7 +157,7 @@ namespace ASC.Files.Thirdparty.OneDrive
|
|||||||
|
|
||||||
public OneDriveStorage CreateStorage(OAuth20Token token, int id)
|
public OneDriveStorage CreateStorage(OAuth20Token token, int id)
|
||||||
{
|
{
|
||||||
if (Storage != null) return Storage;
|
if (Storage != null && Storage.IsOpened) return Storage;
|
||||||
|
|
||||||
var onedriveStorage = ServiceProvider.GetService<OneDriveStorage>();
|
var onedriveStorage = ServiceProvider.GetService<OneDriveStorage>();
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ namespace ASC.Files.Thirdparty.OneDrive
|
|||||||
{
|
{
|
||||||
token = OAuth20TokenHelper.RefreshToken<OneDriveLoginProvider>(ConsumerFactory, token);
|
token = OAuth20TokenHelper.RefreshToken<OneDriveLoginProvider>(ConsumerFactory, token);
|
||||||
|
|
||||||
var dbDao = ServiceProvider.GetService<CachedProviderAccountDao>();
|
var dbDao = ServiceProvider.GetService<ProviderAccountDao>();
|
||||||
var authData = new AuthData(token: token.ToJson());
|
var authData = new AuthData(token: token.ToJson());
|
||||||
dbDao.UpdateProviderInfo(id, authData);
|
dbDao.UpdateProviderInfo(id, authData);
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ namespace ASC.Files.Thirdparty
|
|||||||
|
|
||||||
public void RenameProvider(T provider, string newTitle)
|
public void RenameProvider(T provider, string newTitle)
|
||||||
{
|
{
|
||||||
var dbDao = ServiceProvider.GetService<CachedProviderAccountDao>();
|
var dbDao = ServiceProvider.GetService<ProviderAccountDao>();
|
||||||
dbDao.UpdateProviderInfo(provider.ID, newTitle, null, provider.RootFolderType);
|
dbDao.UpdateProviderInfo(provider.ID, newTitle, null, provider.RootFolderType);
|
||||||
provider.UpdateTitle(newTitle); //This will update cached version too
|
provider.UpdateTitle(newTitle); //This will update cached version too
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace ASC.Files.Thirdparty.SharePoint
|
|||||||
{
|
{
|
||||||
internal class SharePointDaoBase : ThirdPartyProviderDao<SharePointProviderInfo>
|
internal class SharePointDaoBase : ThirdPartyProviderDao<SharePointProviderInfo>
|
||||||
{
|
{
|
||||||
public override string Id { get => "spoint"; }
|
protected override string Id { get => "spoint"; }
|
||||||
|
|
||||||
public SharePointDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
public SharePointDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +50,7 @@ namespace ASC.Files.Thirdparty.Sharpbox
|
|||||||
{
|
{
|
||||||
internal abstract class SharpBoxDaoBase : ThirdPartyProviderDao<SharpBoxProviderInfo>
|
internal abstract class SharpBoxDaoBase : ThirdPartyProviderDao<SharpBoxProviderInfo>
|
||||||
{
|
{
|
||||||
public override string Id { get => "sbox"; }
|
protected override string Id { get => "sbox"; }
|
||||||
|
|
||||||
public SharpBoxDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
public SharpBoxDaoBase(IServiceProvider serviceProvider, UserManager userManager, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FilesDbContext> dbContextManager, SetupInfo setupInfo, IOptionsMonitor<ILog> monitor, FileUtility fileUtility) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility)
|
||||||
{
|
{
|
||||||
|
@ -864,21 +864,8 @@ namespace ASC.Employee.Core.Controllers
|
|||||||
return new ThumbnailsDataWrapper(user.ID, UserPhotoManager);
|
return new ThumbnailsDataWrapper(user.ID, UserPhotoManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Create("{userid}/photo")]
|
[Create("{userid}/photo")]
|
||||||
public FileUploadResult UploadMemberPhotoFromBody(string userid, [FromBody]IFormCollection model)
|
public FileUploadResult UploadMemberPhoto(string userid, IFormCollection model)
|
||||||
{
|
|
||||||
return UploadMemberPhoto(userid, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Create("{userid}/photo")]
|
|
||||||
[Consumes("application/x-www-form-urlencoded")]
|
|
||||||
public FileUploadResult UploadMemberPhotoFromForm(string userid, [FromForm] IFormCollection model)
|
|
||||||
{
|
|
||||||
return UploadMemberPhoto(userid, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FileUploadResult UploadMemberPhoto(string userid, IFormCollection model)
|
|
||||||
{
|
{
|
||||||
var result = new People.Models.FileUploadResult();
|
var result = new People.Models.FileUploadResult();
|
||||||
var autosave = bool.Parse(model["Autosave"]);
|
var autosave = bool.Parse(model["Autosave"]);
|
||||||
|
Loading…
Reference in New Issue
Block a user