Skip to content

Commit

Permalink
Support multi language storage and enhance backward compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
VladimirKhil committed Nov 11, 2023
1 parent 73f93fa commit bcbb064
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 40 deletions.
6 changes: 4 additions & 2 deletions src/Common/SIPackages.Providers/IPackagesProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ public interface IPackagesProvider
/// <summary>
/// Enumerates available packages names.
/// </summary>
/// <param name="culture">Packages culture.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task<IEnumerable<string>> GetPackagesAsync(CancellationToken cancellationToken = default);
Task<IEnumerable<string>> GetPackagesAsync(string culture, CancellationToken cancellationToken = default);

/// <summary>
/// Gets package by name.
/// </summary>
/// <param name="culture">Packages culture.</param>
/// <param name="name">Package name.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task<SIDocument> GetPackageAsync(string name, CancellationToken cancellationToken = default);
Task<SIDocument> GetPackageAsync(string culture, string name, CancellationToken cancellationToken = default);
}
34 changes: 30 additions & 4 deletions src/Common/SIPackages.Providers/PackageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static Task<SIDocument> GenerateRandomPackageAsync(
CancellationToken cancellationToken = default)
{
var doc = SIDocument.Create(name, author, stream);
return GenerateCoreAsync(provider, roundsCount, themesCount, baseCost, doc, roundNameFormat, finalName, int.MaxValue, cancellationToken);
return GenerateCoreAsync(provider, roundsCount, themesCount, baseCost, doc, roundNameFormat, finalName, "", int.MaxValue, cancellationToken);
}

