Skip to content

Commit 3803984

Browse files
committed
task complete command
1 parent 1a7d3dc commit 3803984

File tree

12 files changed

+425
-36
lines changed

12 files changed

+425
-36
lines changed

Figment.Common/Data/IThingStorageProvider.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections;
20+
1921
namespace Figment.Common.Data;
2022

2123
/// <summary>
@@ -66,6 +68,23 @@ public interface IThingStorageProvider
6668
/// <returns>An enumeration of references to every <see cref="Thing"/> that adheres to the specified <paramref name="schemaGuid"/>.</returns>
6769
public IAsyncEnumerable<Reference> GetBySchemaAsync(string schemaGuid, CancellationToken cancellationToken);
6870

71+
/// <summary>
72+
/// Retrieves an enumeration of references to every <see cref="Thing"/> that adheres to the specified <paramref name="schemaGuid"/>
73+
/// and also has a specific property value.
74+
/// </summary>
75+
/// <param name="schemaGuid">Unique identifier of the schema selected objects must implement.</param>
76+
/// <param name="propName">Name of the property on the thing for which to check the property value.</param>
77+
/// <param name="propValue">Value of the property on the thing to compare.</param>
78+
/// <param name="comparer">The comparer to use when comparing the value in the potential thing's <paramref name="propName"/> value with the comparison value specified in <paramref name="propValue"/>.</param>
79+
/// <param name="cancellationToken">The cancellation token.</param>
80+
/// <returns>The thing, if it was found. If the thing was not located, <see cref="Reference.EMPTY"/> is returned.</returns>
81+
public IAsyncEnumerable<Thing> FindBySchemaAndPropertyValue(
82+
string schemaGuid,
83+
string propName,
84+
object? propValue,
85+
IComparer comparer,
86+
CancellationToken cancellationToken);
87+
6988
/// <summary>
7089
/// Obtains a reference to a thing if it is located by its <paramref name="exactName"/>.
7190
/// </summary>

Figment.Common/Data/ThingStorageProviderBase.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections;
20+
1921
namespace Figment.Common.Data;
2022

2123
/// <summary>
@@ -47,6 +49,14 @@ public abstract class ThingStorageProviderBase : IThingStorageProvider
4749
/// <inheritdoc/>
4850
public abstract IAsyncEnumerable<Reference> GetBySchemaAsync(string schemaGuid, CancellationToken cancellationToken);
4951

52+
/// <inheritdoc/>
53+
public abstract IAsyncEnumerable<Thing> FindBySchemaAndPropertyValue(
54+
string schemaGuid,
55+
string propName,
56+
object? propValue,
57+
IComparer comparer,
58+
CancellationToken cancellationToken);
59+
5060
/// <inheritdoc/>
5161
public abstract Task<bool> GuidExists(string thingGuid, CancellationToken cancellationToken);
5262

Figment.Common/SchemaDateField.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,15 @@ public partial class SchemaDateField(string Name) : SchemaTextField(Name)
4444
/// Formats that this date field will attempt to parse exactly, such as RFC 3339 formats.
4545
/// </summary>
4646
internal static readonly string[] _completeFormats = [
47+
// 2025-06-01T17:12:10.2305983-05:00
4748
"yyyy-MM-ddTHH:mm:ssK",
49+
"yyyy-MM-ddTHH:mm:ss.fffffffK",
50+
"yyyy-MM-ddTHH:mm:ss.ffffffK",
51+
"yyyy-MM-ddTHH:mm:ss.fffffK",
52+
"yyyy-MM-ddTHH:mm:ss.ffffK",
53+
"yyyy-MM-ddTHH:mm:ss.fffK",
4854
"yyyy-MM-ddTHH:mm:ss.ffK",
55+
"yyyy-MM-ddTHH:mm:ss.fK",
4956
"yyyy-MM-ddTHH:mm:ssZ",
5057
"yyyy-MM-ddTHH:mm:ss.ffZ",
5158

Figment.Common/Thing.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,12 +1076,6 @@ public static bool IsThingNameValid(string thingName)
10761076
return false;
10771077
}
10781078

