Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.IO;
using System.Text.Json;
Expand Down Expand Up @@ -37,6 +37,11 @@ protected JsonStorage()
{
}

/// <summary>
/// Initializes a new instance of the <c>JsonStorage&lt;T&gt;</c> class for the specified file path.
/// </summary>
/// <param name="filePath">The full path to the JSON file used for storage.</param>
/// <exception cref="ArgumentException">Thrown if the provided file path does not contain a valid directory.</exception>
public JsonStorage(string filePath)
{
FilePath = filePath;
Expand All @@ -45,11 +50,18 @@ public JsonStorage(string filePath)
FilesFolders.ValidateDirectory(DirectoryPath);
}

/// <summary>
/// Determines whether the main JSON storage file exists on disk.
/// </summary>
/// <returns>True if the storage file exists; otherwise, false.</returns>
public bool Exists()
{
return File.Exists(FilePath);
}

/// <summary>
/// Deletes the main JSON storage file, its backup, and temporary file if they exist.
/// </summary>
public void Delete()
{
foreach (var path in new[] { FilePath, BackupFilePath, TempFilePath })
Expand All @@ -61,6 +73,10 @@ public void Delete()
}
}

/// <summary>
/// Asynchronously loads and deserializes the JSON file into <c>Data</c>, falling back to a backup or a default instance if necessary.
/// </summary>
/// <returns>The loaded or newly created instance of <typeparamref name="T"/>.</returns>
public async Task<T> LoadAsync()
{
if (Data != null)
Expand Down
66 changes: 65 additions & 1 deletion Flow.Launcher/Storage/TopMostRecord.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -14,6 +14,12 @@ public class FlowLauncherJsonStorageTopMostRecord
private readonly FlowLauncherJsonStorage<MultipleTopMostRecord> _topMostRecordStorage;
private readonly MultipleTopMostRecord _topMostRecord;

/// <summary>
/// Initializes the top most records storage, handling migration from the old single-record-per-query format to the new multiple-records-per-query format if necessary.
/// </summary>
/// <remarks>
/// If new-format data exists, it loads it and deletes any old-format data. If only old-format data exists, it migrates the data to the new format, deletes the old data, and saves the new structure. If neither exists, it initializes an empty new-format storage.
/// </remarks>
public FlowLauncherJsonStorageTopMostRecord()
{
// Get old data & new data
Expand Down Expand Up @@ -63,21 +69,35 @@ public FlowLauncherJsonStorageTopMostRecord()
}
}

/// <summary>
/// Persists the current top most records to storage.
/// </summary>
public void Save()
{
_topMostRecordStorage.Save();
}

/// <summary>
/// Determines whether the specified result is marked as top most in the current records.
/// </summary>
/// <param name="result">The result to check.</param>
/// <returns>True if the result is marked as top most; otherwise, false.</returns>
public bool IsTopMost(Result result)
{
return _topMostRecord.IsTopMost(result);
}

/// <summary>
/// Removes the specified result from the top most records if it exists.
/// </summary>
public void Remove(Result result)
{
_topMostRecord.Remove(result);
}

/// <summary>
/// Adds a result to the top most records or updates it if it already exists.
/// </summary>
public void AddOrUpdate(Result result)
{
_topMostRecord.AddOrUpdate(result);
Expand All @@ -92,6 +112,11 @@ internal class TopMostRecord
[JsonInclude]
public ConcurrentDictionary<string, Record> records { get; private set; } = new();

/// <summary>
/// Determines whether the specified result is the top most record for its originating query.
/// </summary>
/// <param name="result">The result to check.</param>
/// <returns>True if the result matches the stored top most record for its query; otherwise, false.</returns>
internal bool IsTopMost(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
Expand All @@ -118,6 +143,10 @@ internal void Remove(Result result)
records.Remove(result.OriginQuery.RawQuery, out _);
}

/// <summary>
/// Adds or updates the top most record for the specified result's query, replacing any existing record for that query.
/// </summary>
/// <param name="result">The result whose information is to be stored as the top most record for its originating query. If <c>OriginQuery</c> is null, no action is taken.</param>
internal void AddOrUpdate(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
Expand Down Expand Up @@ -147,6 +176,10 @@ internal class MultipleTopMostRecord
[JsonConverter(typeof(ConcurrentDictionaryConcurrentBagConverter))]
public ConcurrentDictionary<string, ConcurrentBag<Record>> records { get; private set; } = new();

/// <summary>
/// Migrates all records from an existing <see cref="TopMostRecord"/> into this multiple-records-per-query structure.
/// </summary>
/// <param name="topMostRecord">The old single-record-per-query data structure to migrate from.</param>
internal void Add(TopMostRecord topMostRecord)
{
if (topMostRecord == null || topMostRecord.records.IsEmpty)
Expand All @@ -164,6 +197,11 @@ internal void Add(TopMostRecord topMostRecord)
}
}

/// <summary>
/// Determines whether the specified result is marked as top most for its originating query.
/// </summary>
/// <param name="result">The result to check.</param>
/// <returns>True if the result is a top most record for its query; otherwise, false.</returns>
internal bool IsTopMost(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
Expand All @@ -178,6 +216,10 @@ internal bool IsTopMost(Result result)
return value.Any(record => record.Equals(result));
}

/// <summary>
/// Removes a matching record for the given result from the collection of top most records for its query.
/// </summary>
/// <param name="result">The result whose corresponding record should be removed.</param>
internal void Remove(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
Expand All @@ -202,6 +244,10 @@ internal void Remove(Result result)
}
}

