splits client into multiple interfaces and adds a context

adds code format for rider / resharper
This commit is contained in:
Philipp Häfelfinger 2022-10-15 00:07:59 +02:00
parent b2940dc33b
commit e5ba4c6e4a
21 changed files with 475 additions and 277 deletions

View File

@ -0,0 +1,59 @@
using Flurl.Http.Configuration;
using Flurl.Http.Testing;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using Piwigo.Client.Contract;
namespace Piwigo.Client.Tests;
public class ApiTestsBase
{
private const string TestUri = "http://localhost:8080/ws.php?format=json";
private const string Username = "admin";
private const string Password = "admin";
private const bool UseHttpTest = true;
protected HttpTest? HttpTest { get; private set; }
protected IPiwigoContext Context { get; private set; } = null!;
[SetUp]
public void SetUp()
{
if (UseHttpTest)
{
HttpTest = new HttpTest();
}
Context = new PiwigoContext(new PiwigoConfiguration(TestUri, Username, Password), new NullLogger<PiwigoContext>());
OnSetUp();
}
protected virtual void OnSetUp()
{
}
[TearDown]
public void TearDown()
{
HttpTest?.Dispose();
}
protected async Task LoginAsync()
{
HttpTest?.RespondWith("{}", 200, cookies: new { pwg_id = "pwg_id" });
var sessionApi = new SessionApi(Context, new NullLogger<SessionApi>());
await sessionApi.LoginAsync();
Context.IsLoggedIn.Should().BeTrue();
}
internal void SetJsonResult<T>(PiwigoResponse<T> serverResponse)
{
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
var serializer = new NewtonsoftJsonSerializer(settings);
var jsonResponse = serializer.Serialize(serverResponse);
HttpTest?.RespondWith(jsonResponse);
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.Extensions.Logging.Abstractions;
using Piwigo.Client.Contract;
namespace Piwigo.Client.Tests;
[TestFixture]
public class CategoryApiTests : ApiTestsBase
{
private CategoryApi _categoryApi = null!;
protected override void OnSetUp()
{
base.OnSetUp();
_categoryApi = new CategoryApi(Context, new NullLogger<CategoryApi>());
}
[Test]
public async Task GetAllCategories_should_return_all_existing_categories()
{
await LoginAsync();
var serverResponse = new PiwigoResponse<PiwigoCategoryList>
{
Result = new PiwigoCategoryList
{
Categories = new List<PiwigoCategory>
{
new() { Id = 1, Name = "UnitTestMain" },
new() { Id = 3, Name = "UnitTestSub2", IdUpperCat = 1 },
new() { Id = 2, Name = "UnitTestSub1", IdUpperCat = 1 }
}
}
};
SetJsonResult(serverResponse);
var response = await _categoryApi.GetAllCategoriesAsync();
response.Should().HaveCount(3);
response.Should().SatisfyRespectively(c =>
{
c.Id.Should().Be(1);
c.Name.Should().Be("UnitTestMain");
}, c =>
{
c.Id.Should().Be(3);
c.Name.Should().Be("UnitTestSub2");
c.IdUpperCat.Should().Be(1);
}, c =>
{
c.Id.Should().Be(2);
c.Name.Should().Be("UnitTestSub1");
c.IdUpperCat.Should().Be(1);
});
}
}

View File

@ -1,128 +0,0 @@
using Flurl.Http.Configuration;
using Flurl.Http.Testing;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Piwigo.Client.Tests;
[TestFixture]
public class PiwigoClientTests
{
private const string TestUri = "http://localhost:8080/ws.php?format=json";
private const string Username = "admin";
private const string Password = "admin";
private PiwigoClient _piwigoClient = null!;
private HttpTest? _httpTest;
[SetUp]
public void SetUp()
{
_piwigoClient = new PiwigoClient(new NullLogger<PiwigoClient>());
_httpTest = new HttpTest();
}
[TearDown]
public void TearDown()
{
_httpTest?.Dispose();
}
[Test]
public async Task Login_should_set_cookies_and_session()
{
await LoginAsync();
}
[Test]
public async Task GetStatus_should_return_config()
{
await LoginAsync();
var serverResponse = new PiwigoResponse<PiwigoStatus>
{
Status = "OK",
Result = new PiwigoStatus
{
Username = "admin",
Version = "12.0.0"
}
};
SetJsonResult(serverResponse);
var status = await _piwigoClient.GetStatusAsync();
status.Should().NotBeNull();
status.Username.Should().Be("admin");
status.Version.Should().NotBeEmpty();
}
[Test]
public async Task Logout_should_set_IsLoggedIn_to_false()
{
await LoginAsync();
_httpTest?.RespondWith("OK");
await _piwigoClient.LogoutAsync();
_piwigoClient.IsLoggedIn.Should().BeFalse();
}
[Test]
public async Task GetAllCategories_should_return_all_existing_categories()
{
await LoginAsync();
var serverResponse = new PiwigoResponse<PiwigoCategoryList>
{
Result = new PiwigoCategoryList
{
Categories = new List<PiwigoCategory>
{
new() { Id = 1, Name = "UnitTestMain" },
new() { Id = 3, Name = "UnitTestSub2", IdUpperCat = 1 },
new() { Id = 2, Name = "UnitTestSub1", IdUpperCat = 1 }
}
}
};
SetJsonResult(serverResponse);
var response = await _piwigoClient.GetAllCategoriesAsync();
response.Should().HaveCount(3);
response.Should().SatisfyRespectively(c =>
{
c.Id.Should().Be(1);
c.Name.Should().Be("UnitTestMain");
}, c =>
{
c.Id.Should().Be(3);
c.Name.Should().Be("UnitTestSub2");
c.IdUpperCat.Should().Be(1);
}, c =>
{
c.Id.Should().Be(2);
c.Name.Should().Be("UnitTestSub1");
c.IdUpperCat.Should().Be(1);
});
}
private void SetJsonResult<T>(PiwigoResponse<T> serverResponse)
{
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
var serializer = new NewtonsoftJsonSerializer(settings);
var jsonResponse = serializer.Serialize(serverResponse);
_httpTest?.RespondWith(jsonResponse);
}
private async Task LoginAsync()
{
_httpTest?.RespondWith("{}", 200, cookies: new { pwg_id = "pwg_id" });
await _piwigoClient.LoginAsync(new Uri(TestUri), Username, Password);
_piwigoClient.IsLoggedIn.Should().BeTrue();
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.Extensions.Logging.Abstractions;
using Piwigo.Client.Contract;
namespace Piwigo.Client.Tests;
[TestFixture]
public class SessionApiTests : ApiTestsBase
{
private SessionApi _sessionApi = null!;
protected override void OnSetUp()
{
base.OnSetUp();
_sessionApi = new SessionApi(Context, new NullLogger<SessionApi>());
}
[Test]
public async Task Login_should_set_cookies_and_session()
{
await LoginAsync();
}
[Test]
public async Task GetStatus_should_return_config()
{
await LoginAsync();
var serverResponse = new PiwigoResponse<PiwigoStatus>
{
Status = "OK",
Result = new PiwigoStatus
{
Username = "admin",
Version = "12.0.0"
}
};
SetJsonResult(serverResponse);
var status = await _sessionApi.GetStatusAsync();
status.Should().NotBeNull();
status.Username.Should().Be("admin");
status.Version.Should().NotBeEmpty();
}
[Test]
public async Task Logout_should_set_IsLoggedIn_to_false()
{
await LoginAsync();
HttpTest?.RespondWith("OK");
await _sessionApi.LogoutAsync();
Context.IsLoggedIn.Should().BeFalse();
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.ObjectModel;
using Flurl.Http;
using Microsoft.Extensions.Logging;
using Piwigo.Client.Contract;
namespace Piwigo.Client;
public class CategoryApi : ICategoryApi
{
private readonly IPiwigoContext _context;
private readonly ILogger<CategoryApi> _logger;
public CategoryApi(IPiwigoContext context, ILogger<CategoryApi> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<IReadOnlyCollection<PiwigoCategory>> GetAllCategoriesAsync()
{
_logger.LogInformation("Getting all existing categories from server");
var response = await _context.ConfigureRequest(_logger).PostMultipartAsync(c => c.AddMethod("pwg.categories.getList").AddString("recursive", "true"));
var typedResponse = await response.GetJsonAsync<PiwigoResponse<PiwigoCategoryList>>();
return new ReadOnlyCollection<PiwigoCategory>(typedResponse.Result.Categories);
}
}

View File

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace Piwigo.Client;
namespace Piwigo.Client.Contract;
[SuppressMessage("ReSharper", "StringLiteralTypo")]
public class PiwigoCategory

View File

@ -1,6 +1,6 @@
using Newtonsoft.Json;
namespace Piwigo.Client;
namespace Piwigo.Client.Contract;
public class PiwigoCategoryList
{

View File

@ -1,6 +1,6 @@
using Newtonsoft.Json;
namespace Piwigo.Client;
namespace Piwigo.Client.Contract;
internal class PiwigoResponse<T>
{

View File

@ -1,6 +1,6 @@
using Newtonsoft.Json;
namespace Piwigo.Client;
namespace Piwigo.Client.Contract;
public class PiwigoStatus
{

View File

@ -0,0 +1,16 @@
using Flurl.Http.Content;
namespace Piwigo.Client;
internal static class FlurlExtensions
{
internal static CapturedMultipartContent AddMethod(this CapturedMultipartContent part, string piwigoMethod)
{
if (string.IsNullOrWhiteSpace(piwigoMethod))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(piwigoMethod));
}
return part.AddString("method", piwigoMethod);
}
}

View File

@ -0,0 +1,8 @@
using Piwigo.Client.Contract;
namespace Piwigo.Client;
public interface ICategoryApi
{
Task<IReadOnlyCollection<PiwigoCategory>> GetAllCategoriesAsync();
}

View File

@ -2,10 +2,6 @@
public interface IPiwigoClient
{
bool IsLoggedIn { get; }
int ChunkSize { get; set; }
Task LoginAsync(Uri uri, string username, string password);
Task LogoutAsync();
Task<PiwigoStatus> GetStatusAsync();
Task<IReadOnlyCollection<PiwigoCategory>> GetAllCategoriesAsync();
ISessionApi Session { get; }
ICategoryApi Category { get; }
}

View File

@ -0,0 +1,9 @@
namespace Piwigo.Client;
public interface IPiwigoConfiguration
{
public int ChunkSize { get; }
public string BaseUri { get; }
public string UserName { get; }
public string Password { get; }
}

View File

@ -0,0 +1,13 @@
using Flurl.Http;
using Microsoft.Extensions.Logging;
namespace Piwigo.Client;
public interface IPiwigoContext
{
IPiwigoConfiguration Config { get; }
bool IsLoggedIn { get; }
IFlurlRequest ConfigureRequest(ILogger logger, bool requireLogin = true);
void LoggedOut();
void LoggedIn();
}

View File

@ -0,0 +1,10 @@
using Piwigo.Client.Contract;
namespace Piwigo.Client;
public interface ISessionApi
{
Task LoginAsync();
Task LogoutAsync();
Task<PiwigoStatus> GetStatusAsync();
}

View File

@ -1,110 +1,13 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net;
using System.Runtime.CompilerServices;
using Flurl;
using Flurl.Http;
using Microsoft.Extensions.Logging;
namespace Piwigo.Client;
public class PiwigoClient : IPiwigoClient
{
private CookieJar _cookies = new();
private string _piwigoBaseUri = null!;
public bool IsLoggedIn { get; private set; }
public int ChunkSize { get; set; } = 512;
private IFlurlRequest Request => _piwigoBaseUri.WithCookies(_cookies).ConfigureRequest(r => r.AfterCallAsync = LogResponse);
private readonly ILogger<PiwigoClient> _logger;
public PiwigoClient(ILogger<PiwigoClient> logger)
public PiwigoClient(ISessionApi session, ICategoryApi category)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
Session = session;
Category = category;
}
public async Task LoginAsync(Uri uri, string username, string password)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(password));
}
if (IsLoggedIn)
{
throw new PiwigoException("The client is already logged in. Create a new instance or log out first!");
}
_piwigoBaseUri = uri.AppendPathSegment("ws.php").SetQueryParam("format", "json");
_logger.LogInformation("Logging into {PiwigoBaseUri} using username {Username}", _piwigoBaseUri, username);
var response = await _piwigoBaseUri.WithCookies(out var cookieJar).PostMultipartAsync(c =>
c.PiwigoLogin(username, password));
if (response.StatusCode != (int)HttpStatusCode.OK)
{
_logger.LogError("Failed to log in {StatusCode}", response.StatusCode);
throw new PiwigoException($"Could not log in to {_piwigoBaseUri} using username {username}");
}
_logger.LogInformation("Logging in succeeded");
_cookies = cookieJar;
IsLoggedIn = true;
}
public async Task LogoutAsync()
{
EnsureLoggedIn();
_logger.LogInformation("Logging out from {Uri}", _piwigoBaseUri);
await Request.PostMultipartAsync(c => c.PiwigoLogout());
IsLoggedIn = false;
_cookies.Clear();
}
public async Task<PiwigoStatus> GetStatusAsync()
{
EnsureLoggedIn();
_logger.LogInformation("Getting status");
var response = await Request.PostMultipartAsync(c => c.PiwigoGetStatus());
var typedResponse = await response.GetJsonAsync<PiwigoResponse<PiwigoStatus>>();
return typedResponse.Result;
}
public async Task<IReadOnlyCollection<PiwigoCategory>> GetAllCategoriesAsync()
{
EnsureLoggedIn();
_logger.LogInformation("Getting all existing categories from server");
var response = await Request.PostMultipartAsync(c => c.PiwigoGetAllCategories());
var typedResponse = await response.GetJsonAsync<PiwigoResponse<PiwigoCategoryList>>();
return new ReadOnlyCollection<PiwigoCategory>(typedResponse.Result.Categories);
}
private void EnsureLoggedIn([CallerMemberName] string? callerName = null)
{
if (!IsLoggedIn)
{
throw new InvalidOperationException($"Could not execute {callerName} as the client is not logged in.");
}
}
private async Task LogResponse(FlurlCall call)
{
var responseString = await call.Response.GetStringAsync();
_logger.LogDebug("PiwigoResponse: {Response}", responseString);
}
public ISessionApi Session { get; }
public ICategoryApi Category { get; }
}

View File

@ -0,0 +1,31 @@
namespace Piwigo.Client;
public class PiwigoConfiguration : IPiwigoConfiguration
{
public PiwigoConfiguration(string baseUri, string userName, string password)
{
if (string.IsNullOrWhiteSpace(baseUri))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(baseUri));
}
if (string.IsNullOrWhiteSpace(userName))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(userName));
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(password));
}
BaseUri = baseUri;
UserName = userName;
Password = password;
}
public int ChunkSize { get; set; } = 512;
public string BaseUri { get; }
public string UserName { get; }
public string Password { get; }
}

