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;
}
}
70 changes: 70 additions & 0 deletions JL.Core/Mining/MiningUtils.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Globalization;
using System.Text;
using System.Text.Json;
using JL.Core.Audio;
using JL.Core.Config;
using JL.Core.Dicts;
Expand Down Expand Up @@ -440,6 +441,75 @@ public static async Task MineToFile(LookupResult lookupResult, string currentTex
Utils.Logger.Information("Mined {PrimarySpelling}", lookupResult.PrimarySpelling);
}

public static async Task<List<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 = [];
List<bool> ret = [];
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
{
ret.Add(false);
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;
Dictionary<JLField, string> filteredMiningParams = new()
{
{ firstValue, GetMiningParameters(lookupResult, currentText, null, null, currentCharPosition, true)[firstValue] }
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
};
Dictionary<string, string> fields = ConvertFields(userFields, filteredMiningParams);

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
Utils.Logger.Error("Sending notes to anki {notes}", JsonSerializer.Serialize(notes));
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 == null)
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
return null;
}

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

return ret;
}

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
52 changes: 52 additions & 0 deletions JL.Windows/GUI/PopupWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ internal sealed partial class PopupWindow
public nint WindowHandle { get; private set; }

public List<LookupResult> LastLookupResults { get; private set; } = [];
public List<Button> DuplicateIcons { get; private set; } = [];
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved

private List<Dict> _dictsWithResults = [];

Expand Down Expand Up @@ -251,6 +252,7 @@ public Task LookupOnCharPosition(TextBox textBox, int charPosition, bool enableM
}

LastLookupResults = lookupResults;
DuplicateIcons = [];
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved

if (enableMiningMode)
{
Expand Down Expand Up @@ -365,6 +367,7 @@ public Task LookupOnSelect(TextBox textBox)
_previousTextBox = textBox;
LastSelectedText = lookupResults[0].MatchedText;
LastLookupResults = lookupResults;
DuplicateIcons = [];
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved

EnableMiningMode();
DisplayResults(true);
Expand Down Expand Up @@ -508,6 +511,7 @@ public void DisplayResults(bool generateAllResults)
}

PopupListView.ItemsSource = popupItemSource;
CheckResultForDuplicates();
GenerateDictTypeButtons();
UpdateLayout();
}
Expand Down Expand Up @@ -695,6 +699,29 @@ private StackPanel PrepareResultStackPanel(LookupResult result, int index, int r
audioButton.Click += AudioButton_Click;

_ = top.Children.Add(audioButton);

if (!configManager.MineToFileInsteadOfAnki)
rampaa marked this conversation as resolved.
Show resolved Hide resolved
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
Button duplicate = new()
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
Name = nameof(duplicate),
Content = "⚠️",
Foreground = configManager.DefinitionsColor,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(3, 0, 0, 0),
HorizontalAlignment = HorizontalAlignment.Left,
Background = Brushes.Transparent,
Cursor = Cursors.Arrow,
BorderThickness = new Thickness(0),
Padding = new Thickness(0),
FontSize = 14,
ToolTip = $"{result.PrimarySpelling} is already in anki deck.",
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
Visibility = Visibility.Collapsed
};

_ = top.Children.Add(duplicate);
DuplicateIcons.Add(duplicate);
}
}

if (result.AlternativeSpellings is not null && configManager.AlternativeSpellingsFontSize > 0)
Expand Down Expand Up @@ -1067,6 +1094,30 @@ private StackPanel PrepareResultStackPanel(LookupResult result, int index, int r
return stackPanel;
}

private async void CheckResultForDuplicates()
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
ConfigManager configManager = ConfigManager.Instance;
CoreConfigManager coreConfigManager = CoreConfigManager.Instance;
if (MiningMode && coreConfigManager.AnkiIntegration
&& coreConfigManager.CheckForDuplicateCards
&& !configManager.MineToFileInsteadOfAnki)
{
List<bool>? duplicateCard = await MiningUtils.CheckDuplicates(LastLookupResults, _currentText, _currentCharPosition).ConfigureAwait(false);

if (duplicateCard != null)
bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
{
for (int i = 0; i < duplicateCard.Count; i++)
{
if (duplicateCard[i])
{
await MainWindow.Instance.Dispatcher.InvokeAsync(() => { DuplicateIcons[i].Visibility = Visibility.Visible; });
}

bpwhelan marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

private int GetFirstVisibleListViewItemIndex()
{
StackPanel? firstVisibleStackPanel = PopupListView.Items.Cast<StackPanel>()
Expand Down Expand Up @@ -2068,6 +2119,7 @@ private void Window_Closed(object sender, EventArgs e)
PopupListView.ItemsSource = null;
_lastInteractedTextBox = null;
LastLookupResults = null!;
DuplicateIcons = null!;
_dictsWithResults = null!;
_currentText = null!;
_buttonAll.Click -= DictTypeButtonOnClick;
Expand Down
7 changes: 7 additions & 0 deletions JL.Windows/GUI/PreferencesWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,13 @@
<CheckBox x:Name="AllowDuplicateCardsCheckBox" HorizontalAlignment="Right" />
</DockPanel>

<DockPanel Margin="0,10,0,0">
<TextBlock HorizontalAlignment="Left" Text="Check for duplicate cards"
Style="{StaticResource TextBlockDefault}" VerticalAlignment="Center" TextWrapping="Wrap"
ToolTip="Checks Anki for Duplicate Expressions when entering Mining Mode" Cursor="Help"/>
<CheckBox x:Name="CheckForDuplicateCardsCheckBox" HorizontalAlignment="Right" />
</DockPanel>

<TabControl Margin="0,10,0,0">
<TabControl.Resources>
<Style TargetType="{x:Type TabPanel}">
Expand Down