diff --git a/src/Common/SIPackages.Providers/IPackagesProvider.cs b/src/Common/SIPackages.Providers/IPackagesProvider.cs index 3a8dfd0d..03b91bdb 100644 --- a/src/Common/SIPackages.Providers/IPackagesProvider.cs +++ b/src/Common/SIPackages.Providers/IPackagesProvider.cs @@ -8,13 +8,15 @@ public interface IPackagesProvider /// /// Enumerates available packages names. /// + /// Packages culture. /// Cancellation token. - Task> GetPackagesAsync(CancellationToken cancellationToken = default); + Task> GetPackagesAsync(string culture, CancellationToken cancellationToken = default); /// /// Gets package by name. /// + /// Packages culture. /// Package name. /// Cancellation token. - Task GetPackageAsync(string name, CancellationToken cancellationToken = default); + Task GetPackageAsync(string culture, string name, CancellationToken cancellationToken = default); } diff --git a/src/Common/SIPackages.Providers/PackageHelper.cs b/src/Common/SIPackages.Providers/PackageHelper.cs index 1735e3b2..75f0f540 100644 --- a/src/Common/SIPackages.Providers/PackageHelper.cs +++ b/src/Common/SIPackages.Providers/PackageHelper.cs @@ -26,7 +26,7 @@ public static Task 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 GenerateRandomPackageAsync( @@ -36,6 +36,7 @@ public static Task GenerateRandomPackageAsync( string author, string roundNameFormat, string finalName, + string culture, int roundsCount = 3, int themesCount = 6, int baseCost = 100, @@ -44,7 +45,7 @@ public static Task 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 GenerateCoreAsync( @@ -55,10 +56,11 @@ private static async Task 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) { @@ -86,6 +88,7 @@ private static async Task GenerateCoreAsync( round => round.Type == RoundTypes.Standart && round.Themes.Count > 0, packageComments, baseCost, + culture, cancellationToken)) { j--; @@ -116,6 +119,7 @@ private static async Task GenerateCoreAsync( round => round.Type == RoundTypes.Final && round.Themes.Count > 0, packageComments, 0, + culture, cancellationToken)) { j--; @@ -141,10 +145,11 @@ private static async Task ExtractThemeAsync( Func 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) { @@ -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) diff --git a/src/Common/SIPackages.Providers/SIStoragePackageProvider.cs b/src/Common/SIPackages.Providers/SIStoragePackageProvider.cs index 242bd53f..5f6523f1 100644 --- a/src/Common/SIPackages.Providers/SIStoragePackageProvider.cs +++ b/src/Common/SIPackages.Providers/SIStoragePackageProvider.cs @@ -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 _packageCache = new(); + private readonly Dictionary> _packageCache = new(); + private Dictionary? _languageCache = null; private readonly SemaphoreSlim _packageSemaphore = new(1, 1); public SIStoragePackageProvider(ISIStorageServiceClient siStorageServiceClient) => _siStorageServiceClient = siStorageServiceClient; - public async Task> GetPackagesAsync(CancellationToken cancellationToken = default) + public async Task> 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(); + 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) { @@ -40,7 +69,7 @@ public async Task> GetPackagesAsync(CancellationToken cancel continue; } - _packageCache[package.Id.ToString()] = new PackageEntry { Uri = package.DirectContentUri }; + localizedCache[package.Id.ToString()] = new PackageEntry { Uri = package.DirectContentUri }; } } } @@ -50,32 +79,65 @@ public async Task> GetPackagesAsync(CancellationToken cancel } } - return _packageCache.Keys; + return localizedCache.Keys; } - public async Task GetPackageAsync(string name, CancellationToken cancellationToken = default) + public async Task 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)); @@ -85,18 +147,21 @@ public void Dispose() { var exceptionsList = new List(); - 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); } } @@ -106,7 +171,7 @@ public void Dispose() } } - private record struct PackageEntry + private record PackageEntry { public Uri Uri { get; set; } diff --git a/src/SICore/SICore/Clients/Game/GameLogic.cs b/src/SICore/SICore/Clients/Game/GameLogic.cs index cf9fbfab..e9be1c3c 100644 --- a/src/SICore/SICore/Clients/Game/GameLogic.cs +++ b/src/SICore/SICore/Clients/Game/GameLogic.cs @@ -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 // { diff --git a/src/SICore/SICore/Clients/Game/QuestionPlayHandler.cs b/src/SICore/SICore/Clients/Game/QuestionPlayHandler.cs index ed55a484..1e9158e0 100644 --- a/src/SICore/SICore/Clients/Game/QuestionPlayHandler.cs +++ b/src/SICore/SICore/Clients/Game/QuestionPlayHandler.cs @@ -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); } diff --git a/src/SICore/SICore/PackageProvider.cs b/src/SICore/SICore/PackageProvider.cs index cb29bd4e..d6e8e08c 100644 --- a/src/SICore/SICore/PackageProvider.cs +++ b/src/SICore/SICore/PackageProvider.cs @@ -15,12 +15,12 @@ public PackageProvider(string folder) _folder = folder; } - public Task> GetPackagesAsync(CancellationToken cancellationToken = default) + public Task> GetPackagesAsync(string culture, CancellationToken cancellationToken = default) { var dir = new DirectoryInfo(_folder); return Task.FromResult(dir.EnumerateFiles("*.siq").Select(file => file.Name)); } - public Task GetPackageAsync(string name, CancellationToken cancellationToken = default) => + public Task GetPackageAsync(string culture, string name, CancellationToken cancellationToken = default) => Task.FromResult(SIDocument.Load(File.OpenRead(Path.Combine(_folder, name)))); }