diff --git a/PiwigoDotnet/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj b/PiwigoDotnet/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj index 36edec5..9bf5783 100644 --- a/PiwigoDotnet/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj +++ b/PiwigoDotnet/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj @@ -9,11 +9,16 @@ - - - - - + + + + + + + + + + diff --git a/PiwigoDotnet/Piwigo.Client.Tests/PiwigoClientTests.cs b/PiwigoDotnet/Piwigo.Client.Tests/PiwigoClientTests.cs new file mode 100644 index 0000000..a507507 --- /dev/null +++ b/PiwigoDotnet/Piwigo.Client.Tests/PiwigoClientTests.cs @@ -0,0 +1,45 @@ +using Flurl.Http.Testing; + +namespace Piwigo.Client.Tests; + +public class PiwigoClientTests +{ + private PiwigoClient _piwigoClient = null!; + private HttpTest _httpTest = null!; + + [SetUp] + public void SetUp() + { + _piwigoClient = new 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 Logout_should_set_IsLoggedIn_to_false() + { + await LoginAsync(); + + _httpTest.RespondWith("OK"); + await _piwigoClient.Logout(); + _piwigoClient.IsLoggedIn.Should().BeFalse(); + } + + private async Task LoginAsync() + { + _httpTest.RespondWith("OK", 200, cookies: new { pwg_id = "pwg_id" }); + await _piwigoClient.LoginAsync(new Uri("http://localhost:8080/foo/bar/ws.php?format=json"), "admin", "admin"); + _piwigoClient.IsLoggedIn.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client.Tests/UnitTest1.cs b/PiwigoDotnet/Piwigo.Client.Tests/UnitTest1.cs deleted file mode 100644 index 8565c93..0000000 --- a/PiwigoDotnet/Piwigo.Client.Tests/UnitTest1.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Piwigo.Client.Tests; - -public class Tests -{ - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } -} \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client.Tests/Usings.cs b/PiwigoDotnet/Piwigo.Client.Tests/Usings.cs index cefced4..25c79ab 100644 --- a/PiwigoDotnet/Piwigo.Client.Tests/Usings.cs +++ b/PiwigoDotnet/Piwigo.Client.Tests/Usings.cs @@ -1 +1,2 @@ -global using NUnit.Framework; \ No newline at end of file +global using NUnit.Framework; +global using FluentAssertions; \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client/CapturedMultipartContentExtensions.cs b/PiwigoDotnet/Piwigo.Client/CapturedMultipartContentExtensions.cs new file mode 100644 index 0000000..fb56758 --- /dev/null +++ b/PiwigoDotnet/Piwigo.Client/CapturedMultipartContentExtensions.cs @@ -0,0 +1,16 @@ +using Flurl.Http.Content; + +namespace Piwigo.Client; + +internal static class CapturedMultipartContentExtensions +{ + public 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); + } +} \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client/IPiwigoClient.cs b/PiwigoDotnet/Piwigo.Client/IPiwigoClient.cs index 6186555..94aa902 100644 --- a/PiwigoDotnet/Piwigo.Client/IPiwigoClient.cs +++ b/PiwigoDotnet/Piwigo.Client/IPiwigoClient.cs @@ -2,5 +2,8 @@ public interface IPiwigoClient { + bool IsLoggedIn { get; } + int ChunkSize { get; set; } Task LoginAsync(Uri uri, string username, string password); + Task Logout(); } \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client/PiwigoClient.cs b/PiwigoDotnet/Piwigo.Client/PiwigoClient.cs new file mode 100644 index 0000000..be8d2dc --- /dev/null +++ b/PiwigoDotnet/Piwigo.Client/PiwigoClient.cs @@ -0,0 +1,69 @@ +using System.Net; +using System.Runtime.CompilerServices; +using Flurl; +using Flurl.Http; + +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); + + 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"); + + var response = await _piwigoBaseUri.WithCookies(out var cookieJar).PostMultipartAsync(c => + c.PiwigoLogin(username, password)); + + if (response.StatusCode != (int)HttpStatusCode.OK) + { + throw new PiwigoException($"Could not log in to {_piwigoBaseUri} using username {username}"); + } + + _cookies = cookieJar; + IsLoggedIn = true; + } + + public async Task Logout() + { + EnsureLoggedIn(); + + await Request.PostMultipartAsync(c => c.PiwigoLogout()); + + IsLoggedIn = false; + _cookies.Clear(); + } + + private void EnsureLoggedIn([CallerMemberName] string? callerName = null) + { + if (!IsLoggedIn) + { + throw new InvalidOperationException($"Could not execute {callerName} as the client is not logged in."); + } + } +} \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client/PiwigoException.cs b/PiwigoDotnet/Piwigo.Client/PiwigoException.cs new file mode 100644 index 0000000..9890caa --- /dev/null +++ b/PiwigoDotnet/Piwigo.Client/PiwigoException.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace Piwigo.Client; + +[Serializable] +public class PiwigoException : Exception +{ + public PiwigoException() + { + } + + protected PiwigoException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public PiwigoException(string? message) : base(message) + { + } + + public PiwigoException(string? message, Exception? innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/PiwigoDotnet/Piwigo.Client/PiwigoMethods.cs b/PiwigoDotnet/Piwigo.Client/PiwigoMethods.cs new file mode 100644 index 0000000..5146e29 --- /dev/null +++ b/PiwigoDotnet/Piwigo.Client/PiwigoMethods.cs @@ -0,0 +1,17 @@ +using Flurl.Http.Content; + +namespace Piwigo.Client; + +internal static class PiwigoMethods +{ + public static CapturedMultipartContent PiwigoLogin(this CapturedMultipartContent part, string username, + string password) + { + return part.AddMethod("pwg.session.login").AddString("username", username).AddString("password", password); + } + + public static CapturedMultipartContent PiwigoLogout(this CapturedMultipartContent part) + { + return part.AddMethod("pwg.session.logout"); + } +} \ No newline at end of file diff --git a/PiwigoDotnet/PiwigoDotnet.sln.DotSettings b/PiwigoDotnet/PiwigoDotnet.sln.DotSettings index 312bea7..7a6b9ad 100644 --- a/PiwigoDotnet/PiwigoDotnet.sln.DotSettings +++ b/PiwigoDotnet/PiwigoDotnet.sln.DotSettings @@ -1,2 +1,6 @@  + Required + Required + Required + Required True \ No newline at end of file