Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 20 additions & 85 deletions src/Couchbase.Lite.Shared/API/DI/Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#endif

using LiteCore.Interop;
using SimpleInjector;
using Microsoft.Extensions.DependencyInjection;

namespace Couchbase.Lite.DI;

Expand All @@ -39,54 +39,56 @@ namespace Couchbase.Lite.DI;
/// </summary>
public static class Service
{
private static readonly Container ServiceCollection = new();
public static readonly ServiceProvider Provider;

[ExcludeFromCodeCoverage]
static Service()
{
ServiceCollection.Options.AllowOverridingRegistrations = true;

var collection = new ServiceCollection();
#if CBL_PLATFORM_DOTNET || CBL_PLATFORM_DOTNETFX
AutoRegister(typeof(Database).GetTypeInfo().Assembly);
AutoRegister(typeof(Database).GetTypeInfo().Assembly, collection);

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
Register<IProxy>(new WindowsProxy());
collection.AddSingleton<IProxy>(new WindowsProxy());
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
Register<IProxy>(new MacProxy());
collection.AddSingleton<IProxy>(new MacProxy());
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
Register<IProxy>(new LinuxProxy());
collection.AddSingleton<IProxy>(new LinuxProxy());
}
#elif CBL_PLATFORM_WINUI
AutoRegister(typeof(Database).GetTypeInfo().Assembly);
AutoRegister(typeof(Database).GetTypeInfo().Assembly, collection);
#elif CBL_PLATFORM_ANDROID
#if !TEST_COVERAGE
if (Droid.Context == null) {
throw new RuntimeException(
"Android context not set. Please ensure that a call to Couchbase.Lite.Support.Droid.Activate() is made.");
}

AutoRegister(typeof(Database).Assembly);
Register<IDefaultDirectoryResolver>(() => new DefaultDirectoryResolver(Droid.Context));
Register<IMainThreadTaskScheduler>(() => new MainThreadTaskScheduler(Droid.Context));
AutoRegister(typeof(Database).Assembly, collection);
collection.AddSingleton<IDefaultDirectoryResolver>(_ => new DefaultDirectoryResolver(Droid.Context));
collection.AddSingleton<IMainThreadTaskScheduler>(_ => new MainThreadTaskScheduler(Droid.Context));
#endif
#elif CBL_PLATFORM_APPLE
AutoRegister(typeof(Database).Assembly);
AutoRegister(typeof(Database).Assembly, collection);
#else
#error Unknown Platform
#endif

Provider = collection.BuildServiceProvider();
}

/// <summary>
/// Automatically register all the dependency types declared
/// <see cref="CouchbaseDependencyAttribute" />s. To auto register classes,
/// they must implement an interface and must have a default constructor.
/// </summary>
/// <param name="assembly"></param>
/// <param name="assembly">The assembly to scan</param>
/// <param name="serviceCollection">The collection to add to</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="assembly"/> is <c>null</c></exception>
/// <exception cref="InvalidOperationException">Thrown if an invalid type is found inside the assembly (i.e.
/// one that does not implement any interfaces and/or does not have a parameter-less constructor)</exception>
[ExcludeFromCodeCoverage]
private static void AutoRegister(Assembly assembly)
private static void AutoRegister(Assembly assembly, IServiceCollection serviceCollection)
{
if (assembly == null) throw new ArgumentNullException(nameof(assembly));

Expand Down Expand Up @@ -117,82 +119,15 @@ private static void AutoRegister(Assembly assembly)
}

if (attribute.Transient) {
ServiceCollection.Register(interfaceType, type, Lifestyle.Transient);
serviceCollection.AddTransient(interfaceType, type);
} else {
if (attribute.Lazy) {
ServiceCollection.Register(interfaceType, type, Lifestyle.Singleton);
serviceCollection.AddSingleton(interfaceType, type);
} else {
ServiceCollection.RegisterInstance(interfaceType, Activator.CreateInstance(type)
serviceCollection.AddSingleton(interfaceType, Activator.CreateInstance(type)
?? throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError, $"Unable to create instance of {type.Name}"));
}
}
}
}