1079-
// Cannot start with a symbol.
1080-
if (char.IsSymbol(thingName, 0))
1081-
{
1082-
return false;
1083-
}
1084-
10851079
return true;
10861080
}
10871081

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
Figment
3+
Copyright (C) 2025 Sean McElroy
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
namespace Figment.Common;
20+
21+
using System.Collections;
22+
23+
/// <summary>
24+
/// A comparer of two unsigned numbers that are of various underlying types,
25+
/// but all of which can be compared as <see cref="ulong"/>.
26+
/// </summary>
27+
public class UnsignedNumberComparer : IComparer
28+
{
29+
/// <inheritdoc/>
30+
public int Compare(object? x, object? y)
31+
{
32+
if (x == null && y == null)
33+
{
34+
return 0;
35+
}
36+
37+
if (x == null)
38+
{
39+
return -1;
40+
}
41+
42+
if (y == null)
43+
{
44+
return 1;
45+
}
46+
47+
ulong xx, yy;
48+
49+
if (x is ulong xul)
50+
{
51+
xx = xul;
52+
}
53+
else if (x is long xl)
54+
{
55+
xx = Convert.ToUInt64(xl);
56+
}
57+
else if (x is uint xui)
58+
{
59+
xx = Convert.ToUInt64(xui);
60+
}
61+
else if (x is int xi)
62+
{
63+
xx = Convert.ToUInt64(xi);
64+
}
65+
else
66+
{
67+
return -1;
68+
}
69+
70+
if (y is ulong yul)
71+
{
72+
yy = yul;
73+
}
74+
else if (y is long yl)
75+
{
76+
yy = Convert.ToUInt64(yl);
77+
}
78+
else if (y is uint yui)
79+
{
80+
yy = Convert.ToUInt64(yui);
81+
}
82+
else if (y is int yi)
83+
{
84+
yy = Convert.ToUInt64(yi);
85+
}
86+
else
87+
{
88+
return 1;
89+
}
90+
91+
return xx.CompareTo(yy);
92+
}
93+
}

Figment.Data.Local/LocalDirectoryThingStorageProvider.cs

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections;
1920
using System.Runtime.CompilerServices;
2021
using System.Text.Json;
2122
using Figment.Common;
@@ -192,19 +193,88 @@ public override async IAsyncEnumerable<Reference> GetBySchemaAsync(string schema
192193
Type = Reference.ReferenceType.Thing
193194
};
194195
}
196+
}
197+
else
198+
{
199+
// No index, so go the expensive route
200+
AmbientErrorContext.Provider.LogWarning($"Missing index at: {indexFilePath}");
201+
await foreach (var (reference, name) in GetAll(cancellationToken))
202+
{
203+
if (cancellationToken.IsCancellationRequested)
204+
yield break;
205+
206+
var thing = await LoadAsync(reference.Guid, cancellationToken);
207+
if (thing != null && thing.SchemaGuids.Any(s => string.Equals(s, schemaGuid, StringComparison.Ordinal)))
208+
yield return reference;
209+
}
210+
}
211+
}
212+
213+
/// <inheritdoc/>
214+
public override async IAsyncEnumerable<Thing> FindBySchemaAndPropertyValue(
215+
string schemaGuid,
216+
string propName,
217+
object? propValue,
218+
IComparer comparer,
219+
[EnumeratorCancellation] CancellationToken cancellationToken)
220+
{
221+
// This might look a lot like GetBySchemaAsync, but it is a special version
222+
// that holds onto the loaded thing object for property testing.
223+
// It also cannot use quite the same index-shortcut in GetBySchemaAsync because
224+
// it returns a fully loaded Thing, not just a reference.
225+
ArgumentException.ThrowIfNullOrWhiteSpace(schemaGuid);
226+
ArgumentException.ThrowIfNullOrWhiteSpace(propName);
227+
ArgumentNullException.ThrowIfNull(comparer);
228+
229+
var thingDir = new DirectoryInfo(ThingDirectoryPath);
230+
if (!thingDir.Exists)
195231
yield break;
232+
233+
async Task<bool> thingMatches(Thing thing)
234+
{
235+
var prop = await thing.GetPropertyByTrueNameAsync(propName, cancellationToken);
236+
if (prop == null)
237+
return false; // Property is missing from thing, so it's not a valid thing of the specified schema.
238+
239+
if (!prop.HasValue && propValue == null)
240+
return true; // We want nulls, and this is null.
241+
242+
if (!prop.HasValue)
243+
return false; // We do not want nulls, and this is null.
244+
245+
return comparer.Compare(prop.Value.Value, propValue) == 0;
196246
}
197247

198-
// No index, so go the expensive route
199-
AmbientErrorContext.Provider.LogWarning($"Missing index at: {indexFilePath}");
200-
await foreach (var thingRef in GetAll(cancellationToken))
248+
var indexFilePath = Path.Combine(thingDir.FullName, $"_thing.schema.{schemaGuid}.csv");
249+
if (File.Exists(indexFilePath))
201250
{
202-
if (cancellationToken.IsCancellationRequested)
203-
yield break;
251+
// Use index
252+
await foreach (var entry in IndexManager.LookupAsync(indexFilePath, e => true, cancellationToken))
253+
{
254+
if (cancellationToken.IsCancellationRequested)
255+
yield break;
204256

205-
var thing = await LoadAsync(thingRef.reference.Guid, cancellationToken);
206-
if (thing != null && thing.SchemaGuids.Any(s => string.Equals(s, schemaGuid, StringComparison.Ordinal)))
207-
yield return thingRef.reference;
257+
var guid = Path.GetFileName(entry.Value).Split('.')[0];
258+
var thing = await LoadAsync(guid, cancellationToken);
259+
if (thing != null && await thingMatches(thing))
260+
yield return thing;
261+
}
262+
}
263+
else
264+
{
265+
// No index, so go the expensive route
266+
AmbientErrorContext.Provider.LogWarning($"Missing index at: {indexFilePath}");
267+
await foreach (var (reference, name) in GetAll(cancellationToken))
268+
{
269+
if (cancellationToken.IsCancellationRequested)
270+
yield break;
271+
272+
var thing = await LoadAsync(reference.Guid, cancellationToken);
273+
if (thing != null
274+
&& thing.SchemaGuids.Any(s => string.Equals(s, schemaGuid, StringComparison.Ordinal))
275+
&& await thingMatches(thing))
276+
yield return thing;
277+
}
208278
}
209279
}
210280

