moves login and logout to context and adds abstracted generic post method to hide flurl from api implementations

This commit is contained in:
Philipp Häfelfinger 2022-10-16 22:58:38 +02:00
parent cae3f94e39
commit 62679b0c09
10 changed files with 205 additions and 74 deletions

View File

@ -33,7 +33,7 @@ public class CategoryApiTests : ApiTestsBase
}
};
SetJsonResult(serverResponse);
var response = await _categoryApi.GetAllCategoriesAsync();
var response = await _categoryApi.GetAllAsync();
response.Should().HaveCount(3);
response.Should().SatisfyRespectively(c =>

View File

@ -1,10 +1,32 @@
using System.Collections.ObjectModel;
using Flurl.Http;
using Microsoft.Extensions.Logging;
using Piwigo.Client.Contract;
namespace Piwigo.Client;
internal static class DictionaryExtensions
{
public static IDictionary<string, string> AddIfValueNotNull(this IDictionary<string, string> dictionary, string key, string? value)
{
if (dictionary == null)
{
throw new ArgumentNullException(nameof(dictionary));
}
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(key));
}
if (value is not null)
{
dictionary.Add(key, value);
}
return dictionary;
}
}
public class CategoryApi : ICategoryApi
{
private readonly IPiwigoContext _context;
@ -16,11 +38,43 @@ public class CategoryApi : ICategoryApi
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<IReadOnlyCollection<PiwigoCategory>> GetAllCategoriesAsync()
public async Task<int> AddAsync(string name, int? parentId = null, string? comment = null, bool? visible = null, CategoryStatus? status = null, bool? commentable = null,
CategoryPosition? position = null)
{
var statusValue = status switch
{
CategoryStatus.Public => "public",
CategoryStatus.Private => "private",
null => null,
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
};
var positionValue = position switch
{
CategoryPosition.First => "first",
CategoryPosition.Last => "last",
null => null,
_ => throw new ArgumentOutOfRangeException(nameof(position), position, null)
};
var formParams = new Dictionary<string, string> { { "name", name } };
formParams.AddIfValueNotNull("parent", parentId?.ToString()).AddIfValueNotNull("comment", comment).AddIfValueNotNull("visible", visible?.ToString())
.AddIfValueNotNull("status", statusValue).AddIfValueNotNull("commentable", visible?.ToString()).AddIfValueNotNull("position", positionValue);
var response = await _context.PostAsync<PiwigoResponse<AlbumAdded>>(_logger, "pwg.categories.add", formParams);
if (!response.Result.Id.HasValue)
{
throw new PiwigoException($"Could not create album {name}: {response.Result.Info}");
}
return response.Result.Id.Value;
}
public async Task<IReadOnlyCollection<PiwigoCategory>> GetAllAsync()
{
_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);
var formParams = new Dictionary<string, string> { { "recursive", "true" } };
var response = await _context.PostAsync<PiwigoResponse<PiwigoCategoryList>>(_logger, "pwg.categories.getList", formParams);
return new ReadOnlyCollection<PiwigoCategory>(response.Result.Categories);
}
}

View File

@ -0,0 +1,7 @@
namespace Piwigo.Client;
public enum CategoryPosition
{
First = 0,
Last = 1
}

View File

@ -0,0 +1,7 @@
namespace Piwigo.Client;
public enum CategoryStatus
{
Public = 0,
Private = 1
}

View File

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace Piwigo.Client.Contract;
internal class AlbumAdded
{
[JsonProperty("info")]
public string? Info { get; init; }
[JsonProperty("id")]
public int? Id { get; init; }
}

View File

@ -7,6 +7,12 @@ internal class PiwigoResponse<T>
[JsonProperty("stat")]
public string? Status { get; init; }
[JsonProperty("err")]
public int? Error { get; init; }
[JsonProperty("message")]
public string? Message { get; init; }
[JsonProperty("result")]
public T Result { get; init; } = default!;
}

View File

@ -4,5 +4,8 @@ namespace Piwigo.Client;
public interface ICategoryApi
{
Task<IReadOnlyCollection<PiwigoCategory>> GetAllCategoriesAsync();
Task<int> AddAsync(string name, int? parentId = null, string? comment = null, bool? visible = null, CategoryStatus? status = null, bool? commentable = null,
CategoryPosition? position = null);
Task<IReadOnlyCollection<PiwigoCategory>> GetAllAsync();
}