/// <summary>
/// Registers an implementation for the given service
/// </summary>
/// <typeparam name="TService">The service type</typeparam>
/// <typeparam name="TImplementation">The implementation type</typeparam>
/// <param name="transient">If <c>true</c> each call to <see cref="GetInstance{T}"/> will return
/// a new instance, otherwise use a singleton</param>
[ExcludeFromCodeCoverage]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static void Register<TService, TImplementation>(bool transient = false) where TService : class where TImplementation : class, TService
{
Lifestyle style = transient ? Lifestyle.Transient : Lifestyle.Singleton;
ServiceCollection.Register<TService, TImplementation>(style);
}

/// <summary>
/// Registers a lazy implementation for the given service
/// </summary>
/// <typeparam name="TService">The service type</typeparam>
/// <param name="generator">The function that creates the object to use</param>
/// <param name="transient">If <c>true</c> each call to <see cref="GetInstance{T}"/> will return
/// a new instance, otherwise use a singleton</param>
[ExcludeFromCodeCoverage]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static void Register<TService>(Func<TService> generator, bool transient = false) where TService : class
{
var style = transient ? Lifestyle.Transient : Lifestyle.Singleton;
ServiceCollection.Register(generator, style);
}

/// <summary>
/// Registers an instantiated object as a singleton implementation for a service
/// </summary>
/// <typeparam name="TService">The service type</typeparam>
/// <param name="instance">The singleton instance to use as the implementation</param>
[ExcludeFromCodeCoverage]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public static void Register<TService>(TService instance)
where TService : class
{
ServiceCollection.RegisterInstance(instance);
}

/// <summary>
/// Gets the implementation for the given service, or <c>null</c>
/// if no implementation is registered
/// </summary>
/// <typeparam name="T">The type of service to get an implementation for</typeparam>
/// <returns>The implementation for the given service</returns>
public static T? GetInstance<T>() where T : class
{
try {
return ServiceCollection.GetInstance<T>();
} catch (ActivationException) {
return null;
}
}

internal static T GetRequiredInstance<T>() where T : class
{
return GetInstance<T>() ?? throw new InvalidOperationException(
$"""
A required dependency injection class is missing ({typeof(T).FullName}).
If this is not a custom platform, please file a bug report at https://github.com/couchbase/couchbase-lite-net/issues
""");
}
}
4 changes: 2 additions & 2 deletions src/Couchbase.Lite.Shared/API/Database/Collection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
using LiteCore;
using LiteCore.Interop;
using LiteCore.Util;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Text.Json;

namespace Couchbase.Lite;

