Skip to content

Commit

Permalink
Merge pull request #23 from JW-CH/dev.openapi
Browse files Browse the repository at this point in the history
Change calls to generated API via OpenApi
  • Loading branch information
3rob3 authored Mar 16, 2024
2 parents 691d196 + 5dfb36d commit a14af2e
Show file tree
Hide file tree
Showing 8 changed files with 10,821 additions and 100 deletions.
125 changes: 55 additions & 70 deletions ImmichFrame/Helpers/AssetHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,31 @@
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace ImmichFrame.Helpers
{
public class AssetHelper
{
private Dictionary<Guid, AssetInfo> _memoryAssetInfos;
private Task<Dictionary<Guid, AssetResponseDto>> _memoryAssetInfos;

Check warning on line 14 in ImmichFrame/Helpers/AssetHelper.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_memoryAssetInfos' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 14 in ImmichFrame/Helpers/AssetHelper.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_memoryAssetInfos' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
private DateTime lastMemoryAssetRefesh;
public Dictionary<Guid, AssetInfo> MemoryAssetInfos
public Task<Dictionary<Guid, AssetResponseDto>> MemoryAssetInfos
{
get
{
if (_memoryAssetInfos == null || lastMemoryAssetRefesh.DayOfYear != DateTime.Today.DayOfYear)
{
lastMemoryAssetRefesh = DateTime.Now;
_memoryAssetInfos = GetMemoryAssetIds().ToDictionary(x => Guid.Parse(x.Id));
_memoryAssetInfos = GetMemoryAssetIds();
}

return _memoryAssetInfos;
}
}

private Dictionary<Guid, AssetInfo> _albumAssetInfos;
private Task<Dictionary<Guid, AssetResponseDto>> _albumAssetInfos;

Check warning on line 30 in ImmichFrame/Helpers/AssetHelper.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_albumAssetInfos' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 30 in ImmichFrame/Helpers/AssetHelper.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_albumAssetInfos' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
private DateTime lastAlbumAssetRefesh;
public Dictionary<Guid, AssetInfo> AlbumAssetInfos
public Task<Dictionary<Guid, AssetResponseDto>> AlbumAssetInfos
{
get
{
Expand All @@ -37,137 +38,121 @@ public Dictionary<Guid, AssetInfo> AlbumAssetInfos
if (_albumAssetInfos == null || lastAlbumAssetRefesh.AddDays(1) < DateTime.Now)
{
lastAlbumAssetRefesh = DateTime.Now;
_albumAssetInfos = GetAlbumAssetIds().ToDictionary(x => Guid.Parse(x.Id));
_albumAssetInfos = GetAlbumAssetIds();
}

return _albumAssetInfos;
}
}

public AssetInfo? GetNextAsset()
public async Task<AssetResponseDto?> GetNextAsset()
{
if (Settings.CurrentSettings.OnlyMemories)
{
return GetRandomMemoryAsset();
return await GetRandomMemoryAsset();
}

return Settings.CurrentSettings.Albums.Any() ? GetRandomAlbumAsset() : GetRandomAsset();
return Settings.CurrentSettings.Albums.Any() ? await GetRandomAlbumAsset() : await GetRandomAsset();
}

private IEnumerable<AssetInfo> GetMemoryAssetIds()
private async Task<Dictionary<Guid, AssetResponseDto>> GetMemoryAssetIds()
{
using (var client = new HttpClient())
{
var settings = Settings.CurrentSettings;
var allAssets = new List<AssetInfo>();

client.UseApiKey(settings.ApiKey);

var date = DateTime.Today;

string url = $"{settings.ImmichServerUrl}/api/asset/memory-lane?day={date.Day}&month={date.Month}";

var response = client.GetAsync(url).Result;

if (!response.IsSuccessStatusCode)
throw new AlbumNotFoundException($"Memories could not be loaded, check your settings file");
var immichApi = new ImmichApi(settings.ImmichServerUrl, client);

var responseContent = response.Content.ReadAsStringAsync().Result;
var allAssets = new List<AssetResponseDto>();

var albumInfo = JsonDocument.Parse(responseContent);
var date = DateTime.Today;

var years = albumInfo.RootElement.EnumerateArray().Cast<JsonElement>().ToList();
var memoryLane = await immichApi.GetMemoryLaneAsync(date.Day, date.Month);

foreach (var year in years)
foreach (var lane in memoryLane)
{
var assets = year.GetProperty("assets").ToString() ?? string.Empty;
var assetList = JsonSerializer.Deserialize<IEnumerable<AssetInfo>>(assets) ?? new List<AssetInfo>();

var title = year.GetProperty("title").ToString() ?? string.Empty;

assetList.ToList().ForEach(asset => asset.ImageDesc = title);
var assets = lane.Assets.ToList();
assets.ForEach(asset => asset.ImageDesc = lane.Title);

allAssets.AddRange(assetList);
allAssets.AddRange(assets);
}

return allAssets;
return allAssets.ToDictionary(x => Guid.Parse(x.Id));
}
}

private IEnumerable<AssetInfo> GetAlbumAssetIds()
private async Task<Dictionary<Guid, AssetResponseDto>> GetAlbumAssetIds()
{
using (var client = new HttpClient())
{
var allAssets = new List<AssetInfo>();
var allAssets = new List<AssetResponseDto>();
var settings = Settings.CurrentSettings;

client.UseApiKey(settings.ApiKey);
foreach (var albumId in settings.Albums!)
{
string url = $"{settings.ImmichServerUrl}/api/album/{albumId}";

var response = client.GetAsync(url).Result;

if (!response.IsSuccessStatusCode)
throw new AlbumNotFoundException($"Album '{albumId}' was not found, check your settings file");

var responseContent = response.Content.ReadAsStringAsync().Result;

var albumInfo = JsonDocument.Parse(responseContent);

var assets = albumInfo.RootElement.GetProperty("assets").ToString() ?? string.Empty;
var x = new ImmichApi(settings.ImmichServerUrl, client);

var assetList = JsonSerializer.Deserialize<IEnumerable<AssetInfo>>(assets) ?? new List<AssetInfo>();

allAssets.AddRange(assetList);
client.UseApiKey(settings.ApiKey);
foreach (var albumId in settings.Albums!)
{
try
{
var albumInfo = await x.GetAlbumInfoAsync(albumId, null, null);

allAssets.AddRange(albumInfo.Assets);
}
catch (ApiException ex)
{
throw new AlbumNotFoundException($"Album '{albumId}' was not found, check your settings file", ex);
}
}

return allAssets;
return allAssets.ToDictionary(x => Guid.Parse(x.Id));
}
}

private Random _random = new Random();
private AssetInfo? GetRandomAlbumAsset()
private async Task<AssetResponseDto?> GetRandomAlbumAsset()
{
if (!AlbumAssetInfos.Any())
var albumAssetInfos = await AlbumAssetInfos;
if (!albumAssetInfos.Any())
throw new AssetNotFoundException();

var rnd = _random.Next(AlbumAssetInfos.Count);
var rnd = _random.Next(albumAssetInfos.Count);

return AlbumAssetInfos.ElementAt(rnd).Value;
return albumAssetInfos.ElementAt(rnd).Value;
}
private AssetInfo? GetRandomMemoryAsset()

private async Task<AssetResponseDto?> GetRandomMemoryAsset()
{
if (!MemoryAssetInfos.Any())
var memoryAssetInfos = await MemoryAssetInfos;
if (!memoryAssetInfos.Any())
throw new AssetNotFoundException();

var rnd = _random.Next(MemoryAssetInfos.Count);
var rnd = _random.Next(memoryAssetInfos.Count);

return MemoryAssetInfos.ElementAt(rnd).Value;
return memoryAssetInfos.ElementAt(rnd).Value;
}
private AssetInfo? GetRandomAsset()
private async Task<AssetResponseDto?> GetRandomAsset()
{
AssetInfo? returnAsset = null;
var settings = Settings.CurrentSettings;

string url = $"{settings.ImmichServerUrl}/api/asset/random";
using (var client = new HttpClient())
{
client.UseApiKey(settings.ApiKey);
var response = client.GetAsync(url).Result;

if (!response.IsSuccessStatusCode)
throw new AssetNotFoundException();
var immichApi = new ImmichApi(settings.ImmichServerUrl, client);

var randomAssets = await immichApi.GetRandomAsync(null);

var responseContent = response.Content.ReadAsStringAsync().Result;
var assetList = JsonSerializer.Deserialize<List<AssetInfo>>(responseContent);
if (assetList != null)
if (randomAssets.Any())
{
returnAsset = assetList.FirstOrDefault();
return randomAssets.First();
}
}

return returnAsset;
return null;
}
}
}
15 changes: 15 additions & 0 deletions ImmichFrame/ImmichFrame.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
</Content>
</ItemGroup>

<ItemGroup>
<OpenApiReference Include="OpenAPIs\immich-openapi-specs.json" CodeGenerator="NSwagCSharp" Namespace="ImmichFrame.Models" ClassName="ImmichApi">
<SourceUri>https://raw.githubusercontent.com/immich-app/immich/main/open-api/immich-openapi-specs.json</SourceUri>
</OpenApiReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
Expand All @@ -31,6 +37,15 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.18.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,34 @@

namespace ImmichFrame.Models;

// Data from JSON partial
public partial class AssetInfo
{
[JsonPropertyName("id")]
public string Id { get; set; }

[JsonPropertyName("fileCreatedAt")]
public DateTime FileCreatedAt { get; set; }
}

// Additional data partial
public partial class AssetInfo
public partial class AssetResponseDto
{
[JsonIgnore]
public string ImageUrl => $"{Settings.CurrentSettings.ImmichServerUrl}/api/asset/thumbnail/{Id}?format={ImageExt}";
private string _imageDesc;

Check warning on line 14 in ImmichFrame/Models/AssetResponseDto.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_imageDesc' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

[JsonIgnore]
public string ImageExt => "JPEG";
public string ImageDesc
{
get
{
if (string.IsNullOrWhiteSpace(_imageDesc))
return this.ExifInfo?.Description ?? string.Empty;

[JsonIgnore]
public string ImageDesc { get; set;}
return _imageDesc;
}
set
{
_imageDesc = value;
}
}

[JsonIgnore]
public Task<Stream> AssetImage => ServeImage();

private async Task<Stream> ServeImage()
{
var localPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Immich_Assets", $"{Id}.{ImageExt}");
var localPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Immich_Assets", $"{Id}.{ThumbnailFormat.JPEG}");
var localDir = Path.GetDirectoryName(localPath);
if (!Directory.Exists(localDir))
{
Expand All @@ -59,23 +59,31 @@ private async Task<Stream> DownloadImageAsync(string localPath)
{
using (var client = new HttpClient())
{
client.UseApiKey(Settings.CurrentSettings.ApiKey);
var data = await client.GetByteArrayAsync(this.ImageUrl);
var settings = Settings.CurrentSettings;

client.UseApiKey(settings.ApiKey);

var immichApi = new ImmichApi(settings.ImmichServerUrl, client);

var data = await immichApi.GetAssetThumbnailAsync(ThumbnailFormat.JPEG, Guid.Parse(this.Id), null);

var stream = new MemoryStream(data);
var stream = data.Stream;
var ms = new MemoryStream();
stream.CopyTo(ms);
if (Settings.CurrentSettings.DownloadImages)
{
// save to folder
using (var fs = File.Create(localPath))
{
stream.CopyTo(fs);
stream.Position = 0;
return stream;
ms.Position = 0;
ms.CopyTo(fs);
ms.Position = 0;
return ms;
}
}
else
{
return stream;
return ms;
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions ImmichFrame/Models/ImmichApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ImmichFrame.Models
{
public partial class ImmichApi
{
public ImmichApi(string url, System.Net.Http.HttpClient httpClient)
{
_baseUrl = url+_baseUrl;
_httpClient = httpClient;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
}
}
4 changes: 2 additions & 2 deletions ImmichFrame/Models/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace ImmichFrame.Models;

public class Settings
{
public string? ImmichServerUrl { get; set; }
public string ImmichServerUrl { get; set; }

Check warning on line 12 in ImmichFrame/Models/Settings.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'ImmichServerUrl' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public string ApiKey { get; set; }

Check warning on line 13 in ImmichFrame/Models/Settings.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'ApiKey' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public int Interval { get; set; }
public bool DownloadImages { get; set; }
Expand Down Expand Up @@ -46,7 +46,7 @@ private static Settings Parse()
{
var xml = File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + @"Settings.xml");

var doc = XDocument.Parse(xml).Root;
var doc = XDocument.Parse(xml).Root!;

var settings = new Settings
{
Expand Down
Loading

0 comments on commit a14af2e

Please sign in to comment.