about summary refs log tree commit diff
path: root/LibMatrix/Homeservers
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-03-15 18:10:58 +0100
committerRory& <root@rory.gay>2024-03-15 18:11:18 +0100
commit096375344ef87fe53ca009b7a7eaa34c9c9f5407 (patch)
tree76d666cd6961ca04ae9e91e47c43d91eed27a87a /LibMatrix/Homeservers
parentFix README (diff)
downloadLibMatrix-096375344ef87fe53ca009b7a7eaa34c9c9f5407.tar.xz
Bot changes, move named filters to subclass
Diffstat (limited to '')
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs93
-rw-r--r--LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs37
-rw-r--r--LibMatrix/Homeservers/Extensions/NamedCaches/NamedFileCache.cs3
-rw-r--r--LibMatrix/Homeservers/Extensions/NamedCaches/NamedFilterCache.cs33
4 files changed, 109 insertions, 57 deletions
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<WhoAmIResponse>("/_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<WhoAmIResponse>("/_matrix/client/v3/account/whoami");
     }
 
+    /// <summary>
+    ///   Upload a filter to the homeserver. Substitutes @me with the user's ID.
+    /// </summary>
+    /// <param name="filter"></param>
+    /// <returns></returns>
+    /// <exception cref="Exception"></exception>
     public async Task<FilterIdResponse> UploadFilterAsync(SyncFilter filter) {
         List<List<string>?> senderLists = [
             filter.AccountData?.Senders,
@@ -326,69 +337,21 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
         return _filterCache[filterId] = await resp.Content.ReadFromJsonAsync<SyncFilter>() ?? throw new Exception("Failed to get filter?");
     }
 
-#region Named filters
-
-    private async Task<Dictionary<string, string>?> GetNamedFilterListOrNullAsync(bool cached = true) {
-        if (cached && _namedFilterCache is not null) return _namedFilterCache;
-        try {
-            return _namedFilterCache = await GetAccountDataAsync<Dictionary<string, string>>("gay.rory.libmatrix.named_filters");
-        }
-        catch (MatrixException e) {
-            if (e is not { ErrorCode: "M_NOT_FOUND" }) throw;
-        }
-
-        return null;
-    }
-
-    /// <summary>
-    /// Utility function to allow avoiding serverside duplication
-    /// </summary>
-    /// <param name="filterName">Name of the filter (<i>please</i> properly namespace and possibly version this...)</param>
-    /// <param name="filter">The filter data</param>
-    /// <returns>Filter ID response</returns>
-    /// <exception cref="Exception"></exception>
-    public async Task<FilterIdResponse> UploadNamedFilterAsync(string filterName, SyncFilter filter) {
-        var idResp = await UploadFilterAsync(filter);
-
-        var filterList = await GetNamedFilterListOrNullAsync() ?? new Dictionary<string, string>();
-        filterList[filterName] = idResp.FilterId;
-        await SetAccountDataAsync("gay.rory.libmatrix.named_filters", filterList);
-
-        _namedFilterCache = filterList;
-
-        return idResp;
-    }
-
-    public async Task<string?> GetNamedFilterIdOrNullAsync(string filterName) {
-        var filterList = await GetNamedFilterListOrNullAsync() ?? new Dictionary<string, string>();
-        return filterList.GetValueOrDefault(filterName); //todo: validate that filter exists
-    }
-
-    public async Task<SyncFilter?> GetNamedFilterOrNullAsync(string filterName) {
-        var filterId = await GetNamedFilterIdOrNullAsync(filterName);
-        if (filterId is null) return null;
-        return await GetFilterAsync(filterId);
-    }
-
-    public async Task<string?> 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; }
     }
 
+    /// <summary>
+    ///   Enumerate all account data per room.
+    ///   <b>Warning</b>: This uses /sync!
+    /// </summary>
+    /// <param name="includeGlobal">Include non-room account data</param>
+    /// <returns>Dictionary of room IDs and their account data.</returns>
+    /// <exception cref="Exception"></exception>
     public async Task<Dictionary<string, EventList?>> 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<string, EventList?>();
@@ -400,9 +363,15 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke
         return perRoomAccountData;
     }
 
+    /// <summary>
+    ///   Enumerate all non-room account data.
+    ///   <b>Warning</b>: This uses /sync!
+    /// </summary>
+    /// <returns>All account data.</returns>
+    /// <exception cref="Exception"></exception>
     public async Task<EventList?> 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<JsonObject>();
     }
+
+    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<T>(AuthenticatedHomeserverGeneric hs, string name) where T : class {
+    private Dictionary<string, T>? _cache = new();
+    private DateTime _expiry = DateTime.MinValue;
+    
+    public async Task<Dictionary<string, T>> ReadCacheMapAsync() {
+        _cache = await hs.GetAccountDataOrNullAsync<Dictionary<string, T>>(name);
+
+        return _cache ?? new();
+    }
+    
+    public async Task<Dictionary<string,T>> ReadCacheMapCachedAsync() {
+        if (_expiry < DateTime.Now || _cache == null) {
+            _cache = await ReadCacheMapAsync();
+            _expiry = DateTime.Now.AddMinutes(5);
+        }
+
+        return _cache;
+    }
+    
+    public virtual async Task<T?> GetValueAsync(string key) {
+        return (await ReadCacheMapCachedAsync()).GetValueOrDefault(key);
+    }
+    
+    public virtual async Task<T> SetValueAsync(string key, T value) {
+        var cache = await ReadCacheMapCachedAsync();
+        cache[key] = value;
+        await hs.SetAccountDataAsync(name, cache);
+
+        return value;
+    }
+    
+    public virtual async Task<T> GetOrSetValueAsync(string key, Func<Task<T>> 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<string>(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<string>(hs, "gay.rory.libmatrix.named_cache.filter") {
+    /// <summary>
+    ///   <inheritdoc cref="NamedCache{T}.GetOrSetValueAsync"/>
+    ///   Allows passing a filter directly, or using a common filter.
+    ///   Substitutes @me for the user's ID.
+    /// </summary>
+    /// <param name="key">Filter name</param>
+    /// <param name="filter">Filter to upload if not cached, otherwise defaults to common filters if that exists.</param>
+    /// <returns></returns>
+    /// <exception cref="ArgumentNullException"></exception>
+    public async Task<string> 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