Expand Down Expand Up @@ -534,7 +534,7 @@ public void CreateIndex(string name, IIndex index)
CheckCollectionValid();
var concreteIndex = Misc.TryCast<IIndex, QueryIndex>(index);
var jsonObj = concreteIndex.ToJSON();
var json = JsonConvert.SerializeObject(jsonObj);
var json = JsonSerializer.Serialize(jsonObj);
LiteCoreBridge.Check(err =>
{
var internalOpts = concreteIndex.Options;
Expand Down
3 changes: 2 additions & 1 deletion src/Couchbase.Lite.Shared/API/Database/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;

namespace Couchbase.Lite;

Expand Down Expand Up @@ -761,7 +762,7 @@ internal void CheckOpenLocked()
private static string DatabasePath(string? directory)
{
var directoryToUse = String.IsNullOrWhiteSpace(directory)
? Service.GetRequiredInstance<IDefaultDirectoryResolver>().DefaultDirectory()
? Service.Provider.GetRequiredService<IDefaultDirectoryResolver>().DefaultDirectory()
: directory;

if (String.IsNullOrWhiteSpace(directoryToUse)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Couchbase.Lite.Info;
using Couchbase.Lite.Internal.Logging;
using Couchbase.Lite.Util;
using Microsoft.Extensions.DependencyInjection;

#if NET8_0_OR_GREATER
using System.Runtime.Versioning;
Expand All @@ -41,7 +42,7 @@ public sealed partial record DatabaseConfiguration
/// </summary>
public string Directory
{
get => _directory ?? Service.GetRequiredInstance<IDefaultDirectoryResolver>().DefaultDirectory();
get => _directory ?? Service.Provider.GetRequiredService<IDefaultDirectoryResolver>().DefaultDirectory();
init => _directory = CBDebug.MustNotBeNull(WriteLog.To.Database, Tag, "Directory", value);
}

Expand Down
104 changes: 54 additions & 50 deletions src/Couchbase.Lite.Shared/API/Document/ArrayObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
using System;
using System.Collections;
using System.Collections.Generic;

using System.Text.Json;
using System.Text.Json.Serialization;
using Couchbase.Lite.Fleece;
using Couchbase.Lite.Internal.Doc;
using Couchbase.Lite.Internal.Serialization;
using Couchbase.Lite.Support;
using LiteCore;
using LiteCore.Interop;
using Newtonsoft.Json;

namespace Couchbase.Lite;

Expand Down Expand Up @@ -171,72 +171,76 @@ IEnumerator IEnumerable.GetEnumerator()
}

[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class IArrayConverter : JsonConverter
internal sealed class IArrayConverter : JsonConverter<IArray>
{
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var arr = value as IArray ?? throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError,
"Invalid input received in WriteJson (not IArray)");
writer.WriteStartArray();
foreach (var item in arr) {
serializer.Serialize(writer, item);
}

writer.WriteEndArray();
}

public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
public override IArray Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var arr = new MutableArrayObject();
while (reader.Read()) {

while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) {
switch (reader.TokenType) {
case JsonToken.EndObject:
case JsonTokenType.StartObject:
{
var val = serializer.Deserialize(reader) as IDictionary<string, object>
?? throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError, "Corrupt input received in ReadJson (EndObject != IDictionary)");
if (val.TryGetValue(Constants.ObjectTypeProperty, out var value) &&
value.Equals(Constants.ObjectTypeBlob)) {
var blob = serializer.Deserialize<Blob>(reader)
?? throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError, "Error deserializing blob in ReadJson (IArray)");
using var document = JsonDocument.ParseValue(ref reader);
var element = document.RootElement;
if (element.TryGetProperty(Constants.ObjectTypeProperty, out var prop)
&& prop.GetString() == Constants.ObjectTypeBlob) {
var blob = element.Deserialize<Blob>(options) ??
throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError,
"Error deserializing blob in ReadJson (IArray)");
arr.AddBlob(blob);
} else {
var dict = serializer.Deserialize<MutableDictionaryObject>(reader)
?? throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError, "Error deserializing dict in ReadJson (IArray)");
}
else {
var dict = element.Deserialize<MutableDictionaryObject>(options) ??
throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError,
"Error deserializing dict in ReadJson (IArray)");
arr.AddValue(dict);
}
break;
}
case JsonToken.EndArray:
{
var array = serializer.Deserialize<MutableArrayObject>(reader)
?? throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError, "Error deserializing array in ReadJson (IArray)");
case JsonTokenType.StartArray:
var array = JsonSerializer.Deserialize<MutableArrayObject>(ref reader) ??
throw new CouchbaseLiteException(C4ErrorCode.UnexpectedError,
"Error deserializing array in ReadJson (IArray)");
arr.AddValue(array);
break;
}
case JsonToken.None:
case JsonToken.StartObject:
case JsonToken.StartArray:
case JsonToken.StartConstructor:
case JsonToken.PropertyName:
case JsonToken.Comment:
case JsonToken.Raw:
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Null:
case JsonToken.Undefined:
case JsonToken.EndConstructor:
case JsonToken.Date:
case JsonToken.Bytes:
case JsonTokenType.String:
arr.AddValue(reader.GetString());
break;
case JsonTokenType.Number:
if (reader.TryGetInt64(out var longValue)) {
arr.AddValue(longValue);
} else {
arr.AddValue(reader.GetDouble());
}
break;
case JsonTokenType.True:
case JsonTokenType.False:
arr.AddValue(reader.GetBoolean());
break;
case JsonTokenType.Null:
arr.AddValue(null);
break;
case JsonTokenType.None:
case JsonTokenType.EndObject:
case JsonTokenType.EndArray:
case JsonTokenType.PropertyName:
case JsonTokenType.Comment:
default:
arr.AddValue(reader.Value);
break;
}
}

return arr;
}

public override bool CanConvert(Type objectType) => typeof(IArray).IsAssignableFrom(objectType);
public override void Write(Utf8JsonWriter writer, IArray value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (var item in value) {
JsonSerializer.Serialize(writer, item, options);
}

writer.WriteEndArray();
}
}
Loading