Skip to content

Commit

Permalink
Merge pull request #11 from pavel-zhur/feature/index
Browse files Browse the repository at this point in the history
Feature/index
  • Loading branch information
pavel-zhur authored Jun 27, 2024
2 parents b632f06 + 8d6da49 commit 5c015ee
Show file tree
Hide file tree
Showing 97 changed files with 2,710 additions and 441 deletions.
9 changes: 8 additions & 1 deletion HarmonyDB and OneShelf.sln
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HarmonyDB.Source", "Harmony
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HarmonyDB.Playground", "HarmonyDB.Playground", "{47FED0C3-B336-4FC6-86FE-51A78872C2DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HarmonyDB.Playground.Web", "HarmonyDB.Playground\HarmonyDB.Playground.Web\HarmonyDB.Playground.Web.csproj", "{C63D176C-03AB-4403-BE88-E8FE4EFE736D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HarmonyDB.Playground.Web", "HarmonyDB.Playground\HarmonyDB.Playground.Web\HarmonyDB.Playground.Web.csproj", "{C63D176C-03AB-4403-BE88-E8FE4EFE736D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HarmonyDB.Common.FullTextSearch", "HarmonyDB.Common\HarmonyDB.Common.FullTextSearch\HarmonyDB.Common.FullTextSearch.csproj", "{21D494EB-BDD0-49B0-A79D-57AE6A529F4D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -421,6 +423,10 @@ Global
{C63D176C-03AB-4403-BE88-E8FE4EFE736D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C63D176C-03AB-4403-BE88-E8FE4EFE736D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C63D176C-03AB-4403-BE88-E8FE4EFE736D}.Release|Any CPU.Build.0 = Release|Any CPU
{21D494EB-BDD0-49B0-A79D-57AE6A529F4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21D494EB-BDD0-49B0-A79D-57AE6A529F4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21D494EB-BDD0-49B0-A79D-57AE6A529F4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21D494EB-BDD0-49B0-A79D-57AE6A529F4D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -488,6 +494,7 @@ Global
{92969108-D9C8-4FF8-B083-B10463EB0FEC} = {2AD8DB5A-A1C3-4116-B8C5-D8DA505B1CC5}
{F2CFF546-5D77-4E7D-BE8B-611022AD6ECA} = {7548FF8E-E1B9-4939-96E9-03DBD5236709}
{C63D176C-03AB-4403-BE88-E8FE4EFE736D} = {47FED0C3-B336-4FC6-86FE-51A78872C2DE}
{21D494EB-BDD0-49B0-A79D-57AE6A529F4D} = {2950865E-9D1A-406E-B0C6-4C249E2E4D61}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59093261-FDDA-411A-852D-EA21AEF83E07}
Expand Down
1 change: 1 addition & 0 deletions HarmonyDB and OneShelf.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Databasish/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Folclore/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Interims/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Localizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Overscroll/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=participations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pdfs/@EntryIndexedValue">True</s:Boolean>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace HarmonyDB.Common.FullTextSearch;

public static class FullTextSearchExtensions
{
public static readonly char[] Separators =
{
'-',
',',
' ',
'\r',
'\n',
'\'',
};

public static string ToSearchSyntax(this string text) => text.ToLowerInvariant().Replace("ё", "е");

public static string SearchSyntaxRemoveSeparators(this string text) => text.Replace("'", "");

public static bool SearchSyntaxAnySeparatorsToRemove(this string text) => text.Contains('\'');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\nuget readme.md" Pack="true" Link="readme.md" PackagePath="\readme.md" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
namespace HarmonyDB.Common.FullTextSearch;

public static class SearchHighlightingTools
{
public static IEnumerable<(string fragment, bool isHighlighted)> GetFragments(string? query, string text)
{
if (string.IsNullOrWhiteSpace(text)) yield break;
var split = query?.ToLowerInvariant().ToSearchSyntax().Split(FullTextSearchExtensions.Separators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (split?.Length is null or 0)
{
yield return (text, false);
yield break;
}

var remaining = text.ToLowerInvariant().ToSearchSyntax().SearchSyntaxRemoveSeparators();
var rendering = text;
var anyToRemove = rendering.SearchSyntaxAnySeparatorsToRemove();

while (true)
{
var first = split
.Select(x => (x, index: (int?)remaining.IndexOf(x, StringComparison.Ordinal)))
.Where(x => x.index > -1)
.OrderBy(x => x.index)
.FirstOrDefault();

var index = first.index;

if (index == null)
{
yield return (remaining, false);
yield break;
}

var updatedIndex = index.Value;

if (updatedIndex > 0 && anyToRemove)
{
var expected = remaining.Substring(0, index.Value);
var renderingTests = rendering.ToLowerInvariant().ToSearchSyntax();
while (true)
{
var test = renderingTests.Substring(0, updatedIndex).SearchSyntaxRemoveSeparators();
if (test == expected) break;

updatedIndex++;
if (updatedIndex > 100) throw new("Protection. Failed.");
}
}

var matchLength = first.x.Length;
var updatedMatchLength = matchLength;

if (anyToRemove)
{
var expected = remaining.Substring(index.Value, matchLength);
var renderingTests = rendering.Substring(updatedIndex).ToLowerInvariant().ToSearchSyntax();
while (true)
{
var test = renderingTests.Substring(0, updatedMatchLength).SearchSyntaxRemoveSeparators();
if (test == expected) break;

updatedMatchLength++;
if (updatedMatchLength > 100) throw new("Protection. Failed.");
}
}

if (index == 0)
{
yield return (rendering.Substring(0, updatedMatchLength), true);
}
else
{
yield return (rendering.Substring(0, updatedIndex), false);
yield return (rendering.Substring(updatedIndex, updatedMatchLength), true);
}

remaining = remaining.Substring(index.Value + matchLength);
rendering = rendering.Substring(updatedIndex + updatedMatchLength);
if (remaining == string.Empty) yield break;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace OneShelf.Frontend.Web.Tools;
namespace HarmonyDB.Common.Transposition;

public static class TranspositionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

namespace HarmonyDB.Index.Analysis;

public static class IServiceCollectionExtensions
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddIndexAnalysis(this IServiceCollection services) => services
.AddSingleton<ChordDataParser>()
.AddSingleton<ProgressionsBuilder>()
.AddSingleton<ProgressionsVisualizer>()
.AddSingleton<InputParser>()
.AddSingleton<ProgressionsSearch>();
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using HarmonyDB.Common.Representations.OneShelf;
using HarmonyDB.Index.Analysis.Models;
using HarmonyDB.Index.Analysis.Services;

namespace HarmonyDB.Index.Api.Services;
namespace HarmonyDB.Index.Analysis.Services;

public class InputParser
{
Expand All @@ -28,14 +27,11 @@ public HarmonyMovementsSequence Parse(string input)
}.AsChild())
.ToList(),
};

var chordsProgression =
_progressionsBuilder.BuildProgression(html.AsChords(new()).Select(_chordDataParser.GetProgressionData)
_progressionsBuilder.BuildProgression(html.AsChords(new()).Select(_chordDataParser.GetProgressionData)
.ToList());

var sequence = chordsProgression.ExtendedHarmonyMovementsSequences.Single();

Console.WriteLine(string.Join(" ", sequence.Movements.Select(m => m.Title)));

return sequence;
return chordsProgression.ExtendedHarmonyMovementsSequences.Single();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using HarmonyDB.Index.Analysis.Models;
using OneShelf.Common;

namespace HarmonyDB.Index.Analysis.Services;

public class ProgressionsVisualizer
{
private readonly ProgressionsSearch _progressionsSearch;

public const string AttributeSearch = "search";
public const string AttributeSearchFirst = "search-first";

public ProgressionsVisualizer(ProgressionsSearch progressionsSearch)
{
_progressionsSearch = progressionsSearch;
}

public List<(Loop loop, int id, string title, string chordsTitle, bool isCompound, int length)> BuildLoopTitles(ChordsProgression progression)
{
var loops = _progressionsSearch.FindAllLoops(progression.Compact().ExtendedHarmonyMovementsSequences);
var loopTitles = new List<(Loop loop, int id, string title, string chordsTitle, bool isCompound, int length)>();

foreach (var (loop, id) in loops.WithIndices())
{
var length = loop.EndMovement - loop.Start + 1;
var chordsTitle = string.Join(" ", Enumerable.Range(loop.Start, length).Append(loop.Start)
.Select(i => progression.ExtendedHarmonyMovementsSequences[loop.SequenceIndex].FirstMovementFromIndex + i)
.Select(i => progression.HarmonySequence[i].harmonyGroup.HarmonyRepresentation));
var title = $"{loop.Successions}/{loop.Occurrences}, {loop.Coverage.Sum(h => progression.HarmonySequence[h].harmonyGroup.SelectSingle(x => x.EndChordIndex - x.StartChordIndex + 1)) * 100 / progression.OriginalSequence.Count}%";

loopTitles.Add((loop, id, title, chordsTitle, loop.IsCompound, length));
}

return loopTitles;
}

public IReadOnlyDictionary<int, string> BuildCustomAttributesForSearch(
ChordsProgression progression,
(Dictionary<ChordsProgression, float> foundProgressionsWithCoverage, Dictionary<HarmonyGroup, bool> harmonyGroupsWithIsFirst) searchResult)
=> progression.OriginalSequence
.WithIndices()
.Select(x => (
x.x,
x.i,
isFirst: searchResult.harmonyGroupsWithIsFirst.TryGetValue(x.x.harmonyGroup, out var isFirst)
? x.x.indexInHarmonyGroup == 0 && isFirst
: (bool?)null))
.Where(x => x.isFirst.HasValue)
.ToDictionary(x => x.i, x => x.isFirst!.Value ? AttributeSearchFirst : AttributeSearch);

public IReadOnlyDictionary<int, string> BuildCustomAttributesForLoop(IReadOnlyList<(Loop loop, int id, string title, string chordsTitle, bool isCompound, int length)> loops, ChordsProgression progression, int? loopId)
{
var loopsCustomAttributes = new Dictionary<int, string>();

foreach (var x in loops.WithIndices()
.Where(x => x.i == loopId)
.SelectMany(l => l.x.loop.Coverage
.Select(i => progression.HarmonySequence[i].harmonyGroup)
.SelectMany(g => Enumerable.Range(g.StartChordIndex, g.EndChordIndex - g.StartChordIndex + 1))))
{
loopsCustomAttributes[x] = AttributeSearch;
}

foreach (var x in loops.WithIndices()
.Where(x => x.i == loopId)
.SelectMany(l => l.x.loop.FoundFirsts
.Select(i => progression.HarmonySequence[i].harmonyGroup)
.SelectMany(g => Enumerable.Range(g.StartChordIndex, g.EndChordIndex - g.StartChordIndex + 1))))
{
loopsCustomAttributes[x] = AttributeSearchFirst;
}

return loopsCustomAttributes;
}
}
7 changes: 5 additions & 2 deletions HarmonyDB.Index/HarmonyDB.Index.Api.Client/IndexApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ public async Task<TryImportResponse> TryImport(string url)
Url = url,
});

public async Task<SearchResponse> Search(SearchRequest request, ApiTraceBag? apiTraceBag = null)
=> await PostWithCode<SearchRequest, SearchResponse>(IndexApiUrls.VExternal1Search, request, apiTraceBag: apiTraceBag);
public async Task<SongsByChordsResponse> SongsByChords(SongsByChordsRequest request, ApiTraceBag? apiTraceBag = null)
=> await PostWithCode<SongsByChordsRequest, SongsByChordsResponse>(IndexApiUrls.VExternal1SongsByChords, request, apiTraceBag: apiTraceBag);

public async Task<SongsByHeaderResponse> SongsByHeader(SongsByHeaderRequest request, ApiTraceBag? apiTraceBag = null)
=> await PostWithCode<SongsByHeaderRequest, SongsByHeaderResponse>(IndexApiUrls.VExternal1SongsByHeader, request, apiTraceBag: apiTraceBag);

public async Task<LoopsResponse> Loops(LoopsRequest request, ApiTraceBag? apiTraceBag = null)
=> await PostWithCode<LoopsRequest, LoopsResponse>(IndexApiUrls.VExternal1Loops, request, apiTraceBag: apiTraceBag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

namespace HarmonyDB.Index.Api.Client;

// ReSharper disable once InconsistentNaming
public static class IServiceCollectionExtensions
public static class ServiceCollectionExtensions
{
public static IServiceCollection
AddIndexApiClient(this IServiceCollection services, IConfiguration configuration) =>
Expand Down
3 changes: 2 additions & 1 deletion HarmonyDB.Index/HarmonyDB.Index.Api.Model/IndexApiUrls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public static class IndexApiUrls
public const string VInternalTryImport = nameof(VInternalTryImport);
public const string VInternalGetLyrics = nameof(VInternalGetLyrics);

public const string VExternal1Search = nameof(VExternal1Search);
public const string VExternal1SongsByChords = nameof(VExternal1SongsByChords);
public const string VExternal1SongsByHeader = nameof(VExternal1SongsByHeader);
public const string VExternal1Loops = nameof(VExternal1Loops);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace HarmonyDB.Index.Api.Model.VExternal1;

public record SearchRequest : PagedRequestBase
public record SongsByChordsRequest : PagedRequestBase
{
public required string Query { get; init; }

Expand All @@ -13,5 +13,5 @@ public record SearchRequest : PagedRequestBase
public int SongsPerPage { get; init; } = 100;

[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchRequestOrdering Ordering { get; init; } = SearchRequestOrdering.ByRating;
public SongsByChordsRequestOrdering Ordering { get; init; } = SongsByChordsRequestOrdering.ByRating;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace HarmonyDB.Index.Api.Model.VExternal1;

public enum SearchRequestOrdering
public enum SongsByChordsRequestOrdering
{
ByCoverage,
ByRating,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace HarmonyDB.Index.Api.Model.VExternal1;

public record SongsByChordsResponse : PagedResponseBase
{
public required List<SongsByChordsResponseSong> Songs { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace HarmonyDB.Index.Api.Model.VExternal1;

public class SearchResponseSong
public class SongsByChordsResponseSong
{
public required IndexHeader Header { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace HarmonyDB.Index.Api.Model.VExternal1;

public record SongsByHeaderRequest : PagedRequestBase
{
public required string Query { get; init; }

public int MinRating { get; init; } = 70;

public int SongsPerPage { get; init; } = 100;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using HarmonyDB.Source.Api.Model.V1;

namespace HarmonyDB.Index.Api.Model.VExternal1;

public record SongsByHeaderResponse : PagedResponseBase
{
public required List<IndexHeader> Songs { get; init; }
}
Loading

0 comments on commit 5c015ee

Please sign in to comment.