View File

@ -0,0 +1,54 @@
using Flurl.Http;
using Microsoft.Extensions.Logging;
namespace Piwigo.Client;
public class PiwigoContext : IPiwigoContext
{
private readonly CookieJar _cookies = new();
private readonly ILogger<PiwigoContext> _logger;
public PiwigoContext(IPiwigoConfiguration configuration, ILogger<PiwigoContext> logger)
{
Config = configuration ?? throw new ArgumentNullException(nameof(configuration));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public IPiwigoConfiguration Config { get; }
public bool IsLoggedIn { get; private set; }
public IFlurlRequest ConfigureRequest(ILogger logger, bool requireLogin = true)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (requireLogin && !IsLoggedIn)
{
throw new InvalidOperationException("User is not logged in. Ensure login is called before accessing any piwigo methods");
}
return Config.BaseUri.WithCookies(_cookies).ConfigureRequest(r => r.AfterCallAsync = call => LogResponse(call, logger));
}
public void LoggedOut()
{
_logger.LogInformation("logged out, clearing cookies");
IsLoggedIn = false;
_cookies.Clear();
}
public void LoggedIn()
{
_logger.LogInformation("logged in");
IsLoggedIn = true;
}
private static async Task LogResponse(FlurlCall call, ILogger logger)
{
var responseString = await call.Response.GetStringAsync();
logger.LogDebug("PiwigoResponse: {Response}", responseString);
}
}

