diff --git a/common/ASC.Common/Caching/AscCache.cs b/common/ASC.Common/Caching/AscCache.cs index 90baae5bc8..b7be7dbaf8 100644 --- a/common/ASC.Common/Caching/AscCache.cs +++ b/common/ASC.Common/Caching/AscCache.cs @@ -28,8 +28,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Caching; -using System.Text.RegularExpressions; - +using System.Text.RegularExpressions; +using Google.Protobuf; + namespace ASC.Common.Caching { public class AscCache : ICache @@ -143,7 +144,7 @@ namespace ASC.Common.Caching public void ClearCache() { - KafkaNotify.Publish(new AscCacheItem() { Id = Guid.NewGuid().ToString() }, CacheNotifyAction.Any); + KafkaNotify.Publish(new AscCacheItem() { Id = ByteString.CopyFrom(Guid.NewGuid().ToByteArray()) }, CacheNotifyAction.Any); } private MemoryCache GetCache() diff --git a/common/ASC.Common/Caching/KafkaCache.cs b/common/ASC.Common/Caching/KafkaCache.cs index cc05dd176e..d0c6645d47 100644 --- a/common/ASC.Common/Caching/KafkaCache.cs +++ b/common/ASC.Common/Caching/KafkaCache.cs @@ -12,29 +12,47 @@ using Google.Protobuf; namespace ASC.Common.Caching { - public class KafkaCache : ICacheNotify where T : IMessage, new() + public class KafkaCache : IDisposable, ICacheNotify where T : IMessage, new() { private ClientConfig ClientConfig { get; set; } private ILog Log { get; set; } - private ConcurrentDictionary Cts { get; set; } + private ConcurrentDictionary Cts { get; set; } + private ConcurrentDictionary> Actions { get; set; } private MemoryCacheNotify MemoryCacheNotify { get; set; } + private string ChannelName { get; } = $"ascchannel{typeof(T).Name}"; + private ProtobufSerializer ValueSerializer { get; } = new ProtobufSerializer(); + private ProtobufDeserializer ValueDeserializer { get; } = new ProtobufDeserializer(); + private ProtobufSerializer KeySerializer { get; } = new ProtobufSerializer(); + private ProtobufDeserializer KeyDeserializer { get; } = new ProtobufDeserializer(); + private IProducer Producer { get; } + private Guid Key { get; set; } public KafkaCache() { Log = LogManager.GetLogger("ASC"); - Cts = new ConcurrentDictionary(); + Cts = new ConcurrentDictionary(); + Actions = new ConcurrentDictionary>(); + Key = Guid.NewGuid(); var settings = ConfigurationManager.GetSetting("kafka"); if (settings != null && !string.IsNullOrEmpty(settings.BootstrapServers)) { ClientConfig = new ClientConfig { BootstrapServers = settings.BootstrapServers }; + + var config = new ProducerConfig(ClientConfig); + Producer = new ProducerBuilder(config) + .SetErrorHandler((_, e) => Log.Error(e)) + .SetKeySerializer(KeySerializer) + .SetValueSerializer(ValueSerializer) + .Build(); } else { MemoryCacheNotify = new MemoryCacheNotify(); } + } - public async void Publish(T obj, CacheNotifyAction cacheNotifyAction) + public void Publish(T obj, CacheNotifyAction cacheNotifyAction) { if (ClientConfig == null) { @@ -42,16 +60,25 @@ namespace ASC.Common.Caching return; } - var config = new ProducerConfig(ClientConfig); - - using var p = new ProducerBuilder(config) - .SetErrorHandler((_, e) => Log.Error(e)) - .SetValueSerializer(new ProtobufSerializer()) - .Build(); - try { - var dr = await p.ProduceAsync(GetChannelName(cacheNotifyAction), new Message() { Value = obj }); + var channelName = GetChannelName(cacheNotifyAction); + + if (Actions.TryGetValue(channelName, out var onchange)) + { + onchange(obj); + } + + var message = new Message + { + Value = obj, + Key = new AscCacheItem + { + Id = ByteString.CopyFrom(Key.ToByteArray()) + } + }; + + Producer.ProduceAsync(channelName, message); } catch (ProduceException e) { @@ -70,23 +97,24 @@ namespace ASC.Common.Caching MemoryCacheNotify.Subscribe(onchange, cacheNotifyAction); return; } - - Cts[cacheNotifyAction] = new CancellationTokenSource(); + var channelName = GetChannelName(cacheNotifyAction); + Cts[channelName] = new CancellationTokenSource(); + Actions[channelName] = onchange; void action() { var conf = new ConsumerConfig(ClientConfig) { - GroupId = Guid.NewGuid().ToString(), - EnableAutoCommit = true + GroupId = Guid.NewGuid().ToString() }; - using var c = new ConsumerBuilder(conf) + using var c = new ConsumerBuilder(conf) .SetErrorHandler((_, e) => Log.Error(e)) - .SetValueDeserializer(new ProtobufDeserializer()) + .SetKeyDeserializer(KeyDeserializer) + .SetValueDeserializer(ValueDeserializer) .Build(); - c.Assign(new TopicPartition(GetChannelName(cacheNotifyAction), new Partition())); + c.Assign(new TopicPartition(channelName, new Partition())); try { @@ -94,10 +122,10 @@ namespace ASC.Common.Caching { try { - var cr = c.Consume(Cts[cacheNotifyAction].Token); - if (cr != null && cr.Value != null) + var cr = c.Consume(Cts[channelName].Token); + if (cr != null && cr.Value != null && !(new Guid(cr.Key.Id.ToByteArray())).Equals(Key) && Actions.TryGetValue(channelName, out var act)) { - onchange(cr.Value); + act(cr.Value); } } catch (ConsumeException e) @@ -118,17 +146,42 @@ namespace ASC.Common.Caching private string GetChannelName(CacheNotifyAction cacheNotifyAction) { - return $"ascchannel{typeof(T).Name}{cacheNotifyAction}"; + return $"{ChannelName}{cacheNotifyAction}"; } public void Unsubscribe(CacheNotifyAction action) { - Cts.TryGetValue(action, out var source); + Cts.TryGetValue(GetChannelName(action), out var source); if (source != null) { source.Cancel(); } } + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + Producer.Dispose(); + } + + disposedValue = true; + } + } + ~KafkaCache() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } public class KafkaSettings @@ -141,23 +194,23 @@ namespace ASC.Common.Caching private readonly Dictionary>> actions = new Dictionary>>(); public void Publish(T obj, CacheNotifyAction action) - { - if (actions.TryGetValue(GetKey(action), out var onchange) && onchange != null) - { + { + if (actions.TryGetValue(GetKey(action), out var onchange) && onchange != null) + { foreach (var a in onchange) { a(obj); - } + } } } public void Subscribe(Action onchange, CacheNotifyAction notifyAction) { - if (onchange != null) + if (onchange != null) { var key = GetKey(notifyAction); - actions.TryAdd(key, new List>()); - actions[key].Add(onchange); + actions.TryAdd(key, new List>()); + actions[key].Add(onchange); } } @@ -171,4 +224,4 @@ namespace ASC.Common.Caching return $"{typeof(T).Name}{cacheNotifyAction}"; } } -} +} \ No newline at end of file diff --git a/common/ASC.Common/Caching/Protobuf.cs b/common/ASC.Common/Caching/Protobuf.cs index 8ea89666a0..933fc7cb21 100644 --- a/common/ASC.Common/Caching/Protobuf.cs +++ b/common/ASC.Common/Caching/Protobuf.cs @@ -22,4 +22,16 @@ namespace ASC.Common.Caching public T Deserialize(ReadOnlySpan data, bool isNull, SerializationContext context) => parser.ParseFrom(data.ToArray()); } + + public static class GuidExtension + { + public static ByteString ToByteString(this Guid id) + { + return ByteString.CopyFrom(id.ToByteArray()); + } + public static Guid FromByteString(this ByteString id) + { + return new Guid(id.ToByteArray()); + } + } } diff --git a/common/ASC.Common/Utils/HttpRequestExtensions.cs b/common/ASC.Common/Utils/HttpRequestExtensions.cs index e10c3a7795..1cd120d8b2 100644 --- a/common/ASC.Common/Utils/HttpRequestExtensions.cs +++ b/common/ASC.Common/Utils/HttpRequestExtensions.cs @@ -54,35 +54,42 @@ namespace System.Web public static Uri GetUrlRewriter(IHeaderDictionary headers, HttpRequest request) { - if (request.Query != null && request.Query.Count > 0) - { - var rewriterUri = ParseRewriterUrl(request.Query[UrlRewriterHeader]); - if (rewriterUri != null) + if (headers != null) + { + var h = headers[UrlRewriterHeader]; + var rewriterUri = !string.IsNullOrEmpty(h) ? ParseRewriterUrl(h) : null; + if (request != null && rewriterUri != null) { - var result = new UriBuilder(request.GetDisplayUrl()) + var result = new UriBuilder() { Scheme = rewriterUri.Scheme, Host = rewriterUri.Host, Port = rewriterUri.Port - }; + }; + result.Query = request.QueryString.Value; + result.Path = request.Path.Value; return result.Uri; } - } + } + + if (request != null && request.Query != null) + { + var h = request.Query[UrlRewriterHeader]; + var rewriterUri = !string.IsNullOrEmpty(h) ? ParseRewriterUrl(h) : null; + if (rewriterUri != null) + { + var result = new UriBuilder() + { + Scheme = rewriterUri.Scheme, + Host = rewriterUri.Host, + Port = rewriterUri.Port + }; + result.Query = request.QueryString.Value; + result.Path = request.Path.Value; - if (headers != null && !string.IsNullOrEmpty(headers[UrlRewriterHeader])) - { - var rewriterUri = ParseRewriterUrl(headers[UrlRewriterHeader]); - if (rewriterUri != null) - { - var result = new UriBuilder(request.GetDisplayUrl()) - { - Scheme = rewriterUri.Scheme, - Host = rewriterUri.Host, - Port = rewriterUri.Port - }; return result.Uri; } - } + } return request.Url(); } diff --git a/common/ASC.Common/Utils/TimeZoneConverter/TimeZoneConverter.cs b/common/ASC.Common/Utils/TimeZoneConverter/TimeZoneConverter.cs index 30af3b4e6c..8059d04af7 100644 --- a/common/ASC.Common/Utils/TimeZoneConverter/TimeZoneConverter.cs +++ b/common/ASC.Common/Utils/TimeZoneConverter/TimeZoneConverter.cs @@ -67,7 +67,8 @@ namespace ASC.Common.Utils OlsonTimeZoneId = olsonTimeZone, WindowsTimeZoneId = row.Attribute("other").Value, Territory = row.Attribute("territory").Value - }; + }; + _mapZones = _mapZones.ToList(); } catch (Exception error) { diff --git a/common/ASC.Common/protos/AscCacheItem.proto b/common/ASC.Common/protos/AscCacheItem.proto index 74b59401b2..8fbd2e7cfb 100644 --- a/common/ASC.Common/protos/AscCacheItem.proto +++ b/common/ASC.Common/protos/AscCacheItem.proto @@ -3,5 +3,5 @@ package ASC.Common; message AscCacheItem { - string Id = 1; + bytes Id = 1; } \ No newline at end of file diff --git a/common/ASC.Core.Common/Caching/CachedUserService.cs b/common/ASC.Core.Common/Caching/CachedUserService.cs index 28ce9f8bc0..71401ed993 100644 --- a/common/ASC.Core.Common/Caching/CachedUserService.cs +++ b/common/ASC.Core.Common/Caching/CachedUserService.cs @@ -145,14 +145,14 @@ namespace ASC.Core.Caching public UserInfo SaveUser(int tenant, UserInfo user) { user = service.SaveUser(tenant, user); - cacheUserInfoItem.Publish(new UserInfoCacheItem() { ID = user.ID.ToString(), Tenant = tenant }, CacheNotifyAction.InsertOrUpdate); + cacheUserInfoItem.Publish(new UserInfoCacheItem() { ID = user.ID.ToByteString(), Tenant = tenant }, CacheNotifyAction.InsertOrUpdate); return user; } public void RemoveUser(int tenant, Guid id) { service.RemoveUser(tenant, id); - cacheUserInfoItem.Publish(new UserInfoCacheItem { Tenant = tenant, ID = id.ToString() }, CacheNotifyAction.Any); + cacheUserInfoItem.Publish(new UserInfoCacheItem { Tenant = tenant, ID = id.ToByteString() }, CacheNotifyAction.Any); } public byte[] GetUserPhoto(int tenant, Guid id) @@ -256,7 +256,7 @@ namespace ASC.Core.Caching { if (CoreContext.Configuration.Personal && userInfo != null) { - var key = GetUserCacheKeyForPersonal(userInfo.Tenant, Guid.Parse(userInfo.ID)); + var key = GetUserCacheKeyForPersonal(userInfo.Tenant, userInfo.ID.FromByteString()); cache.Remove(key); } diff --git a/common/ASC.Core.Common/Core/UserGroupRef.cs b/common/ASC.Core.Common/Core/UserGroupRef.cs index 6598adbb87..e9c67f7ae2 100644 --- a/common/ASC.Core.Common/Core/UserGroupRef.cs +++ b/common/ASC.Core.Common/Core/UserGroupRef.cs @@ -26,6 +26,7 @@ using System; using System.Diagnostics; +using ASC.Common.Caching; using ASC.Core.Caching; namespace ASC.Core @@ -79,17 +80,11 @@ namespace ASC.Core public static implicit operator UserGroupRef(UserGroupRefCacheItem cache) { - var result = new UserGroupRef(); - - if (Guid.TryParse(cache.UserId, out var userId)) + var result = new UserGroupRef { - result.UserId = userId; - } - - if (Guid.TryParse(cache.GroupId, out var groupId)) - { - result.GroupId = groupId; - } + UserId = cache.UserId.FromByteString(), + GroupId = cache.GroupId.FromByteString() + }; if (Enum.TryParse(cache.RefType, out var refType)) { @@ -107,8 +102,8 @@ namespace ASC.Core { return new UserGroupRefCacheItem { - GroupId = cache.GroupId.ToString(), - UserId = cache.UserId.ToString(), + GroupId = cache.GroupId.ToByteString(), + UserId = cache.UserId.ToByteString(), RefType = cache.RefType.ToString(), LastModified = cache.LastModified.Ticks, Removed = cache.Removed, diff --git a/common/ASC.Core.Common/protos/UserGroupRefCacheItem.proto b/common/ASC.Core.Common/protos/UserGroupRefCacheItem.proto index 0c894c1601..bd86dae496 100644 --- a/common/ASC.Core.Common/protos/UserGroupRefCacheItem.proto +++ b/common/ASC.Core.Common/protos/UserGroupRefCacheItem.proto @@ -3,8 +3,8 @@ package ASC.Core.Caching; message UserGroupRefCacheItem { - string UserId = 1; - string GroupId = 2; + bytes UserId = 1; + bytes GroupId = 2; bool Removed = 3; string RefType = 4; int64 LastModified = 5; diff --git a/common/ASC.Core.Common/protos/UserInfoCacheItem.proto b/common/ASC.Core.Common/protos/UserInfoCacheItem.proto index 14ac3fc2ed..5f19f1d139 100644 --- a/common/ASC.Core.Common/protos/UserInfoCacheItem.proto +++ b/common/ASC.Core.Common/protos/UserInfoCacheItem.proto @@ -3,6 +3,6 @@ package ASC.Core.Caching; message UserInfoCacheItem { - string ID = 1; + bytes ID = 1; int32 Tenant = 2; } \ No newline at end of file diff --git a/common/ASC.Data.Storage/StorageFactory.cs b/common/ASC.Data.Storage/StorageFactory.cs index ff5164be94..856b9f6d1a 100644 --- a/common/ASC.Data.Storage/StorageFactory.cs +++ b/common/ASC.Data.Storage/StorageFactory.cs @@ -42,11 +42,13 @@ namespace ASC.Data.Storage { private const string DefaultTenantName = "default"; private static readonly ICacheNotify Cache; + private static readonly Lazy Section; static StorageFactory() { Cache = new KafkaCache(); Cache.Subscribe((r) => DataStoreCache.Remove(r.TenantId, r.Module), CacheNotifyAction.Remove); + Section = new Lazy(() => CommonServiceProvider.GetService(), true); } public static IDataStore GetStorage(string tenant, string module) @@ -78,7 +80,7 @@ namespace ASC.Data.Storage var store = DataStoreCache.Get(tenant, module); if (store == null) { - var section = CommonServiceProvider.GetService(); + var section = Section.Value; if (section == null) { throw new InvalidOperationException("config section not found"); @@ -98,7 +100,7 @@ namespace ASC.Data.Storage //Make tennant path tenant = TennantPath.CreatePath(tenant); - var section = CommonServiceProvider.GetService(); + var section = Section.Value; if (section == null) { throw new InvalidOperationException("config section not found"); @@ -110,7 +112,7 @@ namespace ASC.Data.Storage public static IEnumerable GetModuleList(string configpath, bool exceptDisabledMigration = false) { - var section = CommonServiceProvider.GetService(); + var section = Section.Value; return section.Module .Where(x => x.Visible) .Where(x => !exceptDisabledMigration || !x.DisableMigrate) @@ -119,7 +121,7 @@ namespace ASC.Data.Storage public static IEnumerable GetDomainList(string configpath, string modulename) { - var section = CommonServiceProvider.GetService(); + var section = Section.Value; if (section == null) { throw new ArgumentException("config section not found"); @@ -218,7 +220,7 @@ namespace ASC.Data.Storage private static IDataStore GetDataStore(string tenant, string module, DataStoreConsumer consumer, IQuotaController controller) { - var storage = CommonServiceProvider.GetService(); + var storage = Section.Value; var moduleElement = storage.GetModuleElement(module); if (moduleElement == null) { diff --git a/products/ASC.People/Client/config-overrides.js b/products/ASC.People/Client/config-overrides.js new file mode 100644 index 0000000000..424e9a1678 --- /dev/null +++ b/products/ASC.People/Client/config-overrides.js @@ -0,0 +1,23 @@ +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const path = require('path'); +module.exports = (config) => { + + config.plugins.push( + new CopyWebpackPlugin( + [ + { + from: path.join('src', path.sep, '**', path.sep, 'locales', path.sep, '**'), + to: 'locales', + transformPath(targetPath) { + const reversedArrayOfFolders = path.dirname(targetPath).split(path.sep).reverse(); + const localePath = reversedArrayOfFolders.pop(); + const finalPath = path.join(path.sep, localePath, path.sep, reversedArrayOfFolders[2], path.sep, reversedArrayOfFolders[0], path.sep, path.basename(targetPath)); + return finalPath; + }, + } + ] + ) + ); + + return config; +} \ No newline at end of file diff --git a/products/ASC.People/Client/package.json b/products/ASC.People/Client/package.json index b9c18da495..6b7afcc9dc 100644 --- a/products/ASC.People/Client/package.json +++ b/products/ASC.People/Client/package.json @@ -38,6 +38,7 @@ "devDependencies": { "ajv": "^6.10.0", "babel-eslint": "10.0.1", + "copy-webpack-plugin": "^5.0.4", "cross-env": "^5.2.0", "eslint": "^5.16.0", "eslint-config-react-app": "^4.0.1", @@ -45,6 +46,7 @@ "eslint-plugin-import": "^2.18.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.14.2", + "react-app-rewired": "^2.1.3", "redux-devtools-extension": "^2.13.8", "rimraf": "2.6.3" }, @@ -52,9 +54,9 @@ "extends": "react-app" }, "scripts": { - "start": "rimraf ./build && react-scripts start", - "build": "react-scripts build", - "test": "cross-env CI=true react-scripts test --env=jsdom", + "start": "rimraf ./build && react-app-rewired start", + "build": "react-app-rewired build", + "test": "cross-env CI=true react-app-rewired test --env=jsdom", "eject": "react-scripts eject", "lint": "eslint ./src/" }, diff --git a/products/ASC.People/Client/src/components/pages/Home/Section/Paging/index.js b/products/ASC.People/Client/src/components/pages/Home/Section/Paging/index.js index 8dd9928be5..88b4f8dabb 100644 --- a/products/ASC.People/Client/src/components/pages/Home/Section/Paging/index.js +++ b/products/ASC.People/Client/src/components/pages/Home/Section/Paging/index.js @@ -10,7 +10,7 @@ const SectionPagingContent = ({ onLoading, selectedCount }) => { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const onNextClick = useCallback( e => { if (!filter.hasNext()) { diff --git a/products/ASC.People/Client/src/components/pages/Profile/Section/Body/index.js b/products/ASC.People/Client/src/components/pages/Profile/Section/Body/index.js index 282c3036be..578f7bd76d 100644 --- a/products/ASC.People/Client/src/components/pages/Profile/Section/Body/index.js +++ b/products/ASC.People/Client/src/components/pages/Profile/Section/Body/index.js @@ -129,7 +129,7 @@ const ProfileInfo = (props) => { const { isVisitor, email, activationStatus, department, title, mobilePhone, sex, workFrom, birthday, location, cultureName, currentCulture } = props.profile; const isAdmin = props.isAdmin; const isSelf = props.isSelf; - const { t, i18n } = useTranslation(); + const t = props.t; const type = isVisitor ? "Guest" : "Employee"; const language = cultureName || currentCulture; @@ -147,7 +147,7 @@ const ProfileInfo = (props) => { - {t('Resource:UserType')}: + {t('UserType')}: {type} @@ -156,7 +156,7 @@ const ProfileInfo = (props) => { {email && - {t('Resource:Email')}: + {t('Email')}: { onClick={onEmailClick} > {activationStatus === 2 && (isAdmin || isSelf) && - + } {email} {(isAdmin || isSelf) && - + } {activationStatus === 2 && (isAdmin || isSelf) && - + } @@ -209,12 +209,12 @@ const ProfileInfo = (props) => { {(mobilePhone || isSelf) && - Phone: + {t('PhoneLbl')}: {mobilePhone} {(isAdmin || isSelf) && - + } @@ -224,7 +224,7 @@ const ProfileInfo = (props) => { {sex && - {t('Resource:Sex')}: + {t('Sex')}: {formatedSex} @@ -244,7 +244,7 @@ const ProfileInfo = (props) => { {birthday && - {t('Resource:Birthdate')}: + {t('Birthdate')}: {birthDayDate} @@ -254,7 +254,7 @@ const ProfileInfo = (props) => { {location && - {t('Resource:Location')}: + {t('Location')}: {location} @@ -264,7 +264,7 @@ const ProfileInfo = (props) => { {isSelf && - {t('Resource:Language')}: + {t('Language')}: {language} @@ -276,7 +276,7 @@ const ProfileInfo = (props) => { }; const SectionBodyContent = props => { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const { profile, history, settings, isAdmin, viewer } = props; const contacts = profile.contacts && getUserContacts(profile.contacts); @@ -308,20 +308,20 @@ const SectionBodyContent = props => {