public static Task<SIDocument> GenerateRandomPackageAsync(
Expand All @@ -36,6 +36,7 @@ public static Task<SIDocument> GenerateRandomPackageAsync(
string author,
string roundNameFormat,
string finalName,
string culture,
int roundsCount = 3,
int themesCount = 6,
int baseCost = 100,
Expand All @@ -44,7 +45,7 @@ public static Task<SIDocument> GenerateRandomPackageAsync(
{
var doc = SIDocument.Create(name, author, folder);

return GenerateCoreAsync(provider, roundsCount, themesCount, baseCost, doc, roundNameFormat, finalName, maxPackageCount, cancellationToken);
return GenerateCoreAsync(provider, roundsCount, themesCount, baseCost, doc, roundNameFormat, finalName, culture, maxPackageCount, cancellationToken);
}

private static async Task<SIDocument> GenerateCoreAsync(
Expand All @@ -55,10 +56,11 @@ private static async Task<SIDocument> GenerateCoreAsync(
SIDocument doc,
string roundNameFormat,
string finalName,
string culture,
int maxPackageCount = int.MaxValue,
CancellationToken cancellationToken = default)
{
var files = (await provider.GetPackagesAsync(cancellationToken)).ToList();
var files = (await provider.GetPackagesAsync(culture, cancellationToken)).ToList();

if (maxPackageCount < int.MaxValue)
{
Expand Down Expand Up @@ -86,6 +88,7 @@ private static async Task<SIDocument> GenerateCoreAsync(
round => round.Type == RoundTypes.Standart && round.Themes.Count > 0,
packageComments,
baseCost,
culture,
cancellationToken))
{
j--;
Expand Down Expand Up @@ -116,6 +119,7 @@ private static async Task<SIDocument> GenerateCoreAsync(
round => round.Type == RoundTypes.Final && round.Themes.Count > 0,
packageComments,
0,
culture,
cancellationToken))
{
j--;
Expand All @@ -141,10 +145,11 @@ private static async Task<bool> ExtractThemeAsync(
Func<Round, bool> predicate,
StringBuilder packageComments,
int baseCost,
string culture,
CancellationToken cancellationToken = default)
{
var fIndex = Random.Shared.Next(files.Count);
var doc2 = await provider.GetPackageAsync(files[fIndex], cancellationToken) ?? throw new PackageNotFoundException(files[fIndex]);
var doc2 = await provider.GetPackageAsync(culture, files[fIndex], cancellationToken) ?? throw new PackageNotFoundException(files[fIndex]);

using (doc2)
{
Expand Down Expand Up @@ -250,6 +255,27 @@ private static async Task InheritContentAsync(
}
}
}

foreach (var atom in question.Scenario)
{
if (atom.Type == AtomTypes.Text || atom.Type == AtomTypes.Oral)
{
continue;
}

var link = doc2.GetLink(atom);

if (link.GetStream != null)
{
var collection = doc.TryGetCollection(atom.Type);

if (collection != null)
{
using var stream = link.GetStream().Stream;
await collection.AddFileAsync(link.Uri, stream, cancellationToken);
}
}
}
}

private static void InheritAuthors(SIDocument doc2, Round round, Theme theme)
Expand Down
125 changes: 95 additions & 30 deletions src/Common/SIPackages.Providers/SIStoragePackageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,55 @@ public sealed class SIStoragePackageProvider : IPackagesProvider, IDisposable
private static readonly HttpClient HttpClient = new() { DefaultRequestVersion = HttpVersion.Version20 };

private readonly ISIStorageServiceClient _siStorageServiceClient;
private readonly Dictionary<string, PackageEntry> _packageCache = new();
private readonly Dictionary<int, Dictionary<string, PackageEntry>> _packageCache = new();
private Dictionary<string, int>? _languageCache = null;
private readonly SemaphoreSlim _packageSemaphore = new(1, 1);

public SIStoragePackageProvider(ISIStorageServiceClient siStorageServiceClient) =>
_siStorageServiceClient = siStorageServiceClient;

public async Task<IEnumerable<string>> GetPackagesAsync(CancellationToken cancellationToken = default)
public async Task<IEnumerable<string>> GetPackagesAsync(string culture, CancellationToken cancellationToken = default)
{
if (_packageCache.Count == 0)
if (_languageCache == null)
{
await _packageSemaphore.WaitAsync(cancellationToken);

try
{
if (_packageCache.Count == 0)
if (_languageCache == null)
{
var languages = await _siStorageServiceClient.Facets.GetLanguagesAsync(cancellationToken);
_languageCache = languages.ToDictionary(l => l.Code, l => l.Id);
}
}
finally
{
_packageSemaphore.Release();
}
}

if (!_languageCache.TryGetValue(culture, out var languageId) || culture == null)
{
if (!_languageCache.TryGetValue("en-US", out languageId))
{
languageId = -1;
}
}

if (!_packageCache.TryGetValue(languageId, out var localizedCache))
{
await _packageSemaphore.WaitAsync(cancellationToken);

try
{
if (!_packageCache.TryGetValue(languageId, out localizedCache))
{
_packageCache[languageId] = localizedCache = new Dictionary<string, PackageEntry>();

var packages = await _siStorageServiceClient.Packages.GetPackagesAsync(
new PackageFilters { TagIds = new[] { -1 } },
new PackageSelectionParameters { Count = 1000 },
cancellationToken);
new PackageFilters { LanguageId = languageId, TagIds = new[] { -1 } },
new PackageSelectionParameters { Count = 1000 },
cancellationToken);

foreach (var package in packages.Packages)
{
Expand All @@ -40,7 +69,7 @@ public async Task<IEnumerable<string>> GetPackagesAsync(CancellationToken cancel
continue;
}

_packageCache[package.Id.ToString()] = new PackageEntry { Uri = package.DirectContentUri };
localizedCache[package.Id.ToString()] = new PackageEntry { Uri = package.DirectContentUri };
}
}
}
Expand All @@ -50,32 +79,65 @@ public async Task<IEnumerable<string>> GetPackagesAsync(CancellationToken cancel
}
}

return _packageCache.Keys;
return localizedCache.Keys;
}

public async Task<SIDocument> GetPackageAsync(string name, CancellationToken cancellationToken = default)
public async Task<SIDocument> GetPackageAsync(string culture, string name, CancellationToken cancellationToken = default)
{
if (!_packageCache.TryGetValue(name, out var info))
ArgumentNullException.ThrowIfNull(name, nameof(name));

int languageId;

if (_languageCache == null || culture == null)
{
languageId = -1;
}
else if (!_languageCache.TryGetValue(culture, out languageId))
{
if (!_languageCache.TryGetValue("en-US", out languageId))
{
languageId = -1;
}
}

if (!_packageCache.TryGetValue(languageId, out var localizedCache))
{
throw new PackageNotFoundException(name);
}

if (info.LocalPath == null)
if (!localizedCache.TryGetValue(name, out var info))
{
var fileName = Path.GetTempFileName();
throw new PackageNotFoundException(name);
}

using var response = await HttpClient.GetAsync(info.Uri, cancellationToken);
if (info.LocalPath == null)
{
await _packageSemaphore.WaitAsync(cancellationToken);

if (!response.IsSuccessStatusCode)
try
{
throw new Exception(
$"Error while accessing \"{info.Uri}\": {await response.Content.ReadAsStringAsync(cancellationToken)}!");
}
if (info.LocalPath == null)
{
var fileName = Path.GetTempFileName();

using var response = await HttpClient.GetAsync(info.Uri, cancellationToken);

if (!response.IsSuccessStatusCode)
{
throw new Exception(
$"Error while accessing \"{info.Uri}\": {await response.Content.ReadAsStringAsync(cancellationToken)}!");
}

using var fs = File.Create(fileName);
await response.Content.CopyToAsync(fs, cancellationToken);
using var fs = File.Create(fileName);
await response.Content.CopyToAsync(fs, cancellationToken);

info.LocalPath = fileName;
info.LocalPath = fileName;
}
}
finally
{
_packageSemaphore.Release();
}
}

return SIDocument.Load(File.OpenRead(info.LocalPath));
Expand All @@ -85,18 +147,21 @@ public void Dispose()
{
var exceptionsList = new List<Exception>();

foreach (var package in _packageCache)
foreach (var localizedCache in _packageCache.Values)
{
try
foreach (var package in localizedCache.Values)
{
if (package.Value.LocalPath != null)
try
{
File.Delete(package.Value.LocalPath);
if (package.LocalPath != null)
{
File.Delete(package.LocalPath);
}
}
catch (Exception exc)
{
exceptionsList.Add(exc);
}
}
catch (Exception exc)
{
exceptionsList.Add(exc);
}
}

Expand All @@ -106,7 +171,7 @@ public void Dispose()
}
}

private record struct PackageEntry
private record PackageEntry
{
public Uri Uri { get; set; }

Expand Down
12 changes: 11 additions & 1 deletion src/SICore/SICore/Clients/Game/GameLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,17 @@ private bool ShareMedia(ContentItem contentItem, bool isBackground = false)
return false;
}

_gameActions.SendMessageWithArgs(Messages.Content, contentItem.Placement, 0, contentItem.Type, globalUri);
// For backward compatibility; remove later
// {
var contentType2 = contentItem.Type;

if (contentType2 == AtomTypes.AudioNew)
{
contentType2 = AtomTypes.Audio;
}
// }

_gameActions.SendMessageWithArgs(Messages.Content, contentItem.Placement, 0, contentType2, globalUri);

// TODO: remove after complete switching to Content message
// {
Expand Down
2 changes: 1 addition & 1 deletion src/SICore/SICore/Clients/Game/QuestionPlayHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public void OnQuestionContentItem(ContentItem contentItem)
break;

case ContentPlacements.Background:
if (contentItem.Type == ContentTypes.Audio)
if (contentItem.Type == ContentTypes.Audio || contentItem.Type == AtomTypes.Audio)
{
GameLogic.OnContentBackgroundAudio(contentItem);
}
Expand Down
4 changes: 2 additions & 2 deletions src/SICore/SICore/PackageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public PackageProvider(string folder)
_folder = folder;
}

public Task<IEnumerable<string>> GetPackagesAsync(CancellationToken cancellationToken = default)
public Task<IEnumerable<string>> GetPackagesAsync(string culture, CancellationToken cancellationToken = default)
{
var dir = new DirectoryInfo(_folder);
return Task.FromResult(dir.EnumerateFiles("*.siq").Select(file => file.Name));
}

public Task<SIDocument> GetPackageAsync(string name, CancellationToken cancellationToken = default) =>
public Task<SIDocument> GetPackageAsync(string culture, string name, CancellationToken cancellationToken = default) =>
Task.FromResult(SIDocument.Load(File.OpenRead(Path.Combine(_folder, name))));
}

0 comments on commit bcbb064

Please sign in to comment.