View File

@ -1,13 +1,12 @@
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();
Task LoginAsync();
Task LogoutAsync();
Task<T> PostAsync<T>(ILogger logger, string method, IDictionary<string, string> formParams);
Task<T> PostAsync<T>(ILogger logger, string method);
}

View File

@ -1,49 +1,126 @@
using System.Net;
using Flurl.Http;
using Flurl.Http.Content;
using Microsoft.Extensions.Logging;
namespace Piwigo.Client;
public class PiwigoContext : IPiwigoContext
{
private readonly IPiwigoConfiguration _config;
private readonly CookieJar _cookies = new();
private readonly ILogger<PiwigoContext> _logger;
public PiwigoContext(IPiwigoConfiguration configuration, ILogger<PiwigoContext> logger)
{
Config = configuration ?? throw new ArgumentNullException(nameof(configuration));
_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)
public async Task LoginAsync()
{
var userName = _config.UserName;
if (string.IsNullOrWhiteSpace(userName))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(userName));
}
var password = _config.Password;
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!");
}
_logger.LogInformation("Logging into {PiwigoBaseUri} using username {Username}", _config.BaseUri, userName);
var response = await ConfigureRequest(_logger).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 {_config.BaseUri} using username {userName}");
}
_logger.LogInformation("Logging in succeeded");
_logger.LogInformation("logged in");
IsLoggedIn = true;
}
public async Task LogoutAsync()
{
if (!IsLoggedIn)
{
_logger.LogWarning("Tried to log out from {Uri} but was not logged in!", _config.BaseUri);
return;
}
_logger.LogInformation("Logging out from {Uri}", _config.BaseUri);
await ConfigureRequest(_logger).PostMultipartAsync(c => c.AddMethod("pwg.session.logout"));
_logger.LogInformation("logged out, clearing cookies");
IsLoggedIn = false;
_cookies.Clear();
}
public Task<T> PostAsync<T>(ILogger logger, string method)
{
return PostInternalAsync<T>(logger, method, null);
}
public Task<T> PostAsync<T>(ILogger logger, string method, IDictionary<string, string> formParams)
{
return PostInternalAsync<T>(logger, method, formParams);
}
private static void AddFormParams(CapturedMultipartContent c, IDictionary<string, string> formParams)
{
foreach (var formParam in formParams)
{
c.AddString(formParam.Key, formParam.Value);
}
}
private async Task<T> PostInternalAsync<T>(ILogger logger, string method, IDictionary<string, string>? formParams)
{
await EnsureLoggedInAsync();
var response = await ConfigureRequest(logger).PostMultipartAsync(c =>
{
c.AddMethod(method);
if (formParams != null)
{
AddFormParams(c, formParams);
}
});
var typedResponse = await response.GetJsonAsync<T>();
return typedResponse;
}
private async ValueTask EnsureLoggedInAsync()
{
if (IsLoggedIn)
{
return;
}
await LoginAsync();
}
private IFlurlRequest ConfigureRequest(ILogger logger)
{
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;
return _config.BaseUri.WithCookies(_cookies).ConfigureRequest(r => r.AfterCallAsync = call => LogResponse(call, logger));
}
private static async Task LogResponse(FlurlCall call, ILogger logger)

View File

@ -1,5 +1,3 @@
using System.Net;
using Flurl.Http;
using Microsoft.Extensions.Logging;
using Piwigo.Client.Contract;
@ -16,52 +14,20 @@ internal class SessionApi : ISessionApi
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task LogoutAsync()
public Task LogoutAsync()
{
_logger.LogInformation("Logging out from {Uri}", _context.Config.BaseUri);
await _context.ConfigureRequest(_logger).PostMultipartAsync(c => c.AddMethod("pwg.session.logout"));
_context.LoggedOut();
return _context.LogoutAsync();
}
public async Task LoginAsync()
public 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();
return _context.LoginAsync();
}
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>>();
var typedResponse = await _context.PostAsync<PiwigoResponse<PiwigoStatus>>(_logger, "pwg.session.getStatus");
return typedResponse.Result;
}
}