mirror of
https://github.com/mxpv/podsync.git
synced 2024-05-11 05:55:04 +00:00
Rework Redis serialization
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
namespace Podsync.Services
|
||||
using Podsync.Services.Resolver;
|
||||
|
||||
namespace Podsync.Services
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const int DefaultPageSize = 50;
|
||||
|
||||
public const ResolveType DefaultFormat = ResolveType.VideoHigh;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@ using Podsync.Services.Resolver;
|
||||
|
||||
namespace Podsync.Services.Storage
|
||||
{
|
||||
public struct FeedMetadata
|
||||
public class FeedMetadata
|
||||
{
|
||||
public Provider Provider { get; set; }
|
||||
|
||||
public LinkType LinkType { get; set; }
|
||||
public LinkType Type { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
@@ -16,5 +16,12 @@ namespace Podsync.Services.Storage
|
||||
public int PageSize { get; set; }
|
||||
|
||||
public override string ToString() => $"{Provider} ({LinkType}) {Id}";
|
||||
|
||||
// Workaround for backward compatibility
|
||||
public LinkType LinkType
|
||||
{
|
||||
get { return Type; }
|
||||
set { Type = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using HashidsNet;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Podsync.Services.Links;
|
||||
using Podsync.Services.Resolver;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Podsync.Services.Storage
|
||||
@@ -18,16 +19,6 @@ namespace Podsync.Services.Storage
|
||||
private const string IdSalt = "65fce519433f4218aa0cee6394225eea";
|
||||
private const int IdLength = 4;
|
||||
|
||||
// Store all fields manually for backward compatibility with existing implementation
|
||||
private const string ProviderField = "provider";
|
||||
private const string TypeField = "type";
|
||||
private const string IdField = "id";
|
||||
private const string QualityField = "quality";
|
||||
private const string PageSizeField = "pageSize";
|
||||
|
||||
private const ResolveType DefaultQuality = ResolveType.VideoHigh;
|
||||
private const int DefaultPageSize = 50;
|
||||
|
||||
private static readonly IHashids HashIds = new Hashids(IdSalt, IdLength);
|
||||
|
||||
private readonly string _cs;
|
||||
@@ -85,13 +76,21 @@ namespace Podsync.Services.Storage
|
||||
{
|
||||
var id = await MakeId();
|
||||
|
||||
if (await Db.KeyExistsAsync(id))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to generate feed id");
|
||||
}
|
||||
|
||||
await Db.HashSetAsync(id, new[]
|
||||
{
|
||||
new HashEntry(ProviderField, metadata.Provider.ToString()),
|
||||
new HashEntry(TypeField, metadata.LinkType.ToString()),
|
||||
new HashEntry(IdField, metadata.Id),
|
||||
new HashEntry(QualityField, metadata.Quality.ToString()),
|
||||
new HashEntry(PageSizeField, metadata.PageSize),
|
||||
// V1
|
||||
new HashEntry(nameof(metadata.Provider), metadata.Provider.ToString()),
|
||||
new HashEntry(nameof(metadata.Type), metadata.Type.ToString()),
|
||||
new HashEntry(nameof(metadata.Id), metadata.Id),
|
||||
|
||||
// V2
|
||||
new HashEntry(nameof(metadata.Quality), metadata.Quality.ToString()),
|
||||
new HashEntry(nameof(metadata.PageSize), metadata.PageSize),
|
||||
});
|
||||
|
||||
await Db.KeyExpireAsync(id, TimeSpan.FromDays(1));
|
||||
@@ -116,42 +115,71 @@ namespace Podsync.Services.Storage
|
||||
throw new KeyNotFoundException("Invaid key");
|
||||
}
|
||||
|
||||
var metadata = new FeedMetadata
|
||||
{
|
||||
Id = entries.Single(x => x.Name == IdField).Value,
|
||||
LinkType = ToEnum<LinkType>(entries.Single(x => x.Name == TypeField)),
|
||||
Provider = ToEnum<Provider>(entries.Single(x => x.Name == ProviderField)),
|
||||
};
|
||||
var metadata = new FeedMetadata();
|
||||
|
||||
if (entries.Length > 3)
|
||||
{
|
||||
metadata.Quality = ToEnum<ResolveType>(entries.Single(x => x.Name == QualityField));
|
||||
metadata.PageSize = (int)entries.Single(x => x.Name == PageSizeField).Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set default values
|
||||
metadata.Quality = DefaultQuality;
|
||||
metadata.PageSize = DefaultPageSize;
|
||||
}
|
||||
// V1
|
||||
SetProperty(metadata, x => x.Id, entries);
|
||||
SetProperty(metadata, x => x.Type, entries);
|
||||
SetProperty(metadata, x => x.Provider, entries);
|
||||
|
||||
// V2
|
||||
SetProperty(metadata, x => x.Quality, entries, Constants.DefaultFormat);
|
||||
SetProperty(metadata, x => x.PageSize, entries, Constants.DefaultPageSize);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public Task ResetCounter()
|
||||
{
|
||||
return Db.KeyDeleteAsync(IdKey);
|
||||
}
|
||||
|
||||
public async Task<string> MakeId()
|
||||
{
|
||||
var id = await Db.StringIncrementAsync(IdKey);
|
||||
return HashIds.EncodeLong(id);
|
||||
}
|
||||
|
||||
private static T ToEnum<T>(HashEntry key)
|
||||
private static void SetProperty<T, P>(T target, Expression<Func<T, P>> memberLamda, HashEntry[] entries)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), key.Value, true);
|
||||
SetProperty(target, memberLamda, entries, default(P), true);
|
||||
}
|
||||
|
||||
private static void SetProperty<T, P>(T target, Expression<Func<T, P>> memberLamda, HashEntry[] entries, P fallback, bool throwIfMissing = false)
|
||||
{
|
||||
var memberExpression = memberLamda.Body as MemberExpression;
|
||||
|
||||
// Get property name via reflection
|
||||
var entryName = memberExpression?.Member?.Name;
|
||||
if (string.IsNullOrEmpty(entryName))
|
||||
{
|
||||
throw new InvalidOperationException("Wrong property expression");
|
||||
}
|
||||
|
||||
P value;
|
||||
|
||||
// RedisValue is value type
|
||||
if (entries.Any(x => string.Equals(x.Name, entryName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var entry = entries.Single(x => string.Equals(x.Name, entryName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var propertyType = typeof(P);
|
||||
if (propertyType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
value = (P)Enum.Parse(propertyType, entry.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (P)Convert.ChangeType(entry.Value, propertyType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (throwIfMissing)
|
||||
{
|
||||
throw new InvalidDataException("Missing mandatory property");
|
||||
}
|
||||
|
||||
value = fallback;
|
||||
}
|
||||
|
||||
var property = memberExpression.Member as PropertyInfo;
|
||||
property?.SetValue(target, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,17 +29,11 @@ namespace Podsync.Tests.Services.Storage
|
||||
{
|
||||
const int idCount = 50;
|
||||
|
||||
try
|
||||
{
|
||||
var results = new string[idCount];
|
||||
Parallel.For(0, results.Length, (i, _) => results[i] = _storage.MakeId().GetAwaiter().GetResult());
|
||||
|
||||
Assert.Equal(results.Length, results.Distinct().Count());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_storage.ResetCounter();
|
||||
}
|
||||
var results = new string[idCount];
|
||||
Parallel.For(0, results.Length, (i, _) => results[i] = _storage.MakeId().GetAwaiter().GetResult());
|
||||
|
||||
Assert.Equal(results.Length, results.Distinct().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -49,7 +43,9 @@ namespace Podsync.Tests.Services.Storage
|
||||
{
|
||||
Id = "123",
|
||||
LinkType = LinkType.Channel,
|
||||
Provider = Provider.Vimeo
|
||||
Provider = Provider.Vimeo,
|
||||
|
||||
PageSize = 45
|
||||
};
|
||||
|
||||
var id = await _storage.Save(feed);
|
||||
@@ -61,6 +57,7 @@ namespace Podsync.Tests.Services.Storage
|
||||
Assert.Equal(feed.Id, loaded.Id);
|
||||
Assert.Equal(feed.LinkType, loaded.LinkType);
|
||||
Assert.Equal(feed.Provider, loaded.Provider);
|
||||
Assert.Equal(45, loaded.PageSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user