Skip to content

Commit ae21e13

Browse files
author
Declan Taylor
committed
Added new demo project, CachedPathSuggestBoxDemo.
Refactored DirectorySuggestionSource. Added LiteDb repository, LiteRepository, for storing paths to reuse.
1 parent 90b9d36 commit ae21e13

File tree

12 files changed

+832
-1
lines changed

12 files changed

+832
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,4 @@ ASALocalRun/
331331

332332
# MFractors (Xamarin productivity tool) working folder
333333
.mfractor/
334+
*.litedb
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Application x:Class="WpfApp1.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:WpfApp1"
5+
StartupUri="MainWindow.xaml">
6+
<Application.Resources>
7+
8+
</Application.Resources>
9+
</Application>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.Data;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using System.Windows;
8+
9+
namespace WpfApp1
10+
{
11+
/// <summary>
12+
/// Interaction logic for App.xaml
13+
/// </summary>
14+
public partial class App : Application
15+
{
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Windows;
2+
3+
[assembly: ThemeInfo(
4+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5+
//(used if a resource is not found in the page,
6+
// or application resource dictionaries)
7+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8+
//(used if a resource is not found in the page,
9+
// app, or any theme specific resource dictionaries)
10+
)]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
2+
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
<UseWPF>true</UseWPF>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="LiteDB" Version="5.0.7" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\..\SuggestBoxLib\SuggestBoxLib.csproj" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#nullable enable
2+
3+
namespace SuggestBoxTestLib.DataSources
4+
{
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
12+
/// <summary>
13+
/// Defines a suggestion object to generate suggestions
14+
/// based on sub entries of specified string.
15+
/// </summary>
16+
public class DirectorySuggestSource
17+
{
18+
#region fields
19+
private readonly Dictionary<string, CancellationTokenSource> _Queue;
20+
private readonly SemaphoreSlim _SlowStuffSemaphore;
21+
#endregion fields
22+
23+
#region ctors
24+
/// <summary>
25+
/// Class constructor
26+
/// </summary>
27+
public DirectorySuggestSource()
28+
{
29+
_Queue = new Dictionary<string, CancellationTokenSource>();
30+
_SlowStuffSemaphore = new SemaphoreSlim(1, 1);
31+
}
32+
#endregion ctors
33+
34+
public async Task<IEnumerable<object>?> MakeSuggestions(string queryThis)
35+
{
36+
// Cancel current task(s) if there is any...
37+
var queueList = _Queue.Values.ToList();
38+
39+
foreach (var t in queueList)
40+
t.Cancel();
41+
42+
var tokenSource = new CancellationTokenSource();
43+
_Queue.Add(queryThis, tokenSource);
44+
45+
// Make sure the task always processes the last input but is not started twice
46+
await _SlowStuffSemaphore.WaitAsync(tokenSource.Token);
47+
try
48+
{
49+
// There is more recent input to process so we ignore this one
50+
if (_Queue.Count <= 1)
51+
return await (string.IsNullOrEmpty(queryThis)
52+
? Task.FromResult(EnumerateSubDirs(queryThis))
53+
: Task.FromResult(queryThis.Length <= 3 ? EnumerateDrives(queryThis) : EnumerateSubDirs(queryThis)));
54+
_Queue.Remove(queryThis);
55+
return null;
56+
57+
}
58+
catch (Exception exp)
59+
{
60+
Console.WriteLine(exp.Message);
61+
}
62+
finally
63+
{
64+
_Queue.Remove(queryThis);
65+
_SlowStuffSemaphore.Release();
66+
}
67+
68+
return null;
69+
70+
71+
72+
static IEnumerable<object> EnumerateSubDirs(string input)
73+
{
74+
if (string.IsNullOrEmpty(input))
75+
return EnumerateLogicalDrives();
76+
77+
var subDirs = EnumerateLogicalDriveOrSubDirs(input, input);
78+
79+
return subDirs ?? Get();
80+
81+
// Find last separator and list directories underneath
82+
// with * search-pattern
83+
IEnumerable<object> Get()
84+
{
85+
int sepIdx = input.LastIndexOf('\\');
86+
87+
if (sepIdx >= input.Length) return EnumerateLogicalDrives();
88+
string folder = input.Substring(0, sepIdx + 1);
89+
string searchPattern = input.Substring(sepIdx + 1) + "*";
90+
string[]? directories = null;
91+
try
92+
{
93+
directories = System.IO.Directory.GetDirectories(folder, searchPattern);
94+
}
95+
catch
96+
{
97+
// Catch invalid path exceptions here ...
98+
}
99+
100+
if (directories == null) return EnumerateLogicalDrives();
101+
var dirs = new List<object>();
102+
103+
foreach (var t in directories)
104+
dirs.Add(new { Header = t, Value = t });
105+
106+
return dirs;
107+
}
108+
}
109+
110+
static IEnumerable<object> EnumerateDrives(string input)
111+
{
112+
if (string.IsNullOrEmpty(input))
113+
return EnumerateLogicalDrives();
114+
115+
return EnumeratePaths(input) ?? EnumerateLogicalDrives();
116+
117+
118+
static IEnumerable<object>? EnumeratePaths(string input) => input.Length switch
119+
{
120+
1 when char.ToUpper(input[0]) >= 'A' && char.ToUpper(input[0]) <= 'Z' =>
121+
EnumerateLogicalDriveOrSubDirs(input + ":\\", input),
122+
123+
2 when char.ToUpper(input[1]) == ':' &&
124+
char.ToUpper(input[0]) >= 'A' && char.ToUpper(input[0]) <= 'Z' =>
125+
EnumerateLogicalDriveOrSubDirs(input + "\\", input),
126+
127+
2 => new List<object>(),
128+
129+
_ when (char.ToUpper(input[1]) == ':' &&
130+
char.ToUpper(input[2]) == '\\' &&
131+
char.ToUpper(input[0]) >= 'A' && char.ToUpper(input[0]) <= 'Z') =>
132+
// Check if we know this drive and list it with sub-folders if we do
133+
EnumerateLogicalDriveOrSubDirs(input, input),
134+
_ => new List<object>()
135+
};
136+
}
137+
138+
139+
static IEnumerable<object> EnumerateLogicalDrives()
140+
{
141+
foreach (var driveName in Environment.GetLogicalDrives()
142+
.Where(driveName => string.IsNullOrEmpty(driveName) == false))
143+
{
144+
string header;
145+
146+
try
147+
{
148+
DriveInfo d = new DriveInfo(driveName);
149+
header = string.IsNullOrEmpty(d.VolumeLabel) == false
150+
? $"{d.VolumeLabel} ({d.Name})"
151+
: driveName;
152+
}
153+
catch
154+
{
155+
header = driveName;
156+
}
157+
158+
yield return new {Header = header, Value = driveName};
159+
}
160+
}
161+
162+
static IEnumerable<object>? EnumerateLogicalDriveOrSubDirs(string testDrive, string input)
163+
{
164+
return System.IO.Directory.Exists(testDrive) != true ?
165+
null :
166+
GetLogicalDriveOrSubDirs2(testDrive, input);
167+
168+
static IEnumerable<object> GetLogicalDriveOrSubDirs2(string testDrive, string input)
169+
{
170+
// List the drive itself if there was only 1 or 2 letters
171+
// since this is not a valid drive and we don'nt know if the user
172+
// wants to go to the drive or a folder contained in it
173+
if (input.Length <= 2)
174+
yield return new {Header = testDrive, Value = testDrive};
175+
176+
// and list all sub-directories of that drive
177+
foreach (var item in System.IO.Directory.GetDirectories(testDrive))
178+
yield return new {Header = item, Value = item};
179+
}
180+
}
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)