View File

@ -1,37 +0,0 @@
using Flurl.Http.Content;
namespace Piwigo.Client;
internal static class PiwigoMethods
{
public static void PiwigoLogin(this CapturedMultipartContent part, string username,
string password)
{
part.AddMethod("pwg.session.login").AddString("username", username).AddString("password", password);
}
public static void PiwigoLogout(this CapturedMultipartContent part)
{
part.AddMethod("pwg.session.logout");
}
public static void PiwigoGetStatus(this CapturedMultipartContent part)
{
part.AddMethod("pwg.session.getStatus");
}
public static void PiwigoGetAllCategories(this CapturedMultipartContent part, bool recursive = true)
{
part.AddMethod("pwg.categories.getList").AddString("recursive", recursive.ToString());
}
private static CapturedMultipartContent AddMethod(this CapturedMultipartContent part, string piwigoMethod)
{
if (string.IsNullOrWhiteSpace(piwigoMethod))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(piwigoMethod));
}
return part.AddString("method", piwigoMethod);
}
}

View File

@ -0,0 +1,67 @@
using System.Net;
using Flurl.Http;
using Microsoft.Extensions.Logging;
using Piwigo.Client.Contract;
namespace Piwigo.Client;
internal class SessionApi : ISessionApi
{
private readonly IPiwigoContext _context;
private readonly ILogger<SessionApi> _logger;
public SessionApi(IPiwigoContext context, ILogger<SessionApi> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task LogoutAsync()
{
_logger.LogInformation("Logging out from {Uri}", _context.Config.BaseUri);
await _context.ConfigureRequest(_logger).PostMultipartAsync(c => c.AddMethod("pwg.session.logout"));
_context.LoggedOut();
}
public async Task LoginAsync()
{
var userName = _context.Config.UserName;
if (string.IsNullOrWhiteSpace(userName))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(userName));
}
var password = _context.Config.Password;
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(password));
}
if (_context.IsLoggedIn)
{
throw new PiwigoException("The client is already logged in. Create a new instance or log out first!");
}
_logger.LogInformation("Logging into {PiwigoBaseUri} using username {Username}", _context.Config.BaseUri, userName);
var response = await _context.ConfigureRequest(_logger, false).PostMultipartAsync(c =>
c.AddMethod("pwg.session.login").AddString("username", userName).AddString("password", password));
if (response.StatusCode != (int)HttpStatusCode.OK)
{
_logger.LogError("Failed to log in {StatusCode}", response.StatusCode);
throw new PiwigoException($"Could not log in to {_context.Config.BaseUri} using username {userName}");
}
_logger.LogInformation("Logging in succeeded");
_context.LoggedIn();
}
public async Task<PiwigoStatus> GetStatusAsync()
{
_logger.LogInformation("Getting status");
var response = await _context.ConfigureRequest(_logger).PostMultipartAsync(c => c.AddMethod("pwg.session.getStatus"));
var typedResponse = await response.GetJsonAsync<PiwigoResponse<PiwigoStatus>>();
return typedResponse.Result;
}
}

