diff --git a/Flow.Launcher/Storage/TopMostRecord.cs b/Flow.Launcher/Storage/TopMostRecord.cs index 7ddca721b..d75e9cb79 100644 --- a/Flow.Launcher/Storage/TopMostRecord.cs +++ b/Flow.Launcher/Storage/TopMostRecord.cs @@ -49,9 +49,11 @@ namespace Flow.Launcher.Storage if (oldTopMostRecord == null || oldTopMostRecord.records.IsEmpty) return; foreach (var record in oldTopMostRecord.records) { - _topMostRecord.records.AddOrUpdate(record.Key, new ConcurrentBag { record.Value }, (key, oldValue) => + var newValue = new ConcurrentQueue(); + newValue.Enqueue(record.Value); + _topMostRecord.records.AddOrUpdate(record.Key, newValue, (key, oldValue) => { - oldValue.Add(record.Value); + oldValue.Enqueue(record.Value); return oldValue; }); } @@ -84,6 +86,11 @@ namespace Flow.Launcher.Storage return _topMostRecord.IsTopMost(result); } + public int GetTopMostIndex(Result result) + { + return _topMostRecord.GetTopMostIndex(result); + } + public void Remove(Result result) { _topMostRecord.Remove(result); @@ -156,8 +163,8 @@ namespace Flow.Launcher.Storage internal class MultipleTopMostRecord { [JsonInclude] - [JsonConverter(typeof(ConcurrentDictionaryConcurrentBagConverter))] - public ConcurrentDictionary> records { get; private set; } = new(); + [JsonConverter(typeof(ConcurrentDictionaryConcurrentQueueConverter))] + public ConcurrentDictionary> records { get; private set; } = new(); internal bool IsTopMost(Result result) { @@ -173,6 +180,32 @@ namespace Flow.Launcher.Storage return value.Any(record => record.Equals(result)); } + internal int GetTopMostIndex(Result result) + { + // origin query is null when user select the context menu item directly of one item from query list + // in this case, we do not need to check if the result is top most + if (records.IsEmpty || result.OriginQuery == null || + !records.TryGetValue(result.OriginQuery.RawQuery, out var value)) + { + return -1; + } + + // since this dictionary should be very small (or empty) going over it should be pretty fast. + // since the latter items should be more recent, we should return the smaller index for score to subtract + // which can make them more topmost + // A, B, C => 2, 1, 0 => (max - 2), (max - 1), (max - 0) + var index = 0; + foreach (var record in value) + { + if (record.Equals(result)) + { + return value.Count - 1 - index; + } + index++; + } + return -1; + } + internal void Remove(Result result) { // origin query is null when user select the context menu item directly of one item from query list @@ -183,17 +216,17 @@ namespace Flow.Launcher.Storage return; } - // remove the record from the bag - var bag = new ConcurrentQueue(value.Where(r => !r.Equals(result))); - if (bag.IsEmpty) + // remove the record from the queue + var queue = new ConcurrentQueue(value.Where(r => !r.Equals(result))); + if (queue.IsEmpty) { - // if the bag is empty, remove the bag from the dictionary + // if the queue is empty, remove the queue from the dictionary records.TryRemove(result.OriginQuery.RawQuery, out _); } else { - // change the bag in the dictionary - records[result.OriginQuery.RawQuery] = new ConcurrentBag(bag); + // change the queue in the dictionary + records[result.OriginQuery.RawQuery] = queue; } } @@ -215,40 +248,38 @@ namespace Flow.Launcher.Storage }; if (!records.TryGetValue(result.OriginQuery.RawQuery, out var value)) { - // create a new bag if it does not exist - value = new ConcurrentBag() - { - record - }; + // create a new queue if it does not exist + value = new ConcurrentQueue(); + value.Enqueue(record); records.TryAdd(result.OriginQuery.RawQuery, value); } else { - // add or update the record in the bag - var bag = new ConcurrentQueue(value.Where(r => !r.Equals(result))); // make sure we don't have duplicates - bag.Enqueue(record); - records[result.OriginQuery.RawQuery] = new ConcurrentBag(bag); + // add or update the record in the queue + var queue = new ConcurrentQueue(value.Where(r => !r.Equals(result))); // make sure we don't have duplicates + queue.Enqueue(record); + records[result.OriginQuery.RawQuery] = queue; } } } /// - /// Because ConcurrentBag does not support serialization, we need to convert it to a List + /// Because ConcurrentQueue does not support serialization, we need to convert it to a List /// - internal class ConcurrentDictionaryConcurrentBagConverter : JsonConverter>> + internal class ConcurrentDictionaryConcurrentQueueConverter : JsonConverter>> { - public override ConcurrentDictionary> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ConcurrentDictionary> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var dictionary = JsonSerializer.Deserialize>>(ref reader, options); - var concurrentDictionary = new ConcurrentDictionary>(); + var concurrentDictionary = new ConcurrentDictionary>(); foreach (var kvp in dictionary) { - concurrentDictionary.TryAdd(kvp.Key, new ConcurrentBag(kvp.Value)); + concurrentDictionary.TryAdd(kvp.Key, new ConcurrentQueue(kvp.Value)); } return concurrentDictionary; } - public override void Write(Utf8JsonWriter writer, ConcurrentDictionary> value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ConcurrentDictionary> value, JsonSerializerOptions options) { var dict = new Dictionary>(); foreach (var kvp in value) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 54ad6c288..adb04279e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1643,9 +1643,10 @@ namespace Flow.Launcher.ViewModel { foreach (var result in metaResults.Results) { - if (_topMostRecord.IsTopMost(result)) + var deviationIndex = _topMostRecord.GetTopMostIndex(result); + if (deviationIndex != -1) { - result.Score = Result.MaxScore; + result.Score = Result.MaxScore - deviationIndex; } else {