Merge branch 'master' of github.com:ONLYOFFICE/CommunityServer-AspNetCore

This commit is contained in:
Alexey Safronov 2019-07-23 12:53:42 +03:00
commit b7d34a00a6
68 changed files with 820 additions and 783 deletions

View File

@ -53,11 +53,10 @@
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NVelocity" Version="1.2.0" />
<PackageReference Include="StackExchange.Redis" Version="2.0.601" />
<PackageReference Include="StackExchange.Redis.Extensions.Core" Version="5.0.3" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos/AscCacheItem.proto" />
<Protobuf Include="protos/AscCacheItem.proto" />
<Protobuf Include="protos\DistributedTaskCancelation.proto" />
</ItemGroup>
</Project>

View File

@ -25,35 +25,25 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.Caching;
using System.Text.RegularExpressions;
using StackExchange.Redis.Extensions.Core.Extensions;
namespace ASC.Common.Caching
{
public class AscCache : ICache, ICacheNotify
public class AscCache : ICache
{
public static readonly ICache Default;
public static readonly ICache Memory;
public static readonly ICacheNotify Notify;
public static readonly ICache Memory;
public readonly ICacheNotify<AscCacheItem> KafkaNotify;
private readonly ConcurrentDictionary<Type, ConcurrentBag<Action<object, CacheNotifyAction>>> actions =
new ConcurrentDictionary<Type, ConcurrentBag<Action<object, CacheNotifyAction>>>();
static AscCache()
{
Memory = new AscCache();
Default = ConfigurationManager.GetSection("redisCacheClient") != null ? new RedisCache() : Memory;
Notify = (ICacheNotify) Default;
Default = Memory;
}
private AscCache()
@ -119,13 +109,12 @@ namespace ASC.Common.Caching
public T HashGet<T>(string key, string field)
{
var cache = GetCache();
T value;
var dic = (IDictionary<string, T>) cache.Get(key);
if (dic != null && dic.TryGetValue(field, out value))
var dic = (IDictionary<string, T>)cache.Get(key);
if (dic != null && dic.TryGetValue(field, out var value))
{
return value;
}
return default(T);
return default;
}
public void HashSet<T>(string key, string field, T value)
@ -155,36 +144,6 @@ namespace ASC.Common.Caching
}
}
public void Subscribe<T>(Action<T, CacheNotifyAction> onchange)
{
if (onchange != null)
{
void action(object o, CacheNotifyAction a) => onchange((T)o, a);
actions.AddOrUpdate(typeof(T),
new ConcurrentBag<Action<object, CacheNotifyAction>> { action },
(type, bag) =>
{
bag.Add(action);
return bag;
});
}
else
{
actions.TryRemove(typeof(T), out var removed);
}
}
public void Publish<T>(T obj, CacheNotifyAction action)
{
actions.TryGetValue(typeof(T), out var onchange);
if (onchange != null)
{
onchange.ToArray().ForEach(r => r(obj, action));
}
}
public void ClearCache()
{
KafkaNotify.Publish(new AscCacheItem() { Id = Guid.NewGuid().ToString() }, CacheNotifyAction.Any);
@ -197,7 +156,6 @@ namespace ASC.Common.Caching
private static void OnClearCache()
{
Default.Remove(new Regex(".*"));
var keys = MemoryCache.Default.Select(r => r.Key).ToList();
foreach (var k in keys)

View File

@ -28,14 +28,7 @@ using System;
using Google.Protobuf;
namespace ASC.Common.Caching
{
public interface ICacheNotify
{
void Publish<T>(T obj, CacheNotifyAction action);
void Subscribe<T>(Action<T, CacheNotifyAction> onchange);
}
{
public interface ICacheNotify<T> where T : IMessage<T>, new()
{
void Publish(T obj, CacheNotifyAction action);

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -15,12 +16,12 @@ namespace ASC.Common.Caching
{
private ClientConfig ClientConfig { get; set; }
private ILog Log { get; set; }
private Dictionary<CacheNotifyAction, CancellationTokenSource> Cts { get; set; }
private ConcurrentDictionary<CacheNotifyAction, CancellationTokenSource> Cts { get; set; }
private MemoryCacheNotify<T> MemoryCacheNotify { get; set; }
public KafkaCache()
{
Log = LogManager.GetLogger("ASC");
Cts = new Dictionary<CacheNotifyAction, CancellationTokenSource>();
Cts = new ConcurrentDictionary<CacheNotifyAction, CancellationTokenSource>();
var settings = ConfigurationManager.GetSetting<KafkaSettings>("kafka");
if (settings != null && !string.IsNullOrEmpty(settings.BootstrapServers))
@ -70,11 +71,13 @@ namespace ASC.Common.Caching
return;
}
Cts[cacheNotifyAction] = new CancellationTokenSource();
void action()
{
var conf = new ConsumerConfig(ClientConfig)
{
GroupId = new Guid().ToString(),
GroupId = Guid.NewGuid().ToString(),
EnableAutoCommit = true
};
@ -83,9 +86,7 @@ namespace ASC.Common.Caching
.SetValueDeserializer(new ProtobufDeserializer<T>())
.Build();
c.Subscribe(GetChannelName(cacheNotifyAction));
Cts[cacheNotifyAction] = new CancellationTokenSource();
c.Assign(new TopicPartition(GetChannelName(cacheNotifyAction), new Partition()));
try
{
@ -122,7 +123,11 @@ namespace ASC.Common.Caching
public void Unsubscribe(CacheNotifyAction action)
{
Cts[action].Cancel();
Cts.TryGetValue(action, out var source);
if(source != null)
{
source.Cancel();
}
}
}

View File

@ -1,289 +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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ASC.Common.Logging;
using ASC.Common.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core;
using StackExchange.Redis.Extensions.Core.Extensions;
namespace ASC.Common.Caching
{
public class RedisCache : ICache, ICacheNotify
{
private readonly string CacheId = Guid.NewGuid().ToString();
private readonly StackExchangeRedisCacheClient redis;
private readonly ConcurrentDictionary<Type, ConcurrentBag<Action<object, CacheNotifyAction>>> actions = new ConcurrentDictionary<Type, ConcurrentBag<Action<object, CacheNotifyAction>>>();
public RedisCache()
{
var configuration = ConfigurationManager.AppSettings["redisConnection"];
if (string.IsNullOrEmpty(configuration))
throw new System.Configuration.ConfigurationErrorsException("Unable to locate <redisConnection> settings into your configuration file. Take a look https://stackexchange.github.io/StackExchange.Redis/Configuration.html");
var stringBuilder = new StringBuilder();
using (var stream = new StringWriter(stringBuilder))
{
var opts = ConfigurationOptions.Parse(configuration);
opts.SyncTimeout = 60000;
var connectionMultiplexer = (IConnectionMultiplexer)ConnectionMultiplexer.Connect(opts, stream);
redis = new StackExchangeRedisCacheClient(connectionMultiplexer, new Serializer());
LogManager.GetLogger("ASC").Debug(stringBuilder.ToString());
}
}
public T Get<T>(string key) where T : class
{
return redis.Get<T>(key);
}
public void Insert(string key, object value, TimeSpan sligingExpiration)
{
redis.Replace(key, value, sligingExpiration);
}
public void Insert(string key, object value, DateTime absolutExpiration)
{
redis.Replace(key, value, absolutExpiration == DateTime.MaxValue ? DateTimeOffset.MaxValue : new DateTimeOffset(absolutExpiration));
}
public void Remove(string key)
{
redis.Remove(key);
}
public void Remove(Regex pattern)
{
var glob = pattern.ToString().Replace(".*", "*").Replace(".", "?");
var keys = redis.SearchKeys(glob);
if (keys.Any())
{
redis.RemoveAll(keys);
}
}
public IDictionary<string, T> HashGetAll<T>(string key)
{
var dic = redis.Database.HashGetAll(key);
return dic
.Select(e =>
{
var val = default(T);
try
{
val = (string)e.Value != null ? JsonConvert.DeserializeObject<T>(e.Value) : default(T);
}
catch (Exception ex)
{
LogManager.GetLogger("ASC").Error("RedisCache HashGetAll", ex);
}
return new { Key = (string)e.Name, Value = val };
})
.Where(e => e.Value != null && !e.Value.Equals(default(T)))
.ToDictionary(e => e.Key, e => e.Value);
}
public T HashGet<T>(string key, string field)
{
var value = (string)redis.Database.HashGet(key, field);
try
{
return value != null ? JsonConvert.DeserializeObject<T>(value) : default(T);
}
catch (Exception ex)
{
LogManager.GetLogger("ASC").Error("RedisCache HashGet", ex);
return default(T);
}
}
public void HashSet<T>(string key, string field, T value)
{
if (value != null)
{
redis.Database.HashSet(key, field, JsonConvert.SerializeObject(value));
}
else
{
redis.Database.HashDelete(key, field);
}
}
public void Publish<T>(T obj, CacheNotifyAction action)
{
redis.Publish("asc:channel:" + typeof(T).FullName, new RedisCachePubSubItem<T>() { CacheId = CacheId, Object = obj, Action = action });
ConcurrentBag<Action<object, CacheNotifyAction>> onchange;
actions.TryGetValue(typeof(T), out onchange);
if (onchange != null)
{
onchange.ToArray().ForEach(r=> r(obj, action));
}
}
public void Subscribe<T>(Action<T, CacheNotifyAction> onchange)
{
redis.Subscribe<RedisCachePubSubItem<T>>("asc:channel:" + typeof(T).FullName, (i) =>
{
if (i.CacheId != CacheId)
{
onchange(i.Object, i.Action);
}
});
if (onchange != null)
{
Action<object, CacheNotifyAction> action = (o, a) => onchange((T)o, a);
actions.AddOrUpdate(typeof(T),
new ConcurrentBag<Action<object, CacheNotifyAction>> { action },
(type, bag) =>
{
bag.Add(action);
return bag;
});
}
else
{
ConcurrentBag<Action<object, CacheNotifyAction>> removed;
actions.TryRemove(typeof(T), out removed);
}
}
[Serializable]
class RedisCachePubSubItem<T>
{
public string CacheId { get; set; }
public T Object { get; set; }
public CacheNotifyAction Action { get; set; }
}
class Serializer : ISerializer
{
private readonly Encoding enc = Encoding.UTF8;
public byte[] Serialize(object item)
{
try
{
var s = JsonConvert.SerializeObject(item);
return enc.GetBytes(s);
}
catch (Exception e)
{
LogManager.GetLogger("ASC").Error("Redis Serialize", e);
throw;
}
}
public object Deserialize(byte[] obj)
{
try
{
var resolver = new ContractResolver();
var settings = new JsonSerializerSettings { ContractResolver = resolver };
var s = enc.GetString(obj);
return JsonConvert.DeserializeObject(s, typeof(object), settings);
}
catch (Exception e)
{
LogManager.GetLogger("ASC").Error("Redis Deserialize", e);
throw;
}
}
public T Deserialize<T>(byte[] obj)
{
try
{
var resolver = new ContractResolver();
var settings = new JsonSerializerSettings { ContractResolver = resolver };
var s = enc.GetString(obj);
return JsonConvert.DeserializeObject<T>(s, settings);
}
catch (Exception e)
{
LogManager.GetLogger("ASC").Error("Redis Deserialize<T>", e);
throw;
}
}
public async Task<byte[]> SerializeAsync(object item)
{
return await Task.Factory.StartNew(() => Serialize(item));
}
public Task<object> DeserializeAsync(byte[] obj)
{
return Task.Factory.StartNew(() => Deserialize(obj));
}
public Task<T> DeserializeAsync<T>(byte[] obj)
{
return Task.Factory.StartNew(() => Deserialize<T>(obj));
}
class ContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
var hasPrivateSetter = property.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
}
}
return prop;
}
}
}
}
}

View File

@ -40,14 +40,22 @@ namespace ASC.Common.Threading
private readonly string key;
private readonly ICache cache;
private readonly ICacheNotify notify;
private static readonly ICacheNotify<DistributedTaskCancelation> notify;
private readonly TaskScheduler scheduler;
private static readonly ConcurrentDictionary<string, CancellationTokenSource> cancelations = new ConcurrentDictionary<string, CancellationTokenSource>();
static DistributedTaskQueue()
{
InstanseId = Process.GetCurrentProcess().Id.ToString();
InstanseId = Process.GetCurrentProcess().Id.ToString();
notify = new KafkaCache<DistributedTaskCancelation>();
notify.Subscribe((c) =>
{
if (cancelations.TryGetValue(c.Id, out var s))
{
s.Cancel();
}
}, CacheNotifyAction.Remove);
}
@ -65,19 +73,9 @@ namespace ASC.Common.Threading
key = name + GetType().Name;
cache = AscCache.Default;
notify = AscCache.Notify;
scheduler = maxThreadsCount <= 0
? TaskScheduler.Default
: new LimitedConcurrencyLevelTaskScheduler(maxThreadsCount);
notify.Subscribe<DistributedTaskCancelation>((c, a) =>
{
CancellationTokenSource s;
if (cancelations.TryGetValue(c.Id, out s))
{
s.Cancel();
}
});
}
@ -113,7 +111,7 @@ namespace ASC.Common.Threading
public void CancelTask(string id)
{
notify.Publish(new DistributedTaskCancelation(id), CacheNotifyAction.Remove);
notify.Publish(new DistributedTaskCancelation() { Id = id }, CacheNotifyAction.Remove);
}
public IEnumerable<DistributedTask> GetTasks()
@ -176,17 +174,5 @@ namespace ASC.Common.Threading
{
return (t) => SetTask(t);
}
[Serializable]
class DistributedTaskCancelation
{
public string Id { get; private set; }
public DistributedTaskCancelation(string id)
{
Id = id;
}
}
}
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Common.Threading;
message DistributedTaskCancelation {
string Id = 1;
}

View File

@ -29,6 +29,20 @@
<Compile Remove="Notify\Jabber\IReverseJabberService.cs" />
<Compile Remove="Resources\ResourceGenerator.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="protos\AzRecordCache.proto" />
<None Remove="protos\ConsumerCacheItem.proto" />
<None Remove="protos\GroupCacheItem.proto" />
<None Remove="protos\QuotaCacheItem.proto" />
<None Remove="protos\SettingsCacheItem.proto" />
<None Remove="protos\SubscriptionMethodCache.proto" />
<None Remove="protos\SubscriptionRecord.proto" />
<None Remove="protos\TenantCacheItem.proto" />
<None Remove="protos\TenantSetting.proto" />
<None Remove="protos\UserGroupRefCacheItem.proto" />
<None Remove="protos\UserInfoCacheItem.proto" />
<None Remove="protos\UserPhotoCacheItem.proto" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\ResourceGenerator.cs">
<DesignTime>True</DesignTime>
@ -41,6 +55,10 @@
<PackageReference Include="AWSSDK.Core" Version="3.3.101.9" />
<PackageReference Include="AWSSDK.S3" Version="3.3.101.16" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.100.18" />
<PackageReference Include="Grpc.Tools" Version="1.22.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MailKit" Version="2.1.5.1" />
</ItemGroup>
<ItemGroup>
@ -49,4 +67,19 @@
<Generator>TextTemplatingFileGenerator</Generator>
</None>
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\SettingsCacheItem.proto" />
<Protobuf Include="protos\ConsumerCacheItem.proto" />
<Protobuf Include="protos\TenantCacheItem.proto" />
<Protobuf Include="protos\GroupCacheItem.proto" />
<Protobuf Include="protos\UserGroupRefCacheItem.proto" />
<Protobuf Include="protos\UserInfoCacheItem.proto" />
<Protobuf Include="protos\UserPhotoCacheItem.proto" />
<Protobuf Include="protos\TenantSetting.proto" />
<Protobuf Include="protos\SubscriptionMethodCache.proto" />
<Protobuf Include="protos\SubscriptionRecord.proto" />
<Protobuf Include="protos\TariffCacheItem.proto" />
<Protobuf Include="protos\AzRecordCache.proto" />
<Protobuf Include="protos\QuotaCacheItem.proto" />
</ItemGroup>
</Project>

View File

@ -49,7 +49,7 @@ namespace ASC.Core.Billing
private static readonly TimeSpan STANDALONE_CACHE_EXPIRATION = TimeSpan.FromMinutes(15);
private readonly static ICache cache;
private readonly static ICacheNotify notify;
private readonly static ICacheNotify<TariffCacheItem> notify;
private readonly static bool billingConfigured = false;
private static readonly ILog log = LogManager.GetLogger("ASC");
@ -66,13 +66,13 @@ namespace ASC.Core.Billing
static TariffService()
{
cache = AscCache.Memory;
notify = AscCache.Notify;
notify.Subscribe<TariffCacheItem>((i, a) =>
notify = new KafkaCache<TariffCacheItem>();
notify.Subscribe((i) =>
{
cache.Remove(GetTariffCacheKey(i.TenantId));
cache.Remove(GetBillingUrlCacheKey(i.TenantId));
cache.Remove(GetBillingPaymentCacheKey(i.TenantId, DateTime.MinValue, DateTime.MaxValue)); // clear all payments
});
}, CacheNotifyAction.Remove);
//TODO: Change code of WCF -> not supported in .NET standard/.Net Core
/*try
@ -590,12 +590,5 @@ namespace ASC.Core.Billing
}
}
}
[Serializable]
class TariffCacheItem
{
public int TenantId { get; set; }
}
}
}

View File

@ -34,22 +34,21 @@ namespace ASC.Core.Caching
{
private readonly IAzService service;
private readonly ICache cache;
private readonly ICacheNotify cacheNotify;
private readonly ICacheNotify<AzRecordCache> cacheNotify;
public TimeSpan CacheExpiration { get; set; }
public CachedAzService(IAzService service)
{
if (service == null) throw new ArgumentNullException("service");
this.service = service;
{
this.service = service ?? throw new ArgumentNullException("service");
cache = AscCache.Memory;
CacheExpiration = TimeSpan.FromMinutes(10);
cacheNotify = AscCache.Notify;
cacheNotify.Subscribe<AzRecord>((r, a) => UpdateCache(r.Tenant, r, a == CacheNotifyAction.Remove));
cacheNotify = new KafkaCache<AzRecordCache>();
cacheNotify.Subscribe((r) => UpdateCache(r, true), CacheNotifyAction.Remove);
cacheNotify.Subscribe((r) => UpdateCache(r, false), CacheNotifyAction.InsertOrUpdate);
}
@ -59,7 +58,7 @@ namespace ASC.Core.Caching
var aces = cache.Get<AzRecordStore>(key);
if (aces == null)
{
var records = service.GetAces(tenant, default(DateTime));
var records = service.GetAces(tenant, default);
cache.Insert(key, aces = new AzRecordStore(records), DateTime.UtcNow.Add(CacheExpiration));
}
return aces;
@ -84,7 +83,7 @@ namespace ASC.Core.Caching
return "acl" + tenant.ToString();
}
private void UpdateCache(int tenant, AzRecord r, bool remove)
private void UpdateCache(AzRecord r, bool remove)
{
var aces = cache.Get<AzRecordStore>(GetKey(r.Tenant));
if (aces != null)

View File

@ -38,7 +38,7 @@ namespace ASC.Core.Caching
private const string KEY_QUOTA_ROWS = "quotarows";
private readonly IQuotaService service;
private readonly ICache cache;
private readonly ICacheNotify cacheNotify;
private readonly ICacheNotify<QuotaCacheItem> cacheNotify;
private readonly TrustInterval interval;
@ -50,16 +50,14 @@ namespace ASC.Core.Caching
public CachedQuotaService(IQuotaService service)
{
if (service == null) throw new ArgumentNullException("service");
this.service = service;
{
this.service = service ?? throw new ArgumentNullException("service");
cache = AscCache.Memory;
interval = new TrustInterval();
CacheExpiration = TimeSpan.FromMinutes(10);
cacheNotify = AscCache.Notify;
cacheNotify.Subscribe<QuotaCacheItem>((i, a) =>
cacheNotify = new KafkaCache<QuotaCacheItem>();
cacheNotify.Subscribe((i) =>
{
if (i.Key == KEY_QUOTA_ROWS)
{
@ -69,7 +67,7 @@ namespace ASC.Core.Caching
{
cache.Remove(KEY_QUOTA);
}
});
}, CacheNotifyAction.Any);
}
@ -91,7 +89,7 @@ namespace ASC.Core.Caching
public TenantQuota SaveTenantQuota(TenantQuota quota)
{
var q = service.SaveTenantQuota(quota);
cacheNotify.Publish(new QuotaCacheItem { Key = KEY_QUOTA }, CacheNotifyAction.Remove);
cacheNotify.Publish(new QuotaCacheItem { Key = KEY_QUOTA }, CacheNotifyAction.Any);
return q;
}
@ -104,7 +102,7 @@ namespace ASC.Core.Caching
public void SetTenantQuotaRow(TenantQuotaRow row, bool exchange)
{
service.SetTenantQuotaRow(row, exchange);
cacheNotify.Publish(new QuotaCacheItem { Key = KEY_QUOTA_ROWS }, CacheNotifyAction.InsertOrUpdate);
cacheNotify.Publish(new QuotaCacheItem { Key = KEY_QUOTA_ROWS }, CacheNotifyAction.Any);
}
public IEnumerable<TenantQuotaRow> FindTenantQuotaRows(TenantQuotaRowQuery query)
@ -171,12 +169,5 @@ namespace ASC.Core.Caching
return list.ToList();
}
}
[Serializable]
class QuotaCacheItem
{
public string Key { get; set; }
}
}
}

View File

@ -35,55 +35,53 @@ namespace ASC.Core.Caching
{
private readonly ISubscriptionService service;
private readonly ICache cache;
private readonly ICacheNotify notify;
private readonly ICacheNotify<SubscriptionRecord> notifyRecord;
private readonly ICacheNotify<SubscriptionMethodCache> notifyMethod;
public TimeSpan CacheExpiration
{
get;
set;
}
public TimeSpan CacheExpiration { get; set; }
public CachedSubscriptionService(ISubscriptionService service)
{
if (service == null) throw new ArgumentNullException("service");
this.service = service;
{
this.service = service ?? throw new ArgumentNullException("service");
cache = AscCache.Memory;
notify = AscCache.Notify;
notifyRecord = new KafkaCache<SubscriptionRecord>();
notifyMethod = new KafkaCache<SubscriptionMethodCache>();
CacheExpiration = TimeSpan.FromMinutes(5);
notify.Subscribe<SubscriptionRecord>((s, a) =>
notifyRecord.Subscribe((s) =>
{
var store = GetSubsciptionsStore(s.Tenant, s.SourceId, s.ActionId);
lock (store)
{
store.SaveSubscription(s);
}
}, CacheNotifyAction.InsertOrUpdate);
notifyRecord.Subscribe((s) =>
{
var store = GetSubsciptionsStore(s.Tenant, s.SourceId, s.ActionId);
lock (store)
{
if (a == CacheNotifyAction.InsertOrUpdate)
if (s.ObjectId == null)
{
store.SaveSubscription(s);
store.RemoveSubscriptions();
}
else if (a == CacheNotifyAction.Remove)
else
{
if (s.ObjectId == null)
{
store.RemoveSubscriptions();
}
else
{
store.RemoveSubscriptions(s.ObjectId);
}
store.RemoveSubscriptions(s.ObjectId);
}
}
});
notify.Subscribe<SubscriptionMethod>((m, a) =>
}, CacheNotifyAction.Remove);
notifyMethod.Subscribe((m) =>
{
var store = GetSubsciptionsStore(m.Tenant, m.SourceId, m.ActionId);
lock (store)
{
store.SetSubscriptionMethod(m);
}
});
}, CacheNotifyAction.Any);
}
@ -117,19 +115,19 @@ namespace ASC.Core.Caching
public void SaveSubscription(SubscriptionRecord s)
{
service.SaveSubscription(s);
notify.Publish(s, CacheNotifyAction.InsertOrUpdate);
notifyRecord.Publish(s, CacheNotifyAction.InsertOrUpdate);
}
public void RemoveSubscriptions(int tenant, string sourceId, string actionId)
{
service.RemoveSubscriptions(tenant, sourceId, actionId);
notify.Publish(new SubscriptionRecord { Tenant = tenant, SourceId = sourceId, ActionId = actionId }, CacheNotifyAction.Remove);
notifyRecord.Publish(new SubscriptionRecord { Tenant = tenant, SourceId = sourceId, ActionId = actionId }, CacheNotifyAction.Remove);
}
public void RemoveSubscriptions(int tenant, string sourceId, string actionId, string objectId)
{
service.RemoveSubscriptions(tenant, sourceId, actionId, objectId);
notify.Publish(new SubscriptionRecord { Tenant = tenant, SourceId = sourceId, ActionId = actionId, ObjectId = objectId }, CacheNotifyAction.Remove);
notifyRecord.Publish(new SubscriptionRecord { Tenant = tenant, SourceId = sourceId, ActionId = actionId, ObjectId = objectId }, CacheNotifyAction.Remove);
}
public IEnumerable<SubscriptionMethod> GetSubscriptionMethods(int tenant, string sourceId, string actionId, string recipientId)
@ -144,7 +142,7 @@ namespace ASC.Core.Caching
public void SetSubscriptionMethod(SubscriptionMethod m)
{
service.SetSubscriptionMethod(m);
notify.Publish(m, CacheNotifyAction.Any);
notifyMethod.Publish(m, CacheNotifyAction.Any);
}

View File

@ -24,11 +24,11 @@
*/
using ASC.Common.Caching;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using System;
using System.Collections.Generic;
using ASC.Common.Caching;
using ASC.Core.Tenants;
namespace ASC.Core.Caching
{
@ -36,42 +36,37 @@ namespace ASC.Core.Caching
{
private const string KEY = "tenants";
private readonly ITenantService service;
private readonly ICache cache;
private readonly ICacheNotify cacheNotify;
private readonly ICache cache;
private readonly ICacheNotify<TenantSetting> cacheNotifySettings;
private readonly ICacheNotify<TenantCacheItem> cacheNotifyItem;
public TimeSpan CacheExpiration
{
get;
set;
}
public TimeSpan CacheExpiration { get; set; }
public TimeSpan SettingsExpiration
{
get;
set;
}
public TimeSpan SettingsExpiration { get; set; }
public CachedTenantService(ITenantService service)
{
if (service == null) throw new ArgumentNullException("service");
this.service = service;
{
this.service = service ?? throw new ArgumentNullException("service");
cache = AscCache.Memory;
CacheExpiration = TimeSpan.FromMinutes(2);
SettingsExpiration = TimeSpan.FromMinutes(2);
cacheNotify = AscCache.Notify;
cacheNotify.Subscribe<Tenant>((t, a) =>
SettingsExpiration = TimeSpan.FromMinutes(2);
cacheNotifyItem = new KafkaCache<TenantCacheItem>();
cacheNotifySettings = new KafkaCache<TenantSetting>();
cacheNotifyItem.Subscribe((t) =>
{
var tenants = GetTenantStore();
tenants.Remove(t.TenantId);
tenants.Clear();
});
cacheNotify.Subscribe<TenantSetting>((s, a) =>
}, CacheNotifyAction.InsertOrUpdate);
cacheNotifySettings.Subscribe((s) =>
{
cache.Remove(s.Key);
});
}, CacheNotifyAction.Remove);
}
@ -138,14 +133,14 @@ namespace ASC.Core.Caching
public Tenant SaveTenant(Tenant tenant)
{
tenant = service.SaveTenant(tenant);
cacheNotify.Publish(new Tenant() { TenantId = tenant.TenantId }, CacheNotifyAction.InsertOrUpdate);
cacheNotifyItem.Publish(new TenantCacheItem() { TenantId = tenant.TenantId }, CacheNotifyAction.InsertOrUpdate);
return tenant;
}
public void RemoveTenant(int id, bool auto = false)
{
service.RemoveTenant(id, auto);
cacheNotify.Publish(new Tenant() { TenantId = id }, CacheNotifyAction.InsertOrUpdate);
cacheNotifyItem.Publish(new TenantCacheItem() { TenantId = id }, CacheNotifyAction.InsertOrUpdate);
}
public IEnumerable<TenantVersion> GetTenantVersions()
@ -169,7 +164,7 @@ namespace ASC.Core.Caching
{
service.SetTenantSettings(tenant, key, data);
var cacheKey = string.Format("settings/{0}/{1}", tenant, key);
cacheNotify.Publish(new TenantSetting { Key = cacheKey }, CacheNotifyAction.Any);
cacheNotifySettings.Publish(new TenantSetting { Key = cacheKey }, CacheNotifyAction.Remove);
}
private TenantStore GetTenantStore()
@ -183,13 +178,6 @@ namespace ASC.Core.Caching
}
[Serializable]
class TenantSetting
{
public string Key { get; set; }
}
class TenantStore
{
private readonly Dictionary<int, Tenant> byId = new Dictionary<int, Tenant>();

View File

@ -42,7 +42,11 @@ namespace ASC.Core.Caching
private readonly IUserService service;
private readonly ICache cache;
private readonly ICacheNotify cacheNotify;
private readonly ICacheNotify<UserGroupRefCacheItem> cacheUserGroupRefItem;
private readonly ICacheNotify<UserPhotoCacheItem> cacheUserPhotoItem;
private readonly ICacheNotify<UserInfoCacheItem> cacheUserInfoItem;
private readonly ICacheNotify<GroupCacheItem> cacheGroupCacheItem;
private readonly TrustInterval trustInterval;
private int getchanges;
@ -55,22 +59,26 @@ namespace ASC.Core.Caching
public CachedUserService(IUserService service)
{
if (service == null) throw new ArgumentNullException("service");
this.service = service;
{
this.service = service ?? throw new ArgumentNullException("service");
cache = AscCache.Memory;
trustInterval = new TrustInterval();
CacheExpiration = TimeSpan.FromMinutes(20);
DbExpiration = TimeSpan.FromMinutes(1);
PhotoExpiration = TimeSpan.FromMinutes(10);
cacheUserInfoItem = new KafkaCache<UserInfoCacheItem>();
cacheUserPhotoItem = new KafkaCache<UserPhotoCacheItem>();
cacheGroupCacheItem = new KafkaCache<GroupCacheItem>();
cacheUserGroupRefItem = new KafkaCache<UserGroupRefCacheItem>();
cacheUserInfoItem.Subscribe((u) => InvalidateCache(u), CacheNotifyAction.Any);
cacheUserPhotoItem.Subscribe((p) => cache.Remove(p.Key), CacheNotifyAction.Remove);
cacheGroupCacheItem.Subscribe((g) => InvalidateCache(), CacheNotifyAction.InsertOrUpdate);
cacheNotify = AscCache.Notify;
cacheNotify.Subscribe<UserInfo>((u, a) => InvalidateCache(u));
cacheNotify.Subscribe<UserPhoto>((p, a) => cache.Remove(p.Key));
cacheNotify.Subscribe<Group>((g, a) => InvalidateCache());
cacheNotify.Subscribe<UserGroupRef>((r, a) => UpdateUserGroupRefCache(r, a == CacheNotifyAction.Remove));
cacheUserGroupRefItem.Subscribe((r) => UpdateUserGroupRefCache(r, true), CacheNotifyAction.Remove);
cacheUserGroupRefItem.Subscribe((r) => UpdateUserGroupRefCache(r, false), CacheNotifyAction.InsertOrUpdate);
}
@ -133,14 +141,14 @@ namespace ASC.Core.Caching
public UserInfo SaveUser(int tenant, UserInfo user)
{
user = service.SaveUser(tenant, user);
cacheNotify.Publish(user, CacheNotifyAction.InsertOrUpdate);
cacheUserInfoItem.Publish(new UserInfoCacheItem() { ID = user.ID.ToString(), Tenant = tenant }, CacheNotifyAction.InsertOrUpdate);
return user;
}
public void RemoveUser(int tenant, Guid id)
{
service.RemoveUser(tenant, id);
cacheNotify.Publish(new UserInfo { Tenant = tenant, ID = id }, CacheNotifyAction.Remove);
cacheUserInfoItem.Publish(new UserInfoCacheItem { Tenant = tenant, ID = id.ToString() }, CacheNotifyAction.Any);
}
public byte[] GetUserPhoto(int tenant, Guid id)
@ -157,7 +165,7 @@ namespace ASC.Core.Caching
public void SetUserPhoto(int tenant, Guid id, byte[] photo)
{
service.SetUserPhoto(tenant, id, photo);
cacheNotify.Publish(new UserPhoto { Key = GetUserPhotoCacheKey(tenant, id) }, CacheNotifyAction.Remove);
cacheUserPhotoItem.Publish(new UserPhotoCacheItem { Key = GetUserPhotoCacheKey(tenant, id) }, CacheNotifyAction.Remove);
}
public string GetUserPassword(int tenant, Guid id)
@ -194,14 +202,14 @@ namespace ASC.Core.Caching
public Group SaveGroup(int tenant, Group group)
{
group = service.SaveGroup(tenant, group);
cacheNotify.Publish(group, CacheNotifyAction.InsertOrUpdate);
cacheGroupCacheItem.Publish(new GroupCacheItem { ID = group.Id.ToString() }, CacheNotifyAction.InsertOrUpdate);
return group;
}
public void RemoveGroup(int tenant, Guid id)
{
service.RemoveGroup(tenant, id);
cacheNotify.Publish(new Group { Id = id }, CacheNotifyAction.Remove);
cacheGroupCacheItem.Publish(new GroupCacheItem { ID = id.ToString() }, CacheNotifyAction.Remove);
}
@ -225,7 +233,7 @@ namespace ASC.Core.Caching
public UserGroupRef SaveUserGroupRef(int tenant, UserGroupRef r)
{
r = service.SaveUserGroupRef(tenant, r);
cacheNotify.Publish(r, CacheNotifyAction.InsertOrUpdate);
cacheUserGroupRefItem.Publish(r, CacheNotifyAction.InsertOrUpdate);
return r;
}
@ -234,7 +242,7 @@ namespace ASC.Core.Caching
service.RemoveUserGroupRef(tenant, userId, groupId, refType);
var r = new UserGroupRef(userId, groupId, refType) { Tenant = tenant };
cacheNotify.Publish(r, CacheNotifyAction.Remove);
cacheUserGroupRefItem.Publish(r, CacheNotifyAction.Remove);
}
public void InvalidateCache()
@ -242,11 +250,11 @@ namespace ASC.Core.Caching
InvalidateCache(null);
}
private void InvalidateCache(UserInfo userInfo)
private void InvalidateCache(UserInfoCacheItem userInfo)
{
if (CoreContext.Configuration.Personal && userInfo != null)
{
var key = GetUserCacheKeyForPersonal(userInfo.Tenant, userInfo.ID);
var key = GetUserCacheKeyForPersonal(userInfo.Tenant, Guid.Parse(userInfo.ID));
cache.Remove(key);
}

View File

@ -38,7 +38,7 @@ namespace ASC.Core.Common.Configuration
{
public class Consumer : IDictionary<string, string>
{
private static ICacheNotify Cache = AscCache.Notify;
private static readonly ICacheNotify<ConsumerCacheItem> Cache = new KafkaCache<ConsumerCacheItem>();
public bool CanSet { get; private set; }
@ -143,7 +143,7 @@ namespace ASC.Core.Common.Configuration
this[providerProp.Key] = null;
}
Cache.Publish(this, CacheNotifyAction.Remove);
Cache.Publish(new ConsumerCacheItem() { Name = this.Name }, CacheNotifyAction.Remove);
}
public bool Contains(KeyValuePair<string, string> item)

View File

@ -65,8 +65,44 @@ namespace ASC.Core
ActionId = actionId;
Reaction = reaction;
ObjectId = objectId;
}
}
public static implicit operator AzRecord(AzRecordCache cache)
{
var result = new AzRecord();
if (Guid.TryParse(cache.SubjectId, out var subjectId))
{
result.SubjectId = subjectId;
}
if (Guid.TryParse(cache.ActionId, out var actionId))
{
result.ActionId = actionId;
}
result.ObjectId = cache.ObjectId;
if (Enum.TryParse<AceType>(cache.Reaction, out var reaction))
{
result.Reaction = reaction;
}
return result;
}
public static implicit operator AzRecordCache(AzRecord cache)
{
return new AzRecordCache
{
SubjectId = cache.SubjectId.ToString(),
ActionId = cache.ActionId.ToString(),
ObjectId = cache.ObjectId,
Reaction = cache.Reaction.ToString(),
Tenant = cache.Tenant
};
}
public override bool Equals(object obj)
{

View File

@ -31,34 +31,37 @@ namespace ASC.Core
[Serializable]
public class SubscriptionMethod
{
public int Tenant
{
get;
set;
}
public int Tenant { get; set; }
public string SourceId
{
get;
set;
}
public string SourceId { get; set; }
public string ActionId
{
get;
set;
}
public string ActionId { get; set; }
public string RecipientId
{
get;
set;
}
public string RecipientId { get; set; }
public string[] Methods
public string[] Methods { get; set; }
public static implicit operator SubscriptionMethod(SubscriptionMethodCache cache)
{
get;
set;
return new SubscriptionMethod()
{
Tenant = cache.Tenant,
SourceId = cache.SourceId,
ActionId = cache.ActionId,
RecipientId= cache.RecipientId
};
}
public static implicit operator SubscriptionMethodCache(SubscriptionMethod cache)
{
return new SubscriptionMethodCache
{
Tenant = cache.Tenant,
SourceId = cache.SourceId,
ActionId = cache.ActionId,
RecipientId = cache.RecipientId
};
}
}
}