View File

@ -1,4 +1,61 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=PiwigoCleanup/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="PiwigoCleanup"&gt;&lt;AspOptimizeRegisterDirectives&gt;True&lt;/AspOptimizeRegisterDirectives&gt;&lt;CppAddTypenameTemplateKeywords&gt;True&lt;/CppAddTypenameTemplateKeywords&gt;&lt;CppRemoveCastDescriptor&gt;True&lt;/CppRemoveCastDescriptor&gt;&lt;CppRemoveElseKeyword&gt;True&lt;/CppRemoveElseKeyword&gt;&lt;CppShortenQualifiedName&gt;True&lt;/CppShortenQualifiedName&gt;&lt;CppDeleteRedundantSpecifier&gt;True&lt;/CppDeleteRedundantSpecifier&gt;&lt;CppRemoveStatement&gt;True&lt;/CppRemoveStatement&gt;&lt;CppDeleteRedundantTypenameTemplateKeywords&gt;True&lt;/CppDeleteRedundantTypenameTemplateKeywords&gt;&lt;CppCStyleToStaticCastDescriptor&gt;True&lt;/CppCStyleToStaticCastDescriptor&gt;&lt;CppReplaceExpressionWithBooleanConst&gt;True&lt;/CppReplaceExpressionWithBooleanConst&gt;&lt;CppMakeIfConstexpr&gt;True&lt;/CppMakeIfConstexpr&gt;&lt;CppMakePostfixOperatorPrefix&gt;True&lt;/CppMakePostfixOperatorPrefix&gt;&lt;CppChangeSmartPointerToMakeFunction&gt;True&lt;/CppChangeSmartPointerToMakeFunction&gt;&lt;CppReplaceThrowWithRethrowFix&gt;True&lt;/CppReplaceThrowWithRethrowFix&gt;&lt;CppTypeTraitAliasDescriptor&gt;True&lt;/CppTypeTraitAliasDescriptor&gt;&lt;CppReplaceExpressionWithNullptr&gt;True&lt;/CppReplaceExpressionWithNullptr&gt;&lt;CppReplaceTieWithStructuredBindingDescriptor&gt;True&lt;/CppReplaceTieWithStructuredBindingDescriptor&gt;&lt;CppUseAssociativeContainsDescriptor&gt;True&lt;/CppUseAssociativeContainsDescriptor&gt;&lt;CppUseEraseAlgorithmDescriptor&gt;True&lt;/CppUseEraseAlgorithmDescriptor&gt;&lt;CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /&gt;&lt;CppReformatCode&gt;True&lt;/CppReformatCode&gt;&lt;CSReorderTypeMembers&gt;True&lt;/CSReorderTypeMembers&gt;&lt;CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /&gt;&lt;RemoveCodeRedundanciesVB&gt;True&lt;/RemoveCodeRedundanciesVB&gt;&lt;Xaml.RedundantFreezeAttribute&gt;True&lt;/Xaml.RedundantFreezeAttribute&gt;&lt;Xaml.RemoveRedundantModifiersAttribute&gt;True&lt;/Xaml.RemoveRedundantModifiersAttribute&gt;&lt;Xaml.RemoveRedundantNameAttribute&gt;True&lt;/Xaml.RemoveRedundantNameAttribute&gt;&lt;Xaml.RemoveRedundantResource&gt;True&lt;/Xaml.RemoveRedundantResource&gt;&lt;Xaml.RemoveRedundantCollectionProperty&gt;True&lt;/Xaml.RemoveRedundantCollectionProperty&gt;&lt;Xaml.RemoveRedundantAttachedPropertySetter&gt;True&lt;/Xaml.RemoveRedundantAttachedPropertySetter&gt;&lt;Xaml.RemoveRedundantStyledValue&gt;True&lt;/Xaml.RemoveRedundantStyledValue&gt;&lt;Xaml.RemoveRedundantNamespaceAlias&gt;True&lt;/Xaml.RemoveRedundantNamespaceAlias&gt;&lt;Xaml.RemoveForbiddenResourceName&gt;True&lt;/Xaml.RemoveForbiddenResourceName&gt;&lt;Xaml.RemoveRedundantGridDefinitionsAttribute&gt;True&lt;/Xaml.RemoveRedundantGridDefinitionsAttribute&gt;&lt;Xaml.RemoveRedundantUpdateSourceTriggerAttribute&gt;True&lt;/Xaml.RemoveRedundantUpdateSourceTriggerAttribute&gt;&lt;Xaml.RemoveRedundantBindingModeAttribute&gt;True&lt;/Xaml.RemoveRedundantBindingModeAttribute&gt;&lt;Xaml.RemoveRedundantGridSpanAttribut&gt;True&lt;/Xaml.RemoveRedundantGridSpanAttribut&gt;&lt;XMLReformatCode&gt;True&lt;/XMLReformatCode&gt;&lt;RemoveCodeRedundancies&gt;True&lt;/RemoveCodeRedundancies&gt;&lt;CSUseAutoProperty&gt;True&lt;/CSUseAutoProperty&gt;&lt;CSMakeFieldReadonly&gt;True&lt;/CSMakeFieldReadonly&gt;&lt;CSMakeAutoPropertyGetOnly&gt;True&lt;/CSMakeAutoPropertyGetOnly&gt;&lt;CSArrangeQualifiers&gt;True&lt;/CSArrangeQualifiers&gt;&lt;CSFixBuiltinTypeReferences&gt;True&lt;/CSFixBuiltinTypeReferences&gt;&lt;HtmlReformatCode&gt;True&lt;/HtmlReformatCode&gt;&lt;VBOptimizeImports&gt;True&lt;/VBOptimizeImports&gt;&lt;VBShortenReferences&gt;True&lt;/VBShortenReferences&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;/CSOptimizeUsings&gt;&lt;CSShortenReferences&gt;True&lt;/CSShortenReferences&gt;&lt;VBReformatCode&gt;True&lt;/VBReformatCode&gt;&lt;VBFormatDocComments&gt;True&lt;/VBFormatDocComments&gt;&lt;CSReformatCode&gt;True&lt;/CSReformatCode&gt;&lt;CSharpFormatDocComments&gt;True&lt;/CSharpFormatDocComments&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;IDEA_SETTINGS&gt;&amp;lt;profile version="1.0"&amp;gt;
&amp;lt;option name="myName" value="PiwigoCleanup" /&amp;gt;
&amp;lt;/profile&amp;gt;&lt;/IDEA_SETTINGS&gt;&lt;RIDER_SETTINGS&gt;&amp;lt;profile&amp;gt;
&amp;lt;Language id="CSS"&amp;gt;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="EditorConfig"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="HTML"&amp;gt;
&amp;lt;OptimizeImports&amp;gt;true&amp;lt;/OptimizeImports&amp;gt;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="HTTP Request"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="Handlebars"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="Ini"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="JSON"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="Jade"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="JavaScript"&amp;gt;
&amp;lt;OptimizeImports&amp;gt;true&amp;lt;/OptimizeImports&amp;gt;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="Markdown"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="Properties"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="RELAX-NG"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="SQL"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="XML"&amp;gt;
&amp;lt;OptimizeImports&amp;gt;true&amp;lt;/OptimizeImports&amp;gt;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;Language id="yaml"&amp;gt;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;
&amp;lt;/Language&amp;gt;
&amp;lt;/profile&amp;gt;&lt;/RIDER_SETTINGS&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">PiwigoCleanup</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
@ -6,4 +63,8 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">180</s:Int64>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Piwigo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>