Skip to content

Commit c928a79

Browse files
authored
Merge pull request #2 from dtwk2/development_demo
Added new demo project, CachedPathSuggestBoxDemo.
2 parents 90b9d36 + a9a05cf commit c928a79

17 files changed

+930
-2
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Application x:Class="CachedPathSuggestBoxDemo.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
StartupUri="MainWindow.xaml">
5+
<Application.Resources>
6+
7+
</Application.Resources>
8+
</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 CachedPathSuggestBoxDemo
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
<ItemGroup>
18+
<Folder Include="Data\" />
19+
</ItemGroup>
20+
21+
</Project>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace CachedPathSuggestBoxDemo.Infrastructure
9+
{
10+
11+
public class CachedPathInformationSuggest :ISuggest
12+
{
13+
private static readonly int NumberOfResultsReturned = 5;
14+
15+
public void Insert(string path)
16+
{
17+
var fileInfo =new FileInfo(path);
18+
if (fileInfo.Exists)
19+
{
20+
LiteRepository.Instance.Insert(fileInfo.FullName);
21+
}
22+
23+
var directoryInfo = new DirectoryInfo(path);
24+
if (!directoryInfo.Exists) return;
25+
LiteRepository.Instance.Insert(directoryInfo.FullName);
26+
}
27+
28+
/// <summary>
29+
/// Makes suggestions of paths based on match between query and the full-name of the path.
30+
/// Only returns latest <see cref="NumberOfResultsReturned"/> results (newest first).
31+
/// </summary>
32+
/// <inheritdoc cref="MakeSuggestions"/>
33+
/// <example>
34+
/// <see cref="queryThis"/> : doc
35+
/// will match with
36+
/// c:\\Documents
37+
/// t:\\files\store\doC.xaml
38+
/// but not
39+
/// f:\\do_letters
40+
/// g:\\document\lists.ico
41+
/// </example>
42+
public async Task<IEnumerable<object>?> MakeSuggestions(string queryThis)
43+
{
44+
return await Task.Run(() => MakeSuggestionsPrivate().ToArray());
45+
46+
IEnumerable<object> MakeSuggestionsPrivate() =>
47+
from item in GetPathInformations(queryThis)
48+
select new { Header = item.FullName, Value = item.FullName };
49+
50+
static PathInformation[] GetPathInformations(string key)
51+
{
52+
return LiteRepository.Instance.Filter(key).OrderByDescending(a => a.Value).Take(NumberOfResultsReturned).Select(a => new PathInformation(a.Key)).ToArray();
53+
}
54+
}
55+
}
56+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace CachedPathSuggestBoxDemo.Infrastructure
9+
{
10+
class CombinedSuggest:ISuggest
11+
{
12+
private readonly DirectorySuggest directorySuggest = new DirectorySuggest();
13+
14+
public readonly CachedPathInformationSuggest CachedPathInformationSuggest = new CachedPathInformationSuggest();
15+
16+
public async Task<IEnumerable<object>?> MakeSuggestions(string queryThis)
17+
{
18+
19+
var suggestions1 = (await CachedPathInformationSuggest.MakeSuggestions(queryThis))?.ToArray();
20+
21+
var suggestions2 = (await directorySuggest.MakeSuggestions(queryThis))?.ToArray();
22+
23+
if (suggestions2 == null && suggestions1?.Any()==false)
24+
{
25+
return null;
26+
}
27+
28+
return suggestions2!=null ?
29+
suggestions1?.Concat(suggestions2) :
30+
suggestions1;
31+
}
32+
}
33+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
#nullable enable
9+
10+
namespace CachedPathSuggestBoxDemo.Infrastructure
11+
{
12+
/// <summary>
13+
/// Defines a suggestion object to generate suggestions
14+
/// based on sub entries of specified string.
15+
/// </summary>
16+
public class DirectorySuggest : ISuggest
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 DirectorySuggest()
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!=null? subDirs.Any()? subDirs: Get():null;
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 = 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) ?
165+
GetDirectories(testDrive) is {} array?
166+
GetLogicalDriveOrSubDirs2(testDrive, input, array):
167+
null:
168+
null;
169+
170+
171+
static IEnumerable<object>? GetLogicalDriveOrSubDirs2(string testDrive, string input, IEnumerable<string> directories)
172+
{
173+
// List the drive itself if there was only 1 or 2 letters
174+
// since this is not a valid drive and we don'nt know if the user
175+
// wants to go to the drive or a folder contained in it
176+
if (input.Length <= 2)
177+
yield return new {Header = testDrive, Value = testDrive};
178+
179+
foreach (var item in directories)
180+
yield return new { Header = item, Value = item };
181+
}
182+
183+
static string[]? GetDirectories(string testDrive)
184+
{
185+
string[] directories;
186+
try
187+
{
188+
directories = Directory.GetDirectories(testDrive);
189+
}
190+
catch (UnauthorizedAccessException)
191+
{
192+
return null;
193+
}
194+
195+
return directories;
196+
}
197+
}
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)