View File

@ -1,70 +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;
namespace ASC.Core
{
[Serializable]
public class SubscriptionRecord
{
public int Tenant
{
get;
set;
}
public string SourceId
{
get;
set;
}
public string ActionId
{
get;
set;
}
public string RecipientId
{
get;
set;
}
public string ObjectId
{
get;
set;
}
public bool Subscribed
{
get;
set;
}
}
}

View File

@ -26,47 +26,24 @@
using System;
using System.Diagnostics;
using ASC.Core.Caching;
namespace ASC.Core
{
[DebuggerDisplay("{UserId} - {GroupId}")]
public class UserGroupRef
{
public Guid UserId
{
get;
set;
}
public Guid UserId { get; set; }
public Guid GroupId
{
get;
set;
}
public Guid GroupId { get; set; }
public bool Removed
{
get;
set;
}
public bool Removed { get; set; }
public DateTime LastModified
{
get;
set;
}
public DateTime LastModified { get; set; }
public UserGroupRefType RefType
{
get;
set;
}
public UserGroupRefType RefType { get; set; }
public int Tenant
{
get;
set;
}
public int Tenant { get; set; }
public UserGroupRef()
@ -99,6 +76,45 @@ namespace ASC.Core
{
var r = obj as UserGroupRef;
return r != null && r.Tenant == Tenant && r.UserId == UserId && r.GroupId == GroupId && r.RefType == RefType;
}
public static implicit operator UserGroupRef(UserGroupRefCacheItem cache)
{
var result = new UserGroupRef();
if (Guid.TryParse(cache.UserId, out var userId))
{
result.UserId = userId;
}
if (Guid.TryParse(cache.GroupId, out var groupId))
{
result.GroupId = groupId;
}
if (Enum.TryParse<UserGroupRefType>(cache.RefType, out var refType))
{
result.RefType = refType;
}
result.Tenant = cache.Tenant;
result.LastModified = new DateTime(cache.LastModified);
result.Removed = cache.Removed;
return result;
}
public static implicit operator UserGroupRefCacheItem(UserGroupRef cache)
{
return new UserGroupRefCacheItem
{
GroupId = cache.GroupId.ToString(),
UserId = cache.UserId.ToString(),
RefType = cache.RefType.ToString(),
LastModified = cache.LastModified.Ticks,
Removed = cache.Removed,
Tenant = cache.Tenant
};
}
}
}

