Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for Duplicates In Mining Mode #109

Merged
merged 11 commits into from
Dec 12, 2024
2 changes: 2 additions & 0 deletions JL.Core/Config/CoreConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public sealed class CoreConfigManager
public bool AnkiIntegration { get; set; } // = false;
public bool ForceSyncAnki { get; private set; } // = false;
public bool AllowDuplicateCards { get; private set; } // = false;
public bool CheckForDuplicateCards { get; private set; } // = false;
public double LookupRate { get; private set; } // = 0;
public bool CaptureTextFromClipboard { get; set; } = true;
public bool CaptureTextFromWebSocket { get; set; } // = false;
Expand Down Expand Up @@ -95,6 +96,7 @@ public void ApplyPreferences(SqliteConnection connection)
AnkiIntegration = ConfigDBManager.GetValueFromConfig(connection, AnkiIntegration, nameof(AnkiIntegration), bool.TryParse);
ForceSyncAnki = ConfigDBManager.GetValueFromConfig(connection, ForceSyncAnki, nameof(ForceSyncAnki), bool.TryParse);
AllowDuplicateCards = ConfigDBManager.GetValueFromConfig(connection, AllowDuplicateCards, nameof(AllowDuplicateCards), bool.TryParse);
CheckForDuplicateCards = ConfigDBManager.GetValueFromConfig(connection, CheckForDuplicateCards, nameof(CheckForDuplicateCards), bool.TryParse);
LookupRate = ConfigDBManager.GetNumberWithDecimalPointFromConfig(connection, LookupRate, nameof(LookupRate), double.TryParse);
TextBoxTrimWhiteSpaceCharacters = ConfigDBManager.GetValueFromConfig(connection, TextBoxTrimWhiteSpaceCharacters, nameof(TextBoxTrimWhiteSpaceCharacters), bool.TryParse);
TextBoxRemoveNewlines = ConfigDBManager.GetValueFromConfig(connection, TextBoxRemoveNewlines, nameof(TextBoxRemoveNewlines), bool.TryParse);
Expand Down
7 changes: 2 additions & 5 deletions JL.Core/Mining/Anki/AnkiConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,12 @@ internal static class AnkiConnect
return Send(req);
}

