Skip to content

Commit

Permalink
Merge pull request #119 from jamesmontemagno/vnext
Browse files Browse the repository at this point in the history
Vnext
  • Loading branch information
jamesmontemagno committed Jan 13, 2023
2 parents 128a8ea + d62385f commit 4dae380
Show file tree
Hide file tree
Showing 19 changed files with 413 additions and 458 deletions.
34 changes: 17 additions & 17 deletions README.md
Expand Up @@ -83,25 +83,22 @@ async Task<IEnumerable<Monkey>> GetMonkeysAsync()
var url = "http://montemagno.com/monkeys.json";

//Dev handle online/offline scenario
if(!CrossConnectivity.Current.IsConnected)
if (!CrossConnectivity.Current.IsConnected)
{
return Barrel.Current.Get<IEnumerable<Monkey>>(key: url);
}

//Dev handles checking if cache is expired
if(!Barrel.Current.IsExpired(key: url))
if (!Barrel.Current.IsExpired(key: url))
{
return Barrel.Current.Get<IEnumerable<Monkey>>(key: url);
}


var client = new HttpClient();
var json = await client.GetStringAsync(url);
var monkeys = JsonConvert.DeserializeObject<IEnumerable<Monkey>>(json);
var monkeys = await client.GetFromJsonAsync<IEnumerable<Monkey>>(url);

