Skip to content

Commit a43eeea

Browse files
author
Declan Taylor
committed
Splits CachedPathSuggestBoxDemo into two projects and deletes original.
Moves validation to CollectionEmptyValidationRule. User NameOf Markup to assign ValuePath to SuggestBox. Improves logic for retrieving cached paths as well as file-system paths. Removes use of DataTemplateSelector; and replaces with more informative datatemplates Uses Net 5.
1 parent 2726129 commit a43eeea

40 files changed

+1185
-1426
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net5.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="LiteDB" Version="5.0.11" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\Infrastructure\Infrastructure.csproj" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
6+
namespace CachedPathSuggest.Infrastructure
7+
{
8+
internal static class DirectoryHelper
9+
{
10+
public static IEnumerable<(string path, string? label)>? EnumerateSubDirectories(string input)
11+
{
12+
var subDirs = EnumerateLogicalDriveOrSubDirectories(input);
13+
14+
// ReSharper disable PossibleMultipleEnumeration
15+
return subDirs != null
16+
? subDirs.Any()
17+
? subDirs.Select(a => (a, (string?)null)) : EnumerateChildren(input)
18+
: new[] { (input, (string?)"Unauthorized Access") };
19+
20+
// Find last separator and list directories underneath with * search-pattern
21+
static IEnumerable<(string, string?)> EnumerateChildren(string input)
22+
{
23+
var sepIdx = input.LastIndexOf('\\');
24+
25+
if (sepIdx >= input.Length)
26+
return EnumerateLogicalDrives();
27+
28+
string folder = input.Substring(0, sepIdx + 1);
29+
string searchPattern = input.Substring(sepIdx + 1) + "*";
30+
try
31+
{
32+
return Directory
33+
.GetDirectories(folder, searchPattern)
34+
.Select(a => (a, (string?)null)).ToList();
35+
}
36+
catch
37+
{
38+
// Catch invalid path exceptions here ...
39+
return EnumerateLogicalDrives();
40+
}
41+
}
42+
43+
static IEnumerable<string>? EnumerateLogicalDriveOrSubDirectories(string testDrive)
44+
{
45+
if (testDrive.Length < 3) return Enumerate(testDrive);
46+
47+
return Directory.Exists(testDrive) ? TryGetDirectories(testDrive) : Array.Empty<string>();
48+
49+
static string[]? TryGetDirectories(string testDrive)
50+
{
51+
try
52+
{
53+
return Directory.GetDirectories(testDrive);
54+
}
55+
catch (UnauthorizedAccessException)
56+
{
57+
return null;
58+
}
59+
}
60+
61+
static IEnumerable<string>? Enumerate(string input)
62+
{
63+
return (input.Length, input.TryGetUpper(0), input.TryGetUpper(1), input.TryGetUpper(2)) switch
64+
{
65+
(1, >= 'A' and <= 'Z', _, _) => new[] { input + ":\\" }.Concat(EnumerateLogicalDriveOrSubDirectories(input + ":\\") ?? Array.Empty<string>()),
66+
(2, >= 'A' and <= 'Z', ':', _) => new[] { input + ":\\" }.Concat(EnumerateLogicalDriveOrSubDirectories(input + "\\") ?? Array.Empty<string>()),
67+
(3, >= 'A' and <= 'Z', ':', '\\') => EnumerateLogicalDriveOrSubDirectories(input),
68+
_ => Array.Empty<string>()
69+
};
70+
}
71+
}
72+
}
73+
74+
public static IEnumerable<(string path, string? label)> EnumerateLogicalDrives()
75+
{
76+
foreach (var driveName in Environment.GetLogicalDrives())
77+
{
78+
if (string.IsNullOrEmpty(driveName))
79+
continue;
80+
81+
string? volumeLabel = null;
82+
83+
try
84+
{
85+
if (new DriveInfo(driveName) is { IsReady: true } driveInfo)
86+
volumeLabel = driveInfo.VolumeLabel;
87+
}
88+
catch (IOException)
89+
{
90+
// network path not found
91+
}
92+
catch (UnauthorizedAccessException)
93+
{
94+
// can't access path
95+
}
96+
97+
yield return (driveName, volumeLabel);
98+
}
99+
}
100+
}
101+
102+
internal static class StringHelper
103+
{
104+
public static char TryGetUpper(this string value, int index)
105+
{
106+
return char.ToUpper(value.ElementAtOrDefault(index));
107+
}
108+
}
109+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#nullable enable
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using LiteDB;
8+
9+
namespace CachedPathSuggest.Infrastructure
10+
{
11+
/// <summary>
12+
/// Implements a static service provider for searching and editing a LiteDB file.
13+
/// </summary>
14+
public class LiteRepository
15+
{
16+
private const string DbPath = @"..\..\..\Data\KeyValue.litedb";
17+
18+
// Collection needs a name because object, keyvaluepair<string,string> is generic.
19+
private const string CollectionName = "collection";
20+
21+
/// <summary>
22+
/// Class constructor
23+
/// </summary>
24+
public LiteRepository()
25+
{
26+
(Directory.GetParent(DbPath) ?? throw new Exception("Can't create directory")).Create();
27+
BsonMapper.Global.Entity<KeyValuePair<string, DateTime>>().Id(x => x.Key);
28+
}
29+
30+
public event Action? Change;
31+
32+
/// <summary>
33+
/// Inserts a new string into the collection of bookmarked strings.
34+
/// </summary>
35+
/// <param name="key"></param>
36+
/// <param name="collectionName"></param>
37+
public void Insert(string key, string? collectionName = null)
38+
{
39+
using var db = new LiteDatabase(DbPath);
40+
var col = db.GetCollection<KeyValuePair<string, DateTime>>(collectionName ?? CollectionName);
41+
col.Upsert(new KeyValuePair<string, DateTime>(key, DateTime.Now));
42+
Change?.Invoke();
43+
}
44+
45+
/// <summary>
46+
/// Inserts a new string into the collection of bookmarked strings.
47+
/// </summary>
48+
/// <param name="key"></param>
49+
/// <param name="collectionName"></param>
50+
public void Remove(string key, string? collectionName = null)
51+
{
52+
using var db = new LiteDatabase(DbPath);
53+
var col = db.GetCollection<KeyValuePair<string, DateTime>>(collectionName ?? CollectionName);
54+
55+
// Make sure string is already present
56+
if (col.FindOne(x => x.Key == key) is { } one)
57+
col.Delete(key);
58+
59+
Change?.Invoke();
60+
}
61+
62+
/// <summary>
63+
/// </summary>
64+
public KeyValuePair<string, DateTime>? Find(string key, string? collectionName = null)
65+
{
66+
using var db = new LiteDatabase(DbPath);
67+
var col = db.GetCollection<KeyValuePair<string, DateTime>>(collectionName ?? CollectionName);
68+
var one = col.FindOne(x => x.Key == key);
69+
return one;
70+
}
71+
72+
/// <summary>
73+
/// Filters the collection of bookmark strings by the given string and
74+
/// returns the resulting collection.
75+
/// </summary>
76+
public IEnumerable<KeyValuePair<string, DateTime>> Filter(string key, string? collectionName = null)
77+
{
78+
using var db = new LiteDatabase(DbPath);
79+
var col = db.GetCollection<KeyValuePair<string, DateTime>>(collectionName ?? CollectionName);
80+
return string.IsNullOrWhiteSpace(key)
81+
? col.Query().ToArray()
82+
: col.Find(Query.Contains(nameof(KeyValuePair<string, DateTime>.Key), key)).ToArray();
83+
}
84+
}
85+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using CachedPathSuggest.Infrastructure;
7+
using CachedPathSuggest.ViewModels;
8+
9+
namespace CachedPathSuggest.Service
10+
{
11+
/// <summary>
12+
/// Wraps a LiteDB to generate previously bookmarked suggestions through similarity for a given string.
13+
/// </summary>
14+
public class CachedPathInformationAsyncSuggest : IAsyncSuggest
15+
{
16+
private static readonly int NumberOfResultsToReturn = 5;
17+
private readonly LiteRepository repository;
18+
19+
public CachedPathInformationAsyncSuggest(LiteRepository repository)
20+
{
21+
this.repository = repository;
22+
}
23+
24+
/// <summary>
25+
/// Makes suggestions of paths based on match between query and the full-name of the path.
26+
/// Only returns latest <see cref="NumberOfResultsToReturn" /> results (newest first).
27+
/// </summary>
28+
/// <inheritdoc cref="SuggestAsync" />
29+
/// <example>
30+
/// <see cref="queryThis" /> : doc
31+
/// will match with
32+
/// c:\\Documents
33+
/// t:\\files\store\doC.xaml
34+
/// but not
35+
/// f:\\do_letters
36+
/// g:\\document\lists.ico
37+
/// </example>
38+
public async Task<IReadOnlyCollection<BaseItem>?> SuggestAsync(string queryThis)
39+
{
40+
return await Task.Run(() => GetPathInformations(queryThis, repository));
41+
42+
static CachedPathInformation[] GetPathInformations(string key, LiteRepository repository)
43+
{
44+
return repository
45+
.Filter(key)
46+
.Take(NumberOfResultsToReturn)
47+
.OrderByDescending(a => a.Value)
48+
.Select(a => new CachedPathInformation(a.Value, a.Key))
49+
.ToArray();
50+
}
51+
}
52+
53+
public void Insert(string path)
54+
{
55+
var fileInfo = new FileInfo(path);
56+
if (fileInfo.Exists) repository.Insert(fileInfo.FullName);
57+
58+
var directoryInfo = new DirectoryInfo(path);
59+
if (!directoryInfo.Exists)
60+
throw new Exception("Path does not exist");
61+
repository.Insert(directoryInfo.FullName);
62+
}
63+
64+
internal void Delete(string text)
65+
{
66+
repository.Remove(text);
67+
}
68+
69+
internal bool Match(string text)
70+
{
71+
return repository.Find(text) is not {Key: null};
72+
}
73+
}
74+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using CachedPathSuggest.Infrastructure;
6+
using CachedPathSuggest.ViewModels;
7+
8+
namespace CachedPathSuggest.Service
9+
{
10+
/// <summary>
11+
/// Wraps a LiteDB and a FileSystem data provider to generate similarity based suggestions
12+
/// for a given string.
13+
/// </summary>
14+
public class CombinedAsyncSuggest : IAsyncSuggest
15+
{
16+
private readonly CachedPathInformationAsyncSuggest
17+
cachedPathInformationAsyncSuggest = new(new LiteRepository());
18+
19+
private readonly DirectoryAsyncSuggest directoryAsyncSuggest = new();
20+
21+
/// <summary>
22+
/// Gets a list of combined suggestions based on string similarity from the:
23+
/// 1) cached entries (bookmarks) and
24+
/// 2) file system data provider.
25+
/// </summary>
26+
/// <param name="queryThis"></param>
27+
/// <returns>
28+
/// Both types (cached entries and file system provider entries) of entries are returned
29+
/// in a single list. The list contains a separator item (if both types of items are returned).
30+
/// The separator item can be used for visual enhancement when displaying the list.
31+
/// </returns>
32+
public async Task<IReadOnlyCollection<BaseItem>?> SuggestAsync(string queryThis)
33+
{
34+
var cachedSuggestions = await cachedPathInformationAsyncSuggest.SuggestAsync(queryThis);
35+
var directorySuggestions = await directoryAsyncSuggest.SuggestAsync(queryThis);
36+
return (cachedSuggestions, directorySuggestions) switch
37+
{
38+
(null, null) => null,
39+
({Count: 0}, {Count: 0}) => null,
40+
({Count: 0}, {Count: > 0}) => directorySuggestions,
41+
({Count: > 0}, {Count: 0}) => cachedSuggestions,
42+
({Count: > 0} c, {Count: > 0} d) => c
43+
.Concat(new[] {new ItemSeparator()}).Concat(d).ToArray(),
44+
_ => throw new ArgumentOutOfRangeException()
45+
};
46+
}
47+
48+
/// <summary>
49+
/// Insert a new suggestion into the available list of suggestions
50+
/// </summary>
51+
/// <param name="text"></param>
52+
public void AddCachedSuggestion(string text)
53+
{
54+
cachedPathInformationAsyncSuggest.Insert(text);
55+
}
56+
57+
/// <summary>
58+
/// Delete a suggestion in the available list of suggestions
59+
/// </summary>
60+
/// <param name="text"></param>
61+
public void RemoveCachedSuggestion(string text)
62+
{
63+
cachedPathInformationAsyncSuggest.Delete(text);
64+
}
65+
66+
/// <summary>
67+
/// Insert a new suggestion into the available list of suggestions
68+
/// </summary>
69+
/// <param name="text"></param>
70+
public bool ContainsSuggestion(string text)
71+
{
72+
return cachedPathInformationAsyncSuggest.Match(text);
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)