Figment.Data.Memory/MemoryThingStorageProvider.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You should have received a copy of the GNU Affero General Public License
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
using System.Collections;
1920
using System.Runtime.CompilerServices;
2021
using Figment.Common;
2122
using Figment.Common.Data;
@@ -135,6 +136,46 @@ public override async IAsyncEnumerable<Reference> GetBySchemaAsync(string schema
135136
}
136137
}
137138

139+
/// <inheritdoc/>
140+
public override async IAsyncEnumerable<Thing> FindBySchemaAndPropertyValue(
141+
string schemaGuid,
142+
string propName,
143+
object? propValue,
144+
IComparer comparer,
145+
[EnumeratorCancellation] CancellationToken cancellationToken)
146+
{
147+
ArgumentException.ThrowIfNullOrWhiteSpace(schemaGuid);
148+
ArgumentException.ThrowIfNullOrWhiteSpace(propName);
149+
ArgumentNullException.ThrowIfNull(comparer);
150+
151+
async Task<bool> thingMatches(Thing thing)
152+
{
153+
var prop = await thing.GetPropertyByTrueNameAsync($"{schemaGuid}.{propName}", cancellationToken);
154+
if (prop == null)
155+
return false; // Property is missing from thing, so it's not a valid thing of the specified schema.
156+
157+
if (!prop.HasValue && propValue == null)
158+
return true; // We want nulls, and this is null.
159+
160+
if (!prop.HasValue)
161+
return false; // We do not want nulls, and this is null.
162+
163+
return comparer.Compare(prop.Value, propValue) == 0;
164+
}
165+
166+
await foreach (var (reference, name) in GetAll(cancellationToken))
167+
{
168+
if (cancellationToken.IsCancellationRequested)
169+
yield break;
170+
171+
var thing = await LoadAsync(reference.Guid, cancellationToken);
172+
if (thing != null
173+
&& thing.SchemaGuids.Any(s => string.Equals(s, schemaGuid, StringComparison.Ordinal))
174+
&& await thingMatches(thing))
175+
yield return thing;
176+
}
177+
}
178+
138179
/// <inheritdoc/>
139180
public override Task<bool> GuidExists(string thingGuid, CancellationToken _)
140181
{

jot/Commands/Tasks/AddTaskCommandSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class AddTaskCommandSettings : CommandSettings
5252
/// Gets a value that indicates the status of the task.
5353
/// </summary>
5454
[Description("Creates the task with the specified status.")]
55-
[CommandOption("--status <STATUSES>")]
55+
[CommandOption("-s|--status <STATUS>")]
5656
public string? Status { get; init; } = null;
5757

5858
/// <inheritdoc/>

0 commit comments

Comments
 (0)