//Saves the cache and pass it a timespan for expiration
Barrel.Current.Add(key: url, data: monkeys, expireIn: TimeSpan.FromDays(1));

}
```

Expand All @@ -110,30 +107,25 @@ Ideally, you can make these calls extremely generic and just pass in a string:
```csharp
public async Task<T> GetAsync<T>(string url, int days = 7, bool forceRefresh = false)
{
var json = string.Empty;

if (!CrossConnectivity.Current.IsConnected)
json = Barrel.Current.Get<string>(url);
return Barrel.Current.Get<T>(url);

if (!forceRefresh && !Barrel.Current.IsExpired(url))
json = Barrel.Current.Get<string>(url);
return Barrel.Current.Get<T>(url);

try
{
if (string.IsNullOrWhiteSpace(json))
{
json = await client.GetStringAsync(url);
Barrel.Current.Add(url, json, TimeSpan.FromDays(days));
}
return JsonConvert.DeserializeObject<T>(json);
T result = await httpClient.GetFromJsonAsync<T>(url);
Barrel.Current.Add(url, result, TimeSpan.FromDays(days));
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Unable to get information from server {ex}");
//probably re-throw here :)
}

return default(T);
return default;
}
```

Expand Down Expand Up @@ -188,6 +180,14 @@ BarrelUtils.SetBaseCachePath("Path");

You MUST call this before initializing or accessing anything in the Barrel, and it can only ever be called once else it will throw an `InvalidOperationException`.

#### Json Serialization

MonkeyCache v2.0 and higher uses System.Text.Json to serialize objects to/from the backing store. By default, the default System.Text.Json serialization behavior is used. There are two options for controlling this serialization:

1. Pass an optional [JsonSerializationOptions](https://docs.microsoft.com/dotnet/api/system.text.json.jsonserializeroptions) instance to `Barrel.Current.Add` and `Barrel.Current.Get`.
2. Pass a `JsonTypeInfo<T>` instance to `Barrel.Current.Add` and `Barrel.Current.Get`. You can get a `JsonTypeInfo<T>` instance by using the System.Text.Json source generator. See [How to use source generation in System.Text.Json](https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-source-generation) for more information.

No matter which option you choose, it is recommended to use the same option between `Barrel.Current.Add` and `Barrel.Current.Get`. If the options are inconsistent, the information going into the backing store may not be read properly when retrieving it back from the Barrel.

### FAQ

Expand Down
92 changes: 44 additions & 48 deletions src/MonkeyCache.FileStore/Barrel.cs
@@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using Newtonsoft.Json;

namespace MonkeyCache.FileStore
{
public class Barrel : IBarrel
{
ReaderWriterLockSlim indexLocker;
readonly JsonSerializerSettings jsonSettings;
Lazy<string> baseDirectory;
HashAlgorithm hashAlgorithm;

Expand All @@ -36,13 +37,6 @@ public class Barrel : IBarrel

indexLocker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

jsonSettings = new JsonSerializerSettings
{
ObjectCreationHandling = ObjectCreationHandling.Replace,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.All,
};

index = new Dictionary<string, Tuple<string, DateTime>>();

LoadIndex();
Expand All @@ -68,14 +62,7 @@ public class Barrel : IBarrel
public static IBarrel Create(string cacheDirectory, HashAlgorithm hash = null) =>
new Barrel(cacheDirectory, hash);

/// <summary>
/// Adds an entry to the barrel
/// </summary>
/// <param name="key">Unique identifier for the entry</param>
/// <param name="data">Data object to store</param>
/// <param name="expireIn">Time from UtcNow to expire entry in</param>
/// <param name="eTag">Optional eTag information</param>
void Add(string key, string data, TimeSpan expireIn, string eTag = null)
void Add(string key, string data, TimeSpan expireIn, string eTag)
{
indexLocker.EnterWriteLock();

Expand All @@ -99,42 +86,45 @@ void Add(string key, string data, TimeSpan expireIn, string eTag = null)
}
}

/// <summary>
/// Adds an entry to the barrel
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">Unique identifier for the entry</param>
/// <param name="data">Data object to store</param>
/// <param name="expireIn">Time from UtcNow to expire entry in</param>
/// <param name="eTag">Optional eTag information</param>
/// <param name="jsonSerializationSettings">Custom json serialization settings to use</param>
public void Add<T>(string key,
T data,
TimeSpan expireIn,
string eTag = null,
JsonSerializerSettings jsonSerializationSettings = null)
void Add<T>(
string key,
T data,
TimeSpan expireIn,
string eTag,
Func<T, string> serializer)
{

if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key can not be null or empty.", nameof(key));

if (data == null)
throw new ArgumentNullException("Data can not be null.", nameof(data));

var dataJson = string.Empty;

string dataJson;
if (BarrelUtils.IsString(data))
{
dataJson = data as string;
}
else
{
dataJson = JsonConvert.SerializeObject(data, jsonSerializationSettings ?? jsonSettings);
dataJson = serializer(data);
}

Add(key, dataJson, expireIn, eTag);
}

/// <inheritdoc/>
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo, or make sure all of the required types are preserved.")]
public void Add<T>(string key, T data, TimeSpan expireIn, JsonSerializerOptions options = null, string eTag = null) =>
Add(key, data, expireIn, eTag, data => JsonSerialize(data, options));

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Workaround https://github.com/dotnet/linker/issues/2001")]
static string JsonSerialize<T>(T data, JsonSerializerOptions options) =>
JsonSerializer.Serialize(data, options);

/// <inheritdoc/>
public void Add<T>(string key, T data, TimeSpan expireIn, JsonTypeInfo<T> jsonTypeInfo, string eTag = null) =>
Add(key, data, expireIn, eTag, data => JsonSerializer.Serialize(data, jsonTypeInfo));

/// <summary>
/// Empties all specified entries regardless if they are expired.
/// Throws an exception if any deletions fail and rolls back changes.
Expand All @@ -152,7 +142,7 @@ public void Empty(params string[] key)
continue;

var file = Path.Combine(baseDirectory.Value, Hash(k));
if(File.Exists(file))
if (File.Exists(file))
File.Delete(file);

index.Remove(k);
Expand Down Expand Up @@ -180,7 +170,7 @@ public void EmptyAll()
{
var hash = Hash(item.Key);
var file = Path.Combine(baseDirectory.Value, hash);
if(File.Exists(file))
if (File.Exists(file))
File.Delete(file);
}

Expand Down Expand Up @@ -295,13 +285,7 @@ public IEnumerable<string> GetKeys(CacheState state = CacheState.Active)
}
}

/// <summary>
/// Gets the data entry for the specified key.
/// </summary>
/// <param name="key">Unique identifier for the entry to get</param>
/// <param name="jsonSerializationSettings">Custom json serialization settings to use</param>
/// <returns>The data object that was stored if found, else default(T)</returns>
public T Get<T>(string key, JsonSerializerSettings jsonSerializationSettings = null)
T Get<T>(string key, Func<FileStream, T> deserialize)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key can not be null or empty.", nameof(key));
Expand All @@ -317,14 +301,13 @@ public T Get<T>(string key, JsonSerializerSettings jsonSerializationSettings = n

if (index.ContainsKey(key) && File.Exists(path) && (!AutoExpire || (AutoExpire && !IsExpired(key))))
{
var contents = File.ReadAllText(path);
if (BarrelUtils.IsString(result))
{
object final = contents;
return (T)final;
return (T)(object)File.ReadAllText(path);
}

result = JsonConvert.DeserializeObject<T>(contents, jsonSerializationSettings ?? jsonSettings);
using FileStream fileStream = new(path, FileMode.Open, FileAccess.Read);
result = deserialize(fileStream);
}
}
finally
Expand All @@ -335,6 +318,19 @@ public T Get<T>(string key, JsonSerializerSettings jsonSerializationSettings = n
return result;
}

/// <inheritdoc/>
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo, or make sure all of the required types are preserved.")]
public T Get<T>(string key, JsonSerializerOptions options = null) =>
Get(key, fileStream => JsonDeserialize<T>(fileStream, options));

[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Workaround https://github.com/dotnet/linker/issues/2001")]
static T JsonDeserialize<T>(FileStream fileStream, JsonSerializerOptions options) =>
JsonSerializer.Deserialize<T>(fileStream, options);

/// <inheritdoc/>
public T Get<T>(string key, JsonTypeInfo<T> jsonTypeInfo) => Get(key, fileStream =>
JsonSerializer.Deserialize(fileStream, jsonTypeInfo));

/// <summary>
/// Gets the DateTime that the item will expire for the specified key.
/// </summary>
Expand Down
94 changes: 38 additions & 56 deletions src/MonkeyCache.FileStore/MonkeyCache.FileStore.csproj
@@ -1,38 +1,38 @@
<Project Sdk="MSBuild.Sdk.Extras/3.0.44">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;MonoAndroid10.0;Xamarin.iOS10;Xamarin.TVOS10;Xamarin.Mac20;net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);uap10.0.19041;net6.0-windows10.0.19041;net461</TargetFrameworks>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<AssemblyFileVersion>1.0.0.0</AssemblyFileVersion>
<Version>1.0.0.0</Version>
<PackageVersion>1.0.0.0</PackageVersion>
<Authors>James Montemagno</Authors>
<PackageId>MonkeyCache.FileStore</PackageId>
<PackOnBuild>true</PackOnBuild>
<PackageIconUrl>https://raw.githubusercontent.com/jamesmontemagno/monkey-cache/master/art/MonkeyCacheSmall.png</PackageIconUrl>
<NeutralLanguage>en</NeutralLanguage>
<PackageLicenseUrl>https://github.com/jamesmontemagno/monkey-cache/blob/master/LICENSE</PackageLicenseUrl>
<Owners>James Montemagno</Owners>
<PackageProjectUrl>https://github.com/jamesmontemagno/monkey-cache</PackageProjectUrl>
<Summary>A simple caching library to cache any data structure for a specific amount of time in any .NET application.</Summary>
<PackageTags>xamarin, windows, ios, android, cache, http</PackageTags>
<Title>🙈 MonkeyCache.FileStore - A .NET Caching Library</Title>
<Description>A simple caching library to cache any data structure for a specific amount of time in any .NET application. Additionally, offers simple HTTP methods for caching web request data. Powered by FileStore.</Description>

<PackageReleaseNotes>See: https://github.com/jamesmontemagno/monkey-cache </PackageReleaseNotes>
<RepositoryUrl>https://github.com/jamesmontemagno/monkey-cache</RepositoryUrl>
<Copyright>2022 Refractored LLC &amp; James Montemagno</Copyright>

<RootNamespace>MonkeyCache.FileStore</RootNamespace>

<LangVersion>default</LangVersion>

<DefineConstants>$(DefineConstants);FILESTORE</DefineConstants>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile Condition=" '$(Configuration)' == 'Release' ">true</GenerateDocumentationFile>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<AssemblyFileVersion>2.0.0.0</AssemblyFileVersion>
<Version>2.0.0.0</Version>
<PackageVersion>2.0.0.0</PackageVersion>
<Authors>James Montemagno</Authors>
<PackageId>MonkeyCache.FileStore</PackageId>
<PackOnBuild>true</PackOnBuild>
<PackageIconUrl>https://raw.githubusercontent.com/jamesmontemagno/monkey-cache/master/art/MonkeyCacheSmall.png</PackageIconUrl>
<NeutralLanguage>en</NeutralLanguage>
<PackageLicenseUrl>https://github.com/jamesmontemagno/monkey-cache/blob/master/LICENSE</PackageLicenseUrl>
<Owners>James Montemagno</Owners>
<PackageProjectUrl>https://github.com/jamesmontemagno/monkey-cache</PackageProjectUrl>
<Summary>A simple caching library to cache any data structure for a specific amount of time in any .NET application.</Summary>
<PackageTags>xamarin, windows, ios, android, cache, http</PackageTags>
<Title>🙈 MonkeyCache.FileStore - A .NET Caching Library</Title>
<Description>A simple caching library to cache any data structure for a specific amount of time in any .NET application. Additionally, offers simple HTTP methods for caching web request data. Powered by FileStore.</Description>

<PackageReleaseNotes>See: https://github.com/jamesmontemagno/monkey-cache </PackageReleaseNotes>
<RepositoryUrl>https://github.com/jamesmontemagno/monkey-cache</RepositoryUrl>
<Copyright>2022 Refractored LLC &amp; James Montemagno</Copyright>

<RootNamespace>MonkeyCache.FileStore</RootNamespace>

<LangVersion>default</LangVersion>

<DefineConstants>$(DefineConstants);FILESTORE</DefineConstants>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile Condition=" '$(Configuration)' == 'Release' ">true</GenerateDocumentationFile>
<DebugType>portable</DebugType>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)'=='Debug' ">
<DebugSymbols>true</DebugSymbols>
Expand All @@ -49,30 +49,12 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup>
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-ios'))">10.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-tvos'))">10.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-maccatalyst'))">13.1</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-macos'))">10.14</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-android'))">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-windows10'))">10.0.16299.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$(TargetFramework.Contains('-windows10'))">10.0.16299.0</TargetPlatformMinVersion>
</PropertyGroup>
<PropertyGroup Condition=" $(TargetFramework.StartsWith('uap10.0')) ">
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\MonkeyCache.Shared\**\*.cs" LinkBase="Shared" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\MonkeyCache.Shared\**\*.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MonkeyCache\MonkeyCache.csproj" />
<Reference Condition=" '$(TargetFramework)' == 'Xamarin.Mac20' " Include="netstandard" />
<ItemGroup>
<ProjectReference Include="..\MonkeyCache\MonkeyCache.csproj" />
</ItemGroup>

</Project>

0 comments on commit 4dae380

Please sign in to comment.