/// <summary>
/// Adds a result as a top most record for its originating query, or updates the existing record if it already exists.
/// </summary>
/// <param name="result">The result to add or update as top most for its query. Ignored if the result's OriginQuery is null.</param>
internal void AddOrUpdate(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
Expand Down Expand Up @@ -254,6 +300,13 @@ internal void AddOrUpdate(Result result)
/// </summary>
internal class ConcurrentDictionaryConcurrentBagConverter : JsonConverter<ConcurrentDictionary<string, ConcurrentBag<Record>>>
{
/// <summary>
/// Deserializes JSON into a <see cref="ConcurrentDictionary{TKey, TValue}"/> mapping strings to <see cref="ConcurrentBag{T}"/> of <see cref="Record"/>.
/// </summary>
/// <param name="reader">The JSON reader positioned at the start of the object.</param>
/// <param name="typeToConvert">The type to convert (ignored).</param>
/// <param name="options">Serialization options to use during deserialization.</param>
/// <returns>A concurrent dictionary where each key maps to a concurrent bag of records.</returns>
public override ConcurrentDictionary<string, ConcurrentBag<Record>> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dictionary = JsonSerializer.Deserialize<Dictionary<string, List<Record>>>(ref reader, options);
Expand All @@ -265,6 +318,12 @@ public override ConcurrentDictionary<string, ConcurrentBag<Record>> Read(ref Utf
return concurrentDictionary;
}

/// <summary>
/// Serializes a <see cref="ConcurrentDictionary{TKey, TValue}"/> of <see cref="ConcurrentBag{T}"/> records to JSON by converting each bag to a list.
/// </summary>
/// <param name="writer">The JSON writer to which the data will be serialized.</param>
/// <param name="value">The concurrent dictionary containing bags of records to serialize.</param>
/// <param name="options">Serialization options to use.</param>
public override void Write(Utf8JsonWriter writer, ConcurrentDictionary<string, ConcurrentBag<Record>> value, JsonSerializerOptions options)
{
var dict = new Dictionary<string, List<Record>>();
Expand All @@ -283,6 +342,11 @@ internal class Record
public string PluginID { get; init; }
public string RecordKey { get; init; }

/// <summary>
/// Determines whether the current record is equal to the specified result based on key or identifying properties.
/// </summary>
/// <param name="r">The result to compare with this record.</param>
/// <returns>True if the records are considered equal; otherwise, false.</returns>
public bool Equals(Result r)
{
if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey))
Expand Down
7 changes: 6 additions & 1 deletion Flow.Launcher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
Expand Down Expand Up @@ -54,6 +54,9 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable

#region Constructor

/// <summary>
/// Initializes a new instance of the MainViewModel class, setting up application state, loading persistent data, configuring results view models, and registering property change handlers and background update tasks.
/// </summary>
public MainViewModel()
{
_queryTextBeforeLeaveResults = "";
Expand Down Expand Up @@ -1605,6 +1608,8 @@ public async void Hide()

/// <summary>
/// Save history, user selected records and top most records
/// <summary>
/// Persists the current history, user-selected records, and top-most records to storage.
/// </summary>
public void Save()
{
Expand Down
Loading