From 096375344ef87fe53ca009b7a7eaa34c9c9f5407 Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 15 Mar 2024 18:10:58 +0100 Subject: Bot changes, move named filters to subclass --- .../Homeservers/AuthenticatedHomeserverGeneric.cs | 93 +++++++++------------- .../Extensions/NamedCaches/NamedCache.cs | 37 +++++++++ .../Extensions/NamedCaches/NamedFileCache.cs | 3 + .../Extensions/NamedCaches/NamedFilterCache.cs | 33 ++++++++ 4 files changed, 109 insertions(+), 57 deletions(-) create mode 100644 LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs create mode 100644 LibMatrix/Homeservers/Extensions/NamedCaches/NamedFileCache.cs create mode 100644 LibMatrix/Homeservers/Extensions/NamedCaches/NamedFilterCache.cs (limited to 'LibMatrix/Homeservers') diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index 1c93235..b4c1cc9 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -10,6 +11,7 @@ using LibMatrix.EventTypes.Spec.State; using LibMatrix.Extensions; using LibMatrix.Filters; using LibMatrix.Helpers; +using LibMatrix.Homeservers.Extensions.NamedCaches; using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; @@ -46,6 +48,7 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke } instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"); + instance.NamedCaches = new HsNamedCaches(instance); return instance; } @@ -57,6 +60,8 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke public string AccessToken { get; set; } = accessToken; + public HsNamedCaches NamedCaches { get; set; } = null!; + public GenericRoom GetRoom(string roomId) { if (roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId)); return new GenericRoom(this, roomId); @@ -294,6 +299,12 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke WhoAmI = await ClientHttpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"); } + /// + /// Upload a filter to the homeserver. Substitutes @me with the user's ID. + /// + /// + /// + /// public async Task UploadFilterAsync(SyncFilter filter) { List?> senderLists = [ filter.AccountData?.Senders, @@ -326,69 +337,21 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke return _filterCache[filterId] = await resp.Content.ReadFromJsonAsync() ?? throw new Exception("Failed to get filter?"); } -#region Named filters - - private async Task?> GetNamedFilterListOrNullAsync(bool cached = true) { - if (cached && _namedFilterCache is not null) return _namedFilterCache; - try { - return _namedFilterCache = await GetAccountDataAsync>("gay.rory.libmatrix.named_filters"); - } - catch (MatrixException e) { - if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; - } - - return null; - } - - /// - /// Utility function to allow avoiding serverside duplication - /// - /// Name of the filter (please properly namespace and possibly version this...) - /// The filter data - /// Filter ID response - /// - public async Task UploadNamedFilterAsync(string filterName, SyncFilter filter) { - var idResp = await UploadFilterAsync(filter); - - var filterList = await GetNamedFilterListOrNullAsync() ?? new Dictionary(); - filterList[filterName] = idResp.FilterId; - await SetAccountDataAsync("gay.rory.libmatrix.named_filters", filterList); - - _namedFilterCache = filterList; - - return idResp; - } - - public async Task GetNamedFilterIdOrNullAsync(string filterName) { - var filterList = await GetNamedFilterListOrNullAsync() ?? new Dictionary(); - return filterList.GetValueOrDefault(filterName); //todo: validate that filter exists - } - - public async Task GetNamedFilterOrNullAsync(string filterName) { - var filterId = await GetNamedFilterIdOrNullAsync(filterName); - if (filterId is null) return null; - return await GetFilterAsync(filterId); - } - - public async Task GetOrUploadNamedFilterIdAsync(string filterName, SyncFilter? filter = null) { - var filterId = await GetNamedFilterIdOrNullAsync(filterName); - if (filterId is not null) return filterId; - if (filter is null && CommonSyncFilters.FilterMap.TryGetValue(filterName, out var commonFilter)) filter = commonFilter; - if (filter is null) throw new ArgumentException($"Filter is null and no common filter was found, filterName={filterName}", nameof(filter)); - var idResp = await UploadNamedFilterAsync(filterName, filter); - return idResp.FilterId; - } - -#endregion - public class FilterIdResponse { [JsonPropertyName("filter_id")] public required string FilterId { get; set; } } + /// + /// Enumerate all account data per room. + /// Warning: This uses /sync! + /// + /// Include non-room account data + /// Dictionary of room IDs and their account data. + /// public async Task> EnumerateAccountDataPerRoom(bool includeGlobal = false) { var syncHelper = new SyncHelper(this); - syncHelper.FilterId = await GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetAccountDataWithRooms); + syncHelper.FilterId = await NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetAccountDataWithRooms); var resp = await syncHelper.SyncAsync(); if (resp is null) throw new Exception("Sync failed"); var perRoomAccountData = new Dictionary(); @@ -400,9 +363,15 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke return perRoomAccountData; } + /// + /// Enumerate all non-room account data. + /// Warning: This uses /sync! + /// + /// All account data. + /// public async Task EnumerateAccountData() { var syncHelper = new SyncHelper(this); - syncHelper.FilterId = await GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetAccountData); + syncHelper.FilterId = await NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetAccountData); var resp = await syncHelper.SyncAsync(); if (resp is null) throw new Exception("Sync failed"); return resp.AccountData; @@ -420,4 +389,14 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke return await res.Content.ReadFromJsonAsync(); } + + public class HsNamedCaches { + internal HsNamedCaches(AuthenticatedHomeserverGeneric hs) { + FileCache = new NamedFileCache(hs); + FilterCache = new NamedFilterCache(hs); + } + + public NamedFilterCache FilterCache { get; init; } + public NamedFileCache FileCache { get; init; } + } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs new file mode 100644 index 0000000..622eef6 --- /dev/null +++ b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs @@ -0,0 +1,37 @@ +namespace LibMatrix.Homeservers.Extensions.NamedCaches; + +public class NamedCache(AuthenticatedHomeserverGeneric hs, string name) where T : class { + private Dictionary? _cache = new(); + private DateTime _expiry = DateTime.MinValue; + + public async Task> ReadCacheMapAsync() { + _cache = await hs.GetAccountDataOrNullAsync>(name); + + return _cache ?? new(); + } + + public async Task> ReadCacheMapCachedAsync() { + if (_expiry < DateTime.Now || _cache == null) { + _cache = await ReadCacheMapAsync(); + _expiry = DateTime.Now.AddMinutes(5); + } + + return _cache; + } + + public virtual async Task GetValueAsync(string key) { + return (await ReadCacheMapCachedAsync()).GetValueOrDefault(key); + } + + public virtual async Task SetValueAsync(string key, T value) { + var cache = await ReadCacheMapCachedAsync(); + cache[key] = value; + await hs.SetAccountDataAsync(name, cache); + + return value; + } + + public virtual async Task GetOrSetValueAsync(string key, Func> value) { + return (await ReadCacheMapCachedAsync()).GetValueOrDefault(key) ?? await SetValueAsync(key, await value()); + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/Extensions/NamedCaches/NamedFileCache.cs b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedFileCache.cs new file mode 100644 index 0000000..87b7636 --- /dev/null +++ b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedFileCache.cs @@ -0,0 +1,3 @@ +namespace LibMatrix.Homeservers.Extensions.NamedCaches; + +public class NamedFileCache(AuthenticatedHomeserverGeneric hs) : NamedCache(hs, "gay.rory.libmatrix.named_cache.media") { } \ No newline at end of file diff --git a/LibMatrix/Homeservers/Extensions/NamedCaches/NamedFilterCache.cs b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedFilterCache.cs new file mode 100644 index 0000000..76533a4 --- /dev/null +++ b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedFilterCache.cs @@ -0,0 +1,33 @@ +using LibMatrix.Filters; +using LibMatrix.Utilities; + +namespace LibMatrix.Homeservers.Extensions.NamedCaches; + +public class NamedFilterCache(AuthenticatedHomeserverGeneric hs) : NamedCache(hs, "gay.rory.libmatrix.named_cache.filter") { + /// + /// + /// Allows passing a filter directly, or using a common filter. + /// Substitutes @me for the user's ID. + /// + /// Filter name + /// Filter to upload if not cached, otherwise defaults to common filters if that exists. + /// + /// + public async Task GetOrSetValueAsync(string key, SyncFilter? filter = null) { + var existingValue = await GetValueAsync(key); + if (existingValue != null) { + return existingValue; + } + + if (filter is null) { + if(CommonSyncFilters.FilterMap.TryGetValue(key, out var commonFilter)) { + filter = commonFilter; + } else { + throw new ArgumentNullException(nameof(filter)); + } + } + + var filterUpload = await hs.UploadFilterAsync(filter); + return await SetValueAsync(key, filterUpload.FilterId); + } +} \ No newline at end of file -- cgit 1.4.1