View File

@ -32,6 +32,7 @@ using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Json;
using System.Text;
using ASC.Common.Caching;
using ASC.Common.Data;
using ASC.Common.Data.Sql;
@ -45,7 +46,7 @@ namespace ASC.Core.Data
private static readonly ILog log = LogManager.GetLogger("ASC");
private static readonly ICache cache = AscCache.Memory;
private static readonly ICacheNotify notify = AscCache.Notify;
private static readonly ICacheNotify<SettingsCacheItem> notify;
private readonly TimeSpan expirationTimeout = TimeSpan.FromMinutes(5);
private readonly IDictionary<Type, DataContractJsonSerializer> jsonSerializers = new Dictionary<Type, DataContractJsonSerializer>();
@ -58,8 +59,9 @@ namespace ASC.Core.Data
}
static DbSettingsManager()
{
notify.Subscribe<SettingsCacheItem>((i, a) => cache.Remove(i.Key));
{
notify = new KafkaCache<SettingsCacheItem>();
notify.Subscribe((i) => cache.Remove(i.Key), CacheNotifyAction.Remove);
}
@ -211,12 +213,5 @@ namespace ASC.Core.Data
return jsonSerializers[type];
}
}
[Serializable]
class SettingsCacheItem
{
public string Key { get; set; }
}
}
}

View File

@ -0,0 +1,15 @@
syntax = "proto3";
package ASC.Core;
message AzRecordCache {
string SubjectId = 1;
string ActionId = 2;
string ObjectId = 3;
string Reaction = 4;
int32 Tenant = 5;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Common.Configuration;
message ConsumerCacheItem {
string Name = 1;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Caching;
message GroupCacheItem {
string ID = 1;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Caching;
message QuotaCacheItem {
string Key = 1;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Data;
message SettingsCacheItem {
string Key = 1;
}

View File

@ -0,0 +1,15 @@
syntax = "proto3";
package ASC.Core;
message SubscriptionMethodCache {
string RecipientId = 1;
string ActionId = 2;
string SourceId = 3;
repeated string Methods = 4;
int32 Tenant = 5;
}

View File

@ -0,0 +1,17 @@
syntax = "proto3";
package ASC.Core;
message SubscriptionRecord {
string RecipientId = 1;
string ActionId = 2;
string ObjectId = 3;
string SourceId = 4;
bool Subscribed = 5;
int32 Tenant = 6;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Billing;
message TariffCacheItem {
int32 TenantId = 1;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Caching;
message TenantCacheItem {
int32 TenantId = 1;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Caching;
message TenantSetting {
string Key = 1;
}

View File

@ -0,0 +1,12 @@
syntax = "proto3";
package ASC.Core.Caching;
message UserGroupRefCacheItem {
string UserId = 1;
string GroupId = 2;
bool Removed = 3;
string RefType = 4;
int64 LastModified = 5;
int32 Tenant = 6;
}

View File

@ -0,0 +1,8 @@
syntax = "proto3";
package ASC.Core.Caching;
message UserInfoCacheItem {
string ID = 1;
int32 Tenant = 2;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.Core.Caching;
message UserPhotoCacheItem {
string Key = 1;
}

View File

@ -21,6 +21,10 @@
<PackageReference Include="Google.Apis.Core" Version="1.40.2" />
<PackageReference Include="Google.Apis.Storage.v1" Version="1.40.2.1635" />
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.4.0-beta02" />
<PackageReference Include="Grpc.Tools" Version="1.22.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="openstack.net" Version="1.7.8" />
<PackageReference Include="Rackspace" Version="0.2.0" />
</ItemGroup>
@ -30,4 +34,8 @@
<ProjectReference Include="..\ASC.Core.Common\ASC.Core.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\DataStoreCacheItem.proto" />
</ItemGroup>
</Project>

View File

@ -48,26 +48,25 @@ namespace ASC.Data.Storage.Configuration
{
return new T();
}
private static readonly ICacheNotify<ConsumerCacheItem> Cache;
static BaseStorageSettings()
{
AscCache.Notify.Subscribe<Consumer>((i, a) =>
{
Cache = new KafkaCache<ConsumerCacheItem>();
Cache.Subscribe((i) =>
{
if (a == CacheNotifyAction.Remove)
var settings = StorageSettings.Load();
if (i.Name == settings.Module)
{
var settings = StorageSettings.Load();
if (i.Name == settings.Module)
{
settings.Clear();
}
var cdnSettings = CdnStorageSettings.Load();
if (i.Name == cdnSettings.Module)
{
cdnSettings.Clear();
}
settings.Clear();
}
});
var cdnSettings = CdnStorageSettings.Load();
if (i.Name == cdnSettings.Module)
{
cdnSettings.Clear();
}
}, CacheNotifyAction.Remove);
}
public override bool Save()

View File

@ -36,39 +36,27 @@ namespace ASC.Data.Storage
public static void Put(IDataStore store, string tenantId, string module)
{
Cache.Insert(DataStoreCacheItem.Create(tenantId, module).MakeCacheKey(), store, DateTime.MaxValue);
Cache.Insert(DataStoreCacheItemExtenstion.MakeCacheKey(tenantId, module), store, DateTime.MaxValue);
}
public static IDataStore Get(string tenantId, string module)
{
return Cache.Get<IDataStore>(DataStoreCacheItem.Create(tenantId, module).MakeCacheKey());
return Cache.Get<IDataStore>(DataStoreCacheItemExtenstion.MakeCacheKey(tenantId, module));
}
public static void Remove(string tenantId, string module)
{
Cache.Remove(DataStoreCacheItem.Create(tenantId, module).MakeCacheKey());
Cache.Remove(DataStoreCacheItemExtenstion.MakeCacheKey(tenantId, module));
}
}
public class DataStoreCacheItem
public static class DataStoreCacheItemExtenstion
{
public string TenantId { get; set; }
public string Module { get; set; }
public static DataStoreCacheItem Create(string tenantId, string module)
internal static string MakeCacheKey(string tenantId, string module)
{
return new DataStoreCacheItem
{
TenantId = tenantId,
Module = module
};
}
internal string MakeCacheKey()
{
return string.Format("{0}:\\{1}", TenantId, Module);
return string.Format("{0}:\\{1}", tenantId, module);
}
}
}

View File

@ -42,12 +42,12 @@ namespace ASC.Data.Storage
public static class StorageFactory
{
private const string DefaultTenantName = "default";
private static readonly ICacheNotify Cache;
private static readonly ICacheNotify<DataStoreCacheItem> Cache;
static StorageFactory()
{
Cache = AscCache.Notify;
Cache.Subscribe<DataStoreCacheItem>((r, act) => DataStoreCache.Remove(r.TenantId, r.Module));
Cache = new KafkaCache<DataStoreCacheItem>();
Cache.Subscribe((r) => DataStoreCache.Remove(r.TenantId, r.Module), CacheNotifyAction.Remove);
}
public static IDataStore GetStorage(string tenant, string module)
@ -205,7 +205,7 @@ namespace ASC.Data.Storage
var path = TennantPath.CreatePath(tenantId);
foreach (var module in GetModuleList("", true))
{
Cache.Publish(DataStoreCacheItem.Create(path, module), CacheNotifyAction.Remove);
Cache.Publish(new DataStoreCacheItem() { TenantId = path, Module = module }, CacheNotifyAction.Remove);
}
}

View File

@ -0,0 +1,9 @@
syntax = "proto3";
package ASC.Core;
message DataStoreCacheItem {
string Module = 1;
string TenantId = 2;
}

View File

@ -9,6 +9,10 @@
<ItemGroup>
<PackageReference Include="DotNetOpenAuth.Ultimate" Version="4.3.4.13329" />
<PackageReference Include="Grpc.Tools" Version="1.22.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="JWT" Version="1.3.4" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
</ItemGroup>
@ -18,4 +22,8 @@
<ProjectReference Include="..\ASC.Core.Common\ASC.Core.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\LinkerCacheItem.proto" />
</ItemGroup>
</Project>

View File

@ -38,13 +38,13 @@ namespace ASC.FederatedLogin
public class AccountLinker
{
private static readonly ICache cache = AscCache.Memory;
private static readonly ICacheNotify notify = AscCache.Notify;
private static readonly ICacheNotify<LinkerCacheItem> notify = new KafkaCache<LinkerCacheItem>();
private readonly string dbid;
static AccountLinker()
{
notify.Subscribe<LinkerCacheItem>((c, a) => cache.Remove(c.Obj));
notify.Subscribe((c) => cache.Remove(c.Obj), CacheNotifyAction.Remove);
}
@ -143,12 +143,5 @@ namespace ASC.FederatedLogin
}
notify.Publish(new LinkerCacheItem { Obj = obj }, CacheNotifyAction.Remove);
}
[Serializable]
class LinkerCacheItem
{
public string Obj { get; set; }
}
}
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.FederatedLogin;
message LinkerCacheItem {
string Obj = 1;
}

View File

@ -7,9 +7,18 @@
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.Tools" Version="1.22.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\ASC.Core.Common\ASC.Core.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\IPRestrictionItem.proto" />
</ItemGroup>
</Project>

View File

@ -34,13 +34,14 @@ namespace ASC.IPSecurity
{
private const string cacheKey = "iprestrictions";
private static readonly ICache cache = AscCache.Memory;
private static readonly ICacheNotify notify = AscCache.Notify;
private static readonly ICacheNotify<IPRestrictionItem> notify;
private static readonly TimeSpan timeout = TimeSpan.FromMinutes(5);
static IPRestrictionsService()
{
notify.Subscribe<IPRestriction>((r, a) => cache.Remove(GetCacheKey(r.TenantId)));
notify = new KafkaCache<IPRestrictionItem>();
notify.Subscribe((r) => cache.Remove(GetCacheKey(r.TenantId)), CacheNotifyAction.Any);
}
@ -58,7 +59,7 @@ namespace ASC.IPSecurity
public static IEnumerable<string> Save(IEnumerable<string> ips, int tenant)
{
var restrictions = IPRestrictionsRepository.Save(ips, tenant);
notify.Publish(new IPRestriction { TenantId = tenant }, CacheNotifyAction.InsertOrUpdate);
notify.Publish(new IPRestrictionItem { TenantId = tenant }, CacheNotifyAction.InsertOrUpdate);
return restrictions;
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.IPSecurity;
message IPRestrictionItem {
int32 TenantId = 1;
}

View File

@ -14,6 +14,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.Tools" Version="1.22.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Twilio" Version="5.6.0" />
</ItemGroup>
@ -26,4 +30,7 @@
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\CachedVoipItem.proto" />
</ItemGroup>
</Project>

View File

@ -35,15 +35,16 @@ namespace ASC.VoipService.Dao
public class CachedVoipDao : VoipDao
{
private static readonly ICache cache = AscCache.Memory;
private static readonly ICacheNotify notify = AscCache.Notify;
private static readonly ICacheNotify<CachedVoipItem> notify;
private static readonly TimeSpan timeout = TimeSpan.FromDays(1);
static CachedVoipDao()
{
try
{
notify.Subscribe<CachedVoipItem>((c, a) => ResetCache(c.Tenant));
{
notify = new KafkaCache<CachedVoipItem>();
notify.Subscribe((c) => ResetCache(c.Tenant), CacheNotifyAction.Any);
}
catch (Exception)
{
@ -59,14 +60,14 @@ namespace ASC.VoipService.Dao
public override VoipPhone SaveOrUpdateNumber(VoipPhone phone)
{
var result = base.SaveOrUpdateNumber(phone);
notify.Publish(new CachedVoipItem { Tenant = TenantID }, CacheNotifyAction.InsertOrUpdate);
notify.Publish(new CachedVoipItem { Tenant = TenantID }, CacheNotifyAction.Any);
return result;
}
public override void DeleteNumber(string phoneId = "")
{
base.DeleteNumber(phoneId);
notify.Publish(new CachedVoipItem { Tenant = TenantID }, CacheNotifyAction.Remove);
notify.Publish(new CachedVoipItem { Tenant = TenantID }, CacheNotifyAction.Any);
}
public override IEnumerable<VoipPhone> GetNumbers(params object[] ids)
@ -91,11 +92,5 @@ namespace ASC.VoipService.Dao
{
return "voip" + tenant.ToString(CultureInfo.InvariantCulture);
}
[Serializable]
class CachedVoipItem
{
public int Tenant { get; set; }
}
}
}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package ASC.VoipService.Dao;
message CachedVoipItem {
int32 Tenant = 1;
}

View File

@ -13,27 +13,35 @@ const SectionBodyContent = (props) => {
return "user";
};
const avatarLabel = profile && profile.id ? "Edit photo" : "Add photo";
const onEditAvatar = () => {};
return (
<div>
<Avatar
size="max"
role={getUserRole(profile)}
source={profile.avatarBig}
userName={profile.userName}
editing={true}
editLabel={avatarLabel}
editAction={onEditAvatar}
/>
{
profile
? <Avatar
size="max"
role={getUserRole(profile)}
source={profile.avatar}
userName={profile.userName}
editing={true}
editLabel={"Edit photo"}
editAction={onEditAvatar}
/>
: <Avatar
size="max"
role={props.userType == "guest" ? "guest" : "user"}
editing={true}
editLabel={"Add photo"}
editAction={onEditAvatar}
/>
}
</div>
);
};
SectionBodyContent.propTypes = {
profile: PropTypes.object.isRequired
profile: PropTypes.object
};
export default SectionBodyContent;

View File

@ -14,18 +14,23 @@ const textStyle = {
const SectionHeaderContent = (props) => {
const {profile, history} = props;
const headerText = profile && profile.userName ? profile.userName : "New employee";
const headerText = profile
? profile.userName
: props.userType == "guest"
? "New guest"
: "New employee";
return (
<div style={wrapperStyle}>
<IconButton iconName={'ProjectDocumentsUpIcon'} size="16" onClick={history.goBack}/>
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={history.goBack}/>
<Text.ContentHeader style={textStyle}>{headerText}</Text.ContentHeader>
</div>
);
};
SectionHeaderContent.propTypes = {
profile: PropTypes.object.isRequired,
profile: PropTypes.object,
history: PropTypes.object.isRequired
};

View File

@ -7,33 +7,52 @@ import { ArticleHeaderContent, ArticleBodyContent } from '../../Article';
import { SectionHeaderContent, SectionBodyContent } from './Section';
import { getUser } from '../../../utils/api';
const getUrlParam = (inputString, paramName, defaultValue) => {
var regex = new RegExp(`${paramName}=([^&]+)`);
var res = regex.exec(inputString);
return res && res[1] ? res[1] : defaultValue;
}
const ProfileAction = (props) => {
const { auth, history, match } = props;
const { auth, history, match, location } = props;
const { userId } = match.params;
const [profile, setProfile] = useState(props.profile);
const [isLoaded, setLoaded] = useState(props.isLoaded);
const isEdit = location.pathname.split("/").includes("edit");
const userType = getUrlParam(location.search, "type", "user");
useEffect(() => {
if(userId === "@self") {
setProfile(auth.user);
if (isEdit) {
if (userId === "@self") {
setProfile(auth.user);
setLoaded(true);
} else {
getUser(userId)
.then((res) => {
if (res.data.error)
throw (res.data.error);
setProfile(res.data.response);
setLoaded(true);
})
.catch(error => {
console.error(error);
});
}
} else {
setProfile(null);
setLoaded(true);
}
else {
getUser(userId)
.then((res) => {
setProfile(res.data.response);
setLoaded(true);
});
}
}, []);
return (
isLoaded ?
<PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent profile={profile} history={history} />}
sectionBodyContent={<SectionBodyContent profile={profile} history={history} /> }
sectionHeaderContent={<SectionHeaderContent profile={profile} history={history} userType={userType} />}
sectionBodyContent={<SectionBodyContent profile={profile} userType={userType} />}
/>
: <PageLayout
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}

View File

@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9913 7.00235L5.7581 7.00934L9.90483 2.58703C10.1145 2.37637 10.2295 2.09099 10.2295 1.79147C10.2295 1.49195 10.1145 1.20857 9.90483 0.997406L9.2385 0.327144C9.02897 0.116481 8.74977 0 8.45202 0C8.15411 0 7.87474 0.115649 7.66521 0.326312L0.32456 7.70602C0.114203 7.91751 -0.000823092 8.19923 4.43411e-06 8.49892C-0.000823092 8.80027 0.114203 9.08215 0.32456 9.29332L7.66521 16.6737C7.87474 16.8842 8.15395 17 8.45202 17C8.74977 17 9.02897 16.884 9.2385 16.6737L9.90483 16.0034C10.1145 15.7931 10.2295 15.5122 10.2295 15.2127C10.2295 14.9133 10.1145 14.6473 9.90483 14.4368L5.7581 10.0027H16.0548C16.6683 10.0027 17 9.56605 17 8.94953V8.00155C17 7.38503 16.6048 7.00235 15.9913 7.00235Z" fill="#A3A9AE"/>
</svg>

After

Width:  |  Height:  |  Size: 860 B

View File

@ -142,6 +142,8 @@ import OrigCheckIcon from './check.react.svg';
import OrigDangerIcon from './danger.react.svg';
import OrigInfoIcon from './info.react.svg';
import OrigArrowPathIcon from './arrow.path.react.svg'
export const AZSortingIcon = createStyledIcon(
OrigAZSortingIcon,
'AZSortingIcon'
@ -689,4 +691,8 @@ export const NavLogoIcon = createStyledIcon(
export const NavLogoOpenedIcon = createStyledIcon(
OrigNavLogoOpenedIcon,
'NavLogoOpenedIcon'
);
export const ArrowPathIcon = createStyledIcon(
OrigArrowPathIcon,
'ArrowPathIcon'
);

View File

@ -0,0 +1,67 @@
import React from 'react'
import styled from 'styled-components'
import { Icons } from '../icons'
import { Text } from '../text'
import PropTypes from 'prop-types'
const StyledContent = styled.div`
color: #333;
display: ${props => props.isOpen ? 'block' : 'none'};
padding-top: 9px;
`;
const Arrow = styled(Icons.ArrowContentIcon)`
margin-right: 9px;
margin-bottom: 5px;
transform: ${props => props.isOpen && 'rotate(180deg)'};
`;
const StyledSpan = styled.span`
cursor: pointer;
user-select: none;
`;
class ToggleContent extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: this.props.isOpen
};
}
toggleContent = (isOpen) => this.setState({ isOpen: isOpen });
componentDidUpdate(prevProps) {
if (this.props.isOpen !== prevProps.isOpen) {
this.setState({ isOpen: this.props.isOpen });
}
};
render() {
return (
<>
<StyledSpan onClick={() => { this.toggleContent(!this.state.isOpen) }}>
<Arrow color="#333333" isfill={true} size='medium' isOpen={this.state.isOpen} />
<Text.Headline tag='h2' isInline={true}>{this.props.label}</Text.Headline>
</StyledSpan>
<StyledContent isOpen={this.state.isOpen}>{this.props.children}</StyledContent>
</>
)
}
}
ToggleContent.propTypes = {
isOpen: PropTypes.bool
}
ToggleContent.defaultProps = {
isOpen: false,
label: "Some label"
}
export default ToggleContent;

View File

@ -33,3 +33,4 @@ export { default as toastr } from './components/toast/toastr'
export { default as ComboBox } from './components/combobox'
export { default as Paging } from './components/paging'
export { default as FilterInput } from './components/filter-input'
export { default as ToggleContent } from './components/toggle-content'

View File

@ -143,5 +143,20 @@
<LastGenOutput>WebstudioNotifyPatternResource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\SmsKeyCacheKey.proto" />
<Protobuf Include="protos\TenantLogoCacheItem.proto" />
<Protobuf Include="protos\MailServiceHelperCache.proto" />
<Protobuf Include="protos\WebItemSecurityNotifier.proto" />
<Protobuf Include="protos\UserPhotoManagerCacheItem.proto" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.Tools" Version="1.22.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -49,9 +49,16 @@ namespace ASC.Web.Core.Mail
public const string DefaultProtocol = "http";
public const int DefaultPort = 8081;
public const string DefaultVersion = "v1";
private static readonly ICache Cache = AscCache.Default;
private static readonly ICacheNotify<MailServiceHelperCache> CacheNotify;
private static readonly ICache Cache = AscCache.Memory;
private const string CacheKey = "mailserverinfo";
static MailServiceHelper()
{
CacheNotify = new KafkaCache<MailServiceHelperCache>();
CacheNotify.Subscribe(r => Cache.Remove(r.Key), CacheNotifyAction.Remove);
}
private static string GetDefaultDatabase()
{
@ -282,8 +289,8 @@ namespace ASC.Web.Core.Mail
}
transaction.Commit();
Cache.Remove(CacheKey);
CacheNotify.Publish(new MailServiceHelperCache() { Key = CacheKey }, CacheNotifyAction.Remove);
}
}
}

View File

@ -40,8 +40,9 @@ namespace ASC.Web.Core.Sms
public static readonly TimeSpan StoreInterval;
public static readonly int AttemptCount;
private static readonly object KeyLocker = new object();
private static readonly ICacheNotify<SmsKeyCacheKey> KeyCacheNotify;
private static readonly ICache KeyCache = AscCache.Default;
private static readonly ICache CheckCache = AscCache.Default;
private static readonly ICache CheckCache = AscCache.Memory;
static SmsKeyStorage()
{
@ -61,6 +62,9 @@ namespace ASC.Web.Core.Sms
{
AttemptCount = 5;
}
KeyCacheNotify = new KafkaCache<SmsKeyCacheKey>();
KeyCacheNotify.Subscribe(r => KeyCache.Remove(r.Key), CacheNotifyAction.Remove);
}
private static string BuildCacheKey(string phone)
@ -138,7 +142,7 @@ namespace ASC.Web.Core.Sms
return Result.Invalide;
var createDate = phoneKeys[key];
KeyCache.Remove(cacheKey);
KeyCacheNotify.Publish(new SmsKeyCacheKey { Key = cacheKey }, CacheNotifyAction.Remove);
if (createDate.Add(StoreInterval) < DateTime.UtcNow)
return Result.Timeout;

View File

@ -120,53 +120,51 @@ namespace ASC.Web.Core.Users
}
}
public class UserPhotoManagerCacheItem
{
public Guid UserID { get; set; }
public Size Size { get; set; }
public string FileName { get; set; }
}
public class UserPhotoManager
{
private static readonly IDictionary<Guid, IDictionary<Size, string>> Photofiles = new Dictionary<Guid, IDictionary<Size, string>>();
private static readonly ICacheNotify CacheNotify;
private static readonly ICacheNotify<UserPhotoManagerCacheItem> CacheNotify;
static UserPhotoManager()
{
try
{
CacheNotify = AscCache.Notify;
CacheNotify = new KafkaCache<UserPhotoManagerCacheItem>();
CacheNotify.Subscribe<UserPhotoManagerCacheItem>((data, action) =>
CacheNotify.Subscribe((data) =>
{
if (action == CacheNotifyAction.InsertOrUpdate)
var userId = new Guid(data.UserID);
var size = new Size(data.Size.Width, data.Size.Height);
lock (Photofiles)
{
if (!Photofiles.ContainsKey(userId))
{
Photofiles[userId] = new ConcurrentDictionary<Size, string>();
}
Photofiles[userId][size] = data.FileName;
}
}, CacheNotifyAction.InsertOrUpdate);
CacheNotify.Subscribe((data) =>
{
var userId = new Guid(data.UserID);
var size = new Size(data.Size.Width, data.Size.Height);
try
{
lock (Photofiles)
{
if (!Photofiles.ContainsKey(data.UserID))
{
Photofiles[data.UserID] = new ConcurrentDictionary<Size, string>();
}
Photofiles[data.UserID][data.Size] = data.FileName;
Photofiles.Remove(userId);
}
var storage = GetDataStore();
storage.DeleteFiles("", data.UserID + "*.*", false);
SetCacheLoadedForTenant(false);
}
if (action == CacheNotifyAction.Remove)
{
try
{
lock (Photofiles)
{
Photofiles.Remove(data.UserID);
}
var storage = GetDataStore();
storage.DeleteFiles("", data.UserID.ToString() + "*.*", false);
SetCacheLoadedForTenant(false);
}
catch { }
}
});
catch { }
}, CacheNotifyAction.Remove);
}
catch (Exception)
{
@ -460,7 +458,7 @@ namespace ASC.Web.Core.Users
{
if (CacheNotify != null)
{
CacheNotify.Publish(new UserPhotoManagerCacheItem {UserID = userID}, CacheNotifyAction.Remove);
CacheNotify.Publish(new UserPhotoManagerCacheItem {UserID = userID.ToString()}, CacheNotifyAction.Remove);
}
}
@ -468,7 +466,7 @@ namespace ASC.Web.Core.Users
{
if (CacheNotify != null)
{
CacheNotify.Publish(new UserPhotoManagerCacheItem {UserID = userId, Size = size, FileName = fileName}, CacheNotifyAction.InsertOrUpdate);
CacheNotify.Publish(new UserPhotoManagerCacheItem {UserID = userId.ToString(), Size = new CacheSize() { Height = size.Height, Width = size.Width }, FileName = fileName}, CacheNotifyAction.InsertOrUpdate);
}
}

View File

@ -44,18 +44,18 @@ namespace ASC.Web.Core
{
private static readonly SecurityAction Read = new SecurityAction(new Guid("77777777-32ae-425f-99b5-83176061d1ae"), "ReadWebItem", false, true);
private static readonly ICache cache;
private static readonly ICacheNotify cacheNotify;
private static readonly ICacheNotify<WebItemSecurityNotifier> cacheNotify;
static WebItemSecurity()
{
try
{
cache = AscCache.Memory;
cacheNotify = AscCache.Notify;
cacheNotify.Subscribe<WebItemSecurityNotifier>((r, act) =>
cacheNotify = new KafkaCache<WebItemSecurityNotifier>();
cacheNotify.Subscribe((r) =>
{
ClearCache();
});
}, CacheNotifyAction.Any);
}
catch
{
@ -384,9 +384,4 @@ namespace ASC.Web.Core
}
}
}
public class WebItemSecurityNotifier
{
}
}

View File

@ -37,7 +37,8 @@ namespace ASC.Web.Core.WhiteLabel
{
public class TenantLogoManager
{
private static readonly ICache Cache = AscCache.Default;
private static readonly ICache Cache = AscCache.Memory;
private static readonly ICacheNotify<TenantLogoCacheItem> CacheNotify;
private static string CacheKey
{
@ -54,7 +55,9 @@ namespace ASC.Web.Core.WhiteLabel
static TenantLogoManager()
{
var hideSettings = (ConfigurationManager.AppSettings["web.hide-settings"] ?? "").Split(new[] { ',', ';', ' ' });
WhiteLabelEnabled = !hideSettings.Contains("WhiteLabel", StringComparer.CurrentCultureIgnoreCase);
WhiteLabelEnabled = !hideSettings.Contains("WhiteLabel", StringComparer.CurrentCultureIgnoreCase);
CacheNotify = new KafkaCache<TenantLogoCacheItem>();
CacheNotify.Subscribe(r => Cache.Remove(r.Key), CacheNotifyAction.Remove);
}
@ -178,8 +181,8 @@ namespace ASC.Web.Core.WhiteLabel
}
public static void RemoveMailLogoDataFromCache()
{
Cache.Remove(CacheKey);
{
CacheNotify.Publish(new TenantLogoCacheItem() { Key = CacheKey }, CacheNotifyAction.Remove);
}
}
}

View File

@ -0,0 +1,8 @@

syntax = "proto3";
package ASC.Web.Core;
message MailServiceHelperCache {
string Key = 1;
}

View File

@ -0,0 +1,8 @@

syntax = "proto3";
package ASC.Web.Core;
message SmsKeyCacheKey {
string Key = 1;
}

View File

@ -0,0 +1,8 @@

syntax = "proto3";
package ASC.Web.Core.WhiteLabel;
message TenantLogoCacheItem {
string Key = 1;
}

View File

@ -0,0 +1,17 @@
syntax = "proto3";
package ASC.Web.Core.Users;
message UserPhotoManagerCacheItem {
string UserID = 1;
CacheSize Size = 2;
string FileName = 3;
}
message CacheSize {
int32 Width = 1;
int32 Height = 2;
}

View File

@ -0,0 +1,7 @@

syntax = "proto3";
package ASC.Web.Core;
message WebItemSecurityNotifier {
}

View File

@ -147,9 +147,9 @@ const articleBodyContent = <p style={{padding: 40}}>Article Content</p>;
const sectionHeaderContent = <HeaderContent>
<IconButton
iconName={"ProjectDocumentsUpIcon"}
iconName={"ArrowPathIcon"}
size='16'
onClick={(e) => action('ProjectDocumentsUpIcon Clicked')(e)}
onClick={(e) => action('ArrowPathIcon Clicked')(e)}
/>
<Text.ContentHeader>Section Header</Text.ContentHeader>
<IconButton

View File

@ -0,0 +1,27 @@
# ToggleContent
## Usage
```js
import { ToggleContent } from 'asc-web-components';
```
#### Description
ToggleContent allow you to adding information, which you may hide/show by clicking header
#### Usage
```js
<ToggleContent>
// your data
<ToggleContent />
```
#### Properties
| Props | Type | Required | Values | Default | Description |
| ------------------ | -------- | :------: | --------------------------- | -------------- | ----------------------------------------------------------------- |
| `label` | `text` | ✅ | - | Some label | Define label for header |

View File

@ -0,0 +1,21 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { ToggleContent } from 'asc-web-components';
import Readme from './README.md';
import withReadme from 'storybook-readme/with-readme';
import { text, withKnobs, boolean } from '@storybook/addon-knobs/react';
storiesOf('Components|ToggleContent', module)
.addDecorator(withKnobs)
.addDecorator(withReadme(Readme))
.add('base', () => {
return (
<>
<ToggleContent
label={text('label', 'Some label')}
isOpen={boolean('isOpen', true)}
>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae
</ToggleContent>
</>
);
});