public static ValueTask<Response?> GetCanAddNotesResponse(Note note)
public static ValueTask<Response?> GetCanAddNotesResponse(Note[] notes)
{
Request req = new("canAddNotes", 6, new Dictionary<string, object>(1, StringComparer.Ordinal)
{
{
"notes", new[]
{
note
}
"notes", notes
}
});

Expand Down
12 changes: 11 additions & 1 deletion JL.Core/Mining/Anki/AnkiUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ public static class AnkiUtils

internal static async Task<bool?> CanAddNote(Note note)
{
Response? response = await AnkiConnect.GetCanAddNotesResponse(note).ConfigureAwait(false);
Response? response = await AnkiConnect.GetCanAddNotesResponse([note]).ConfigureAwait(false);
string? resultString = response?.Result?.ToString() ?? null;

return resultString is not null
? JsonSerializer.Deserialize<bool[]>(resultString)![0]
: null;
}

internal static async Task<bool[]?> CanAddNotes(Note[] notes)
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
Response? response = await AnkiConnect.GetCanAddNotesResponse(notes).ConfigureAwait(false);
string? resultString = response?.Result?.ToString() ?? null;

return resultString is not null
? JsonSerializer.Deserialize<bool[]>(resultString)!
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
: null;
}
}
191 changes: 191 additions & 0 deletions JL.Core/Mining/MiningUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,127 @@ public static class MiningUtils
</style>
""";

private static string? GetMiningParameter(JLField field, LookupResult lookupResult, string currentText, int currentCharPosition, bool useHtmlTags)
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
string readings = "";
if (lookupResult.Readings is not null)
{
readings = string.Join('、', lookupResult.Readings);
}
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
switch (field)
{
case JLField.LeadingSentencePart:
{
string sentence = JapaneseUtils.FindSentence(currentText, currentCharPosition);
int searchStartIndex = currentCharPosition + lookupResult.MatchedText.Length - sentence.Length;
if (searchStartIndex < 0 || searchStartIndex >= currentText.Length)
{
searchStartIndex = 0;
}

int sentenceStartIndex = currentText.IndexOf(sentence, searchStartIndex, StringComparison.Ordinal);
return currentText[sentenceStartIndex..currentCharPosition];
}

case JLField.TrailingSentencePart:
{
string sentence = JapaneseUtils.FindSentence(currentText, currentCharPosition);
int searchStartIndex = currentCharPosition + lookupResult.MatchedText.Length - sentence.Length;
if (searchStartIndex < 0 || searchStartIndex >= currentText.Length)
{
searchStartIndex = 0;
}

int sentenceStartIndex = currentText.IndexOf(sentence, searchStartIndex, StringComparison.Ordinal);
return currentText[(lookupResult.MatchedText.Length + currentCharPosition)..(sentenceStartIndex + sentence.Length)];
}

case JLField.Sentence:
{
string sentence = JapaneseUtils.FindSentence(currentText, currentCharPosition);
int searchStartIndex = currentCharPosition + lookupResult.MatchedText.Length - sentence.Length;
if (searchStartIndex < 0 || searchStartIndex >= currentText.Length)
{
searchStartIndex = 0;
}

int sentenceStartIndex = currentText.IndexOf(sentence, searchStartIndex, StringComparison.Ordinal);
string leadingSentencePart = currentText[sentenceStartIndex..currentCharPosition];
string trailingSentencePart = currentText[(lookupResult.MatchedText.Length + currentCharPosition)..(sentenceStartIndex + sentence.Length)];
return useHtmlTags ? $"{leadingSentencePart}<b>{lookupResult.MatchedText}</b>{trailingSentencePart}" : sentence;
}
case JLField.SourceText:
string leadingSourcePart = currentText[..currentCharPosition];
string trailingSourcePart = currentText[(currentCharPosition + lookupResult.MatchedText.Length)..];
if (useHtmlTags)
{
leadingSourcePart = leadingSourcePart.ReplaceLineEndings("<br/>");
trailingSourcePart = trailingSourcePart.ReplaceLineEndings("<br/>");
}
return useHtmlTags
? $"{leadingSourcePart}<b>{lookupResult.MatchedText}</b>{trailingSourcePart}".ReplaceLineEndings("<br/>")
: currentText;
case JLField.Readings:
return readings;
case JLField.ReadingsWithOrthographyInfo:
return lookupResult.ReadingsOrthographyInfoList is not null && lookupResult.Readings is not null
? LookupResultUtils.ElementWithOrthographyInfoToText(lookupResult.Readings, lookupResult.ReadingsOrthographyInfoList)
: readings;
case JLField.FirstReading:
return lookupResult.Readings?[0];
case JLField.PrimarySpellingAndReadings:
return $"{lookupResult.PrimarySpelling}[{readings}]";
case JLField.PrimarySpellingAndFirstReading:
return $"{lookupResult.PrimarySpelling}[{(lookupResult.Readings is not null ? lookupResult.Readings[0] : "")}]";
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
case JLField.AlternativeSpellings:
return lookupResult.AlternativeSpellings is not null ? string.Join('、', lookupResult.AlternativeSpellings) : null;
case JLField.AlternativeSpellingsWithOrthographyInfo:
return lookupResult.AlternativeSpellings is not null ? lookupResult.AlternativeSpellingsOrthographyInfoList is not null
? LookupResultUtils.ElementWithOrthographyInfoToText(lookupResult.AlternativeSpellings, lookupResult.AlternativeSpellingsOrthographyInfoList)
: string.Join('、', lookupResult.AlternativeSpellings) : null;
case JLField.MatchedText:
return lookupResult.MatchedText;
case JLField.PrimarySpelling:
return lookupResult.PrimarySpelling;
case JLField.DeconjugatedMatchedText:
return lookupResult.DeconjugatedMatchedText;
case JLField.KanjiStats:
return lookupResult.KanjiStats;
case JLField.OnReadings:
return lookupResult.OnReadings is not null ? string.Join('、', lookupResult.OnReadings) : null;
case JLField.KunReadings:
return lookupResult.KunReadings is not null ? string.Join('、', lookupResult.KunReadings) : null;
case JLField.NanoriReadings:
return lookupResult.NanoriReadings is not null ? string.Join('、', lookupResult.NanoriReadings) : null;
case JLField.Nothing:
case JLField.Audio:
case JLField.Image:
case JLField.StrokeCount:
case JLField.KanjiGrade:
case JLField.RadicalNames:
case JLField.Definitions:
case JLField.SelectedDefinitions:
case JLField.DictionaryName:
case JLField.LeadingSourceTextPart:
case JLField.TrailingSourceTextPart:
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
case JLField.LocalTime:
case JLField.Frequencies:
case JLField.RawFrequencies:
case JLField.PreferredFrequency:
case JLField.FrequencyHarmonicMean:
case JLField.PitchAccents:
case JLField.NumericPitchAccents:
case JLField.PitchAccentForFirstReading:
case JLField.NumericPitchAccentForFirstReading:
case JLField.EdictId:
case JLField.DeconjugationProcess:
case JLField.PrimarySpellingWithOrthographyInfo:
case JLField.KanjiComposition:
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
default:
return null;
}
}

private static Dictionary<JLField, string> GetMiningParameters(LookupResult lookupResult, string currentText, string? formattedDefinitions, string? selectedDefinitions, int currentCharPosition, bool useHtmlTags)
{
Dictionary<JLField, string> miningParams = new()
Expand Down Expand Up @@ -440,6 +561,76 @@ public static async Task MineToFile(LookupResult lookupResult, string currentTex
Utils.Logger.Information("Mined {PrimarySpelling}", lookupResult.PrimarySpelling);
}

public static async ValueTask<bool[]?> CheckDuplicates(List<LookupResult> lookupResults, string currentText, int currentCharPosition)
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
List<Note> notes = [];
List<int> positions = [];
bool[] results = new bool[lookupResults.Count];
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < lookupResults.Count; i++)
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
LookupResult lookupResult = lookupResults[i];

Dictionary<MineType, AnkiConfig>? ankiConfigDict = await AnkiConfig.ReadAnkiConfig().ConfigureAwait(false);
if (ankiConfigDict is null)
{
continue;
}
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved

AnkiConfig? ankiConfig;
if (DictUtils.s_wordDictTypes.Contains(lookupResult.Dict.Type))
{
ankiConfig = ankiConfigDict.GetValueOrDefault(MineType.Word);
}
else if (DictUtils.s_kanjiDictTypes.Contains(lookupResult.Dict.Type))
{
ankiConfig = ankiConfigDict.GetValueOrDefault(MineType.Kanji);
}
else if (DictUtils.s_nameDictTypes.Contains(lookupResult.Dict.Type))
{
ankiConfig = ankiConfigDict.GetValueOrDefault(MineType.Name);
}
else
{
ankiConfig = ankiConfigDict.GetValueOrDefault(MineType.Other);
}

if (ankiConfig is null)
{
continue;
}

Dictionary<string, JLField> userFields = ankiConfig.Fields;
JLField firstValue = userFields.First().Value;
string? value = GetMiningParameter(firstValue, lookupResult, currentText, currentCharPosition, true);
if (value is null)
{
continue;
}

Dictionary<string, string> fields = ConvertFields(userFields, new()
{
[firstValue] = value
});
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved

Note note = new(ankiConfig.DeckName, ankiConfig.ModelName, fields, ankiConfig.Tags, null, null, null, null);
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
notes.Add(note);
positions.Add(i);
}

bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
bool[]? canAddNote = await AnkiUtils.CanAddNotes(notes.ToArray()).ConfigureAwait(false);
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
if (canAddNote is null)
{
return null;
}

for (int i = 0; i < canAddNote.Length; i++)
{
results[positions[i]] = !canAddNote[i];
}

return results;
}

public static async Task Mine(LookupResult lookupResult, string currentText, string? formattedDefinitions, string? selectedDefinitions, int currentCharPosition)
{
CoreConfigManager coreConfigManager = CoreConfigManager.Instance;
Expand Down
4 changes: 4 additions & 0 deletions JL.Windows/ConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ public void LoadPreferenceWindow(PreferencesWindow preferenceWindow)
preferenceWindow.WebSocketUriTextBox.Text = coreConfigManager.WebSocketUri.OriginalString;
preferenceWindow.ForceSyncAnkiCheckBox.IsChecked = coreConfigManager.ForceSyncAnki;
preferenceWindow.AllowDuplicateCardsCheckBox.IsChecked = coreConfigManager.AllowDuplicateCards;
preferenceWindow.CheckForDuplicateCardsCheckBox.IsChecked = coreConfigManager.CheckForDuplicateCards;
preferenceWindow.LookupRateNumericUpDown.Value = coreConfigManager.LookupRate;
preferenceWindow.KanjiModeCheckBox.IsChecked = coreConfigManager.KanjiMode;
preferenceWindow.AutoAdjustFontSizesOnResolutionChange.IsChecked = AutoAdjustFontSizesOnResolutionChange;
Expand Down Expand Up @@ -1213,6 +1214,9 @@ public async Task SavePreferences(PreferencesWindow preferenceWindow)
ConfigDBManager.UpdateSetting(connection, nameof(CoreConfigManager.AllowDuplicateCards),
preferenceWindow.AllowDuplicateCardsCheckBox.IsChecked.ToString()!);

ConfigDBManager.UpdateSetting(connection, nameof(CoreConfigManager.CheckForDuplicateCards),
preferenceWindow.CheckForDuplicateCardsCheckBox.IsChecked.ToString()!);

ConfigDBManager.UpdateSetting(connection, nameof(CoreConfigManager.LookupRate),
preferenceWindow.LookupRateNumericUpDown.Value.ToString(CultureInfo.InvariantCulture));

Expand Down
Loading