Skip to content

Commit d2c6647

Browse files
committed
Update async
1 parent a9dad90 commit d2c6647

11 files changed

Lines changed: 205 additions & 68 deletions

File tree

src/AdvantOfCode/DayGenerator.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ public static Solution GetByName(string name, string middleNamespace)
1414
return CreateInstance(solutionType);
1515
}
1616

17+
public static SolutionAsync GetAsyncByName(string name, string middleNamespace)
18+
{
19+
var solutionType = GetSolutionAsyncTypes()
20+
.Where(x => MatchesNamespace(x, middleNamespace))
21+
.FirstOrDefault(x => x.Name == name);
22+
if (solutionType == null) throw new Exception($"Cannot find {middleNamespace}.{name}");
23+
return CreateInstance<SolutionAsync>(solutionType);
24+
}
25+
1726
public static Solution GetSolutionByDay(int year, int day)
1827
{
1928
var solutionType = GetSolutionTypes()
@@ -35,6 +44,18 @@ public static IEnumerable<Solution> GetSolutionsByDay(int year, int day)
3544
yield return CreateInstance(solutionType);
3645
}
3746
}
47+
48+
public static IEnumerable<SolutionAsync> GetSolutionAsyncByDay(int year, int day)
49+
{
50+
var solutionTypes = GetSolutionAsyncTypes()
51+
.Where(x => x.GetCustomAttribute<DayInfoAttribute>() is DayInfoAttribute dayInfo
52+
&& dayInfo.Year == year
53+
&& dayInfo.Day == day);
54+
foreach (Type solutionType in solutionTypes)
55+
{
56+
yield return CreateInstance<SolutionAsync>(solutionType);
57+
}
58+
}
3859

3960
private static bool MatchesNamespace(Type t, string name)
4061
{
@@ -50,11 +71,23 @@ private static List<Type> GetSolutionTypes() =>
5071
.Where(x => x.IsAssignableTo(typeof(Solution)) && !x.IsAbstract)
5172
.OrderBy(x => x.Name)
5273
.ToList();
74+
75+
private static List<Type> GetSolutionAsyncTypes() =>
76+
Assembly.GetAssembly(typeof(Program))!
77+
.ExportedTypes
78+
.Where(x => x.IsAssignableTo(typeof(SolutionAsync)) && !x.IsAbstract)
79+
.OrderBy(x => x.Name)
80+
.ToList();
5381

5482
private static Solution CreateInstance(Type t)
5583
{
5684
return (Activator.CreateInstance(t) as Solution)!;
5785
}
86+
87+
private static T CreateInstance<T>(Type t) where T : class
88+
{
89+
return (Activator.CreateInstance(t) as T)!;
90+
}
5891

5992
public static async Task AddDayInfoAttribute(int year)
6093
{
@@ -113,11 +146,11 @@ public static string FillSolutionTemplate(int year, int day)
113146
namespace AdventOfCode.{{yearString}}.{{dayString}};
114147
115148
[DayInfo({{year}}, {{day:D2}})]
116-
public class {{className}} : Solution
149+
public class {{className}} : SolutionAsync
117150
{
118-
public string Run()
151+
public Task<string> RunAsync()
119152
{
120-
string[] input = this.ReadLines();
153+
string[] input = await this.ReadLinesAsync();
121154
return "1" + "\n";
122155
}
123156
}

src/AdvantOfCode/Extensions/SolutionExtensions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,35 @@ public static string[] ReadLines(this Solution solution, StringSplitOptions opti
2828
.Split(Environment.NewLine, options);
2929
}
3030

31+
public static async Task<string[]> ReadLinesAsync(this SolutionAsync solution, StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries, [CallerFilePath] string path = "")
32+
{
33+
DayInfoAttribute? dayInfo = GetDayInfo(solution);
34+
ArgumentNullException.ThrowIfNull(dayInfo, "No DayInfo attribute found on solution");
35+
36+
string inputFileName = $"input{dayInfo.Day:D2}.txt";
37+
38+
string root = RelativeToRoot(path);
39+
string relativePath = Path.Combine(root, "input", dayInfo.Year.ToString(), inputFileName);
40+
string newInputPath = Path.Combine(Path.GetDirectoryName(path)!, relativePath);
41+
string file = await InputReader.ReadFileAsync(dayInfo.Year, dayInfo.Day, newInputPath);
42+
string symlinkPath = Path.Combine(Path.GetDirectoryName(path)!, inputFileName);
43+
44+
if (!File.Exists(symlinkPath))
45+
{
46+
File.CreateSymbolicLink(symlinkPath, relativePath);
47+
}
48+
49+
return file
50+
.ReplaceLineEndings()
51+
.Split(Environment.NewLine, options);
52+
}
53+
3154
private static DayInfoAttribute? GetDayInfo(this Solution solution) =>
3255
solution.GetType().GetCustomAttribute<DayInfoAttribute>();
3356

57+
private static DayInfoAttribute? GetDayInfo(this SolutionAsync solution) =>
58+
solution.GetType().GetCustomAttribute<DayInfoAttribute>();
59+
3460
private static string RelativeToRoot(string fromPath)
3561
{
3662
var directoryInfo = Directory.GetParent(fromPath);

src/AdvantOfCode/InputReader.cs

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,33 @@ public static string ReadFile(int year, int day, string inputFilePath)
1515
Directory.CreateDirectory(directory);
1616
}
1717

18-
string? session = AoCClient.GetSessionAsync().Result;
18+
string? session = AoCClient.GetSession();
19+
ArgumentNullException.ThrowIfNull(session);
1920
AoCClient downloader = new AoCClient(session);
20-
string text = downloader.DownloadInputAsync(year, day).Result;
21+
string text = downloader.DownloadInput(year, day);
2122
File.WriteAllText(inputFilePath, text);
2223
return text;
2324
}
2425
return File.ReadAllText(inputFilePath);
2526
}
2627

27-
public static IEnumerable<string> StreamFile(string inputFilePath)
28+
public static async Task<string> ReadFileAsync(int year, int day, string inputFilePath)
2829
{
29-
using var fs = new FileStream(inputFilePath, FileMode.Open, FileAccess.Read);
30-
using TextReader sr = new StreamReader(fs);
31-
32-
while (sr.ReadLine() is string line)
30+
if (!File.Exists(inputFilePath))
3331
{
34-
yield return line;
32+
string directory = Path.GetDirectoryName(inputFilePath)!;
33+
if (!Directory.Exists(directory))
34+
{
35+
Directory.CreateDirectory(directory);
36+
}
37+
38+
string? session = await AoCClient.GetSessionAsync();
39+
ArgumentNullException.ThrowIfNull(session);
40+
AoCClient downloader = new(session);
41+
string text = await downloader.DownloadInputAsync(year, day);
42+
await File.WriteAllTextAsync(inputFilePath, text);
43+
return text;
3544
}
36-
}
37-
38-
private static string ReadFile([CallerFilePath] string sourceFilePath = "")
39-
{
40-
string inputPath = sourceFilePath.Replace("Solution", "input").Replace(".cs", ".txt");
41-
string[] split = inputPath.Split(Path.DirectorySeparatorChar);
42-
int year = int.Parse(split.Single(s => s.StartsWith("Year")).Substring(4));
43-
int day = int.Parse(split.Single(s => s.StartsWith("Day")).Substring(3));
44-
45-
return ReadFile(year, day, inputPath);
46-
}
47-
48-
private static IEnumerable<string> ReadFileLinesEnumerable(string path)
49-
{
50-
return ReadFile(path).Replace("\r\n", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries);
51-
}
52-
53-
private static List<string> ReadFileLines([CallerFilePath] string path = null)
54-
{
55-
return ReadFileLinesEnumerable(path).ToList();
56-
}
57-
58-
private static string[] ReadFileLinesArray([CallerFilePath] string path = null)
59-
{
60-
return ReadFileLinesEnumerable(path).ToArray();
45+
return await File.ReadAllTextAsync(inputFilePath);
6146
}
6247
}

src/AdvantOfCode/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
if(args.Length > 1 && int.TryParse(args[1], out int yearArg)) year = yearArg;
88
string solutionName = $"Solution{day:D2}";
99
string yearName = $"Year{year}";
10-
Solution current = DayGenerator.GetByName(solutionName, yearName);
10+
SolutionAsync current = DayGenerator.GetAsyncByName(solutionName, yearName);
1111

1212
Console.WriteLine($"** AdventOfCode - {year} **");
1313
Console.WriteLine($"* {current.GetType().Name} *");
1414
long timestamp = Stopwatch.GetTimestamp();
15-
Console.WriteLine(current.Run());
15+
Console.WriteLine(await current.RunAsync());
1616
TimeSpan elapsedTime = Stopwatch.GetElapsedTime(timestamp);
1717
Console.WriteLine($"Runtime: {elapsedTime}");

src/AdvantOfCode/Solution.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@ namespace AdventOfCode;
44

55
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
66
// ReSharper disable once InconsistentNaming
7-
public interface Solution
7+
public interface Solution : SolutionAsync
88
{
99
string Run();
10+
11+
/// <summary>
12+
/// For backwards compatibility for old Solution types, make sure we can call the RunAsync method.
13+
/// Under the hood, it will call the Run method and return it as a Task.
14+
/// </summary>
15+
/// <returns></returns>
16+
Task<string> SolutionAsync.RunAsync() => Task.FromResult(Run());
17+
}
18+
19+
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
20+
// ReSharper disable once InconsistentNaming
21+
public interface SolutionAsync
22+
{
23+
Task<string> RunAsync();
1024
}

src/AdventOfCode.Client/AoCClient.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ public async Task<string> DownloadInputAsync(int year, int day)
5252

5353
return inputText;
5454
}
55+
56+
public string DownloadInput(int year, int day)
57+
{
58+
var cacheDirectory = GetCacheEventPath(year.ToString());
59+
string cachedInputPath = Path.Combine(cacheDirectory, $"input{day:D2}.txt");
60+
61+
string dayUrl = string.Format(UrlDay, year, day);
62+
string inputText = RequestOrCache(dayUrl + UrlInput, cachedInputPath, -1);
63+
64+
return inputText;
65+
}
5566

5667
/// <summary>
5768
/// Upload an answer and return the response.
@@ -139,6 +150,16 @@ public static string ParseHtml(ReadOnlySpan<char> html)
139150
return session;
140151
}
141152

153+
public static string? GetSession()
154+
{
155+
string? session = Environment.GetEnvironmentVariable("SESSION");
156+
if (session is null && File.Exists("SESSION"))
157+
{
158+
session = File.ReadAllText("SESSION");
159+
}
160+
161+
return session;
162+
}
142163

143164
/// <summary>
144165
/// Verify content of the session string.
@@ -189,6 +210,33 @@ private async Task<string> RequestOrCacheAsync(string url, string cacheFilePath,
189210
return text;
190211
}
191212

213+
private string RequestOrCache(string url, string cacheFilePath, int cacheExpiration = -1)
214+
{
215+
string text;
216+
FileInfo cacheFile = new FileInfo(cacheFilePath);
217+
DirectoryInfo? cacheFileDirectory = cacheFile.Directory;
218+
ArgumentNullException.ThrowIfNull(cacheFileDirectory, nameof(cacheFilePath));
219+
220+
if (!cacheFileDirectory.Exists)
221+
{
222+
cacheFileDirectory.Create();
223+
}
224+
225+
if(cacheFile.Exists && (cacheExpiration < 0 || (DateTime.Now - cacheFile.LastWriteTime).TotalSeconds < CacheTimeSeconds))
226+
{
227+
text = File.ReadAllText(cacheFilePath);
228+
}
229+
else
230+
{
231+
HttpRequestMessage request = new(HttpMethod.Get, url);
232+
HttpResponseMessage response = _client.Send(request);
233+
using var reader = new StreamReader(response.Content.ReadAsStream());
234+
text = reader.ReadToEnd();
235+
File.WriteAllText(cacheFilePath, text);
236+
}
237+
return text;
238+
}
239+
192240
private static string GetCacheEventPath(string eventName) =>
193241
Path.Combine(
194242
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),

test/AdventOfCode.Test/Solutions/Year2019DaySolutionsTest.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,25 @@ public Year2019DaySolutionsTest(ITestOutputHelper testOutputHelper) : base(2019,
1515
[Fact] public void Day06() => AssertDay("162439", "367");
1616
[Fact] public void Day07() => AssertDay("844468", "4215746");
1717
[Fact] public void Day08() => AssertDay("1935", """
18-
██ ████ █ █ █ █
19-
█ █ █ █ █ █ █
20-
█ ███ █ █ █ █
21-
█ █ █ █ █ █
22-
█ █ █ █ █ █ █
23-
██ █ ████ ██ ████
18+
██ ████ █ █ █ █
19+
█ █ █ █ █ █ █
20+
█ ███ █ █ █ █
21+
█ █ █ █ █ █
22+
█ █ █ █ █ █ █
23+
██ █ ████ ██ ████
2424
25-
""");
25+
""");
2626
[Fact] public void Day09() => AssertDay("2436480432", "45710");
2727
[Fact] public void Day10() => AssertDay("319", "517");
2828
[Fact] public void Day11() => AssertDay("2415", """
29-
███ ████ ███ █ █ ████ █ █ ███ ██
30-
█ █ █ █ █ █ █ █ █ █ █ █ █ █
31-
███ ███ █ █ █ █ █ █ █ █ █ █
32-
█ █ █ ███ █ █ █ █ █ ███ █
33-
█ █ █ █ █ █ █ █ █ █ █ █
34-
███ █ █ ██ ████ ██ █ ██
29+
███ ████ ███ █ █ ████ █ █ ███ ██
30+
█ █ █ █ █ █ █ █ █ █ █ █ █ █
31+
███ ███ █ █ █ █ █ █ █ █ █ █
32+
█ █ █ ███ █ █ █ █ █ ███ █
33+
█ █ █ █ █ █ █ █ █ █ █ █
34+
███ █ █ ██ ████ ██ █ ██
3535
36-
""");
36+
""");
3737

3838
[Fact] public void Day12() => AssertDay("12053", "320380285873116");
3939
[Fact] public void Day13() => AssertDay("239", "12099");

test/AdventOfCode.Test/Solutions/Year2021DaySolutionTest.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ public Year2021DaySolutionTest(ITestOutputHelper testOutputHelper) : base(2021,
2121
[Fact] public void Day11() => AssertDay("1675", "515");
2222
[Fact] public void Day12() => AssertDay("5254", "149385");
2323
[Fact] public void Day13() => AssertDay("684", "" +
24-
" ## ### #### ### # ## # # # # " + Environment.NewLine +
25-
" # # # # # # # # # # # # # " + Environment.NewLine +
26-
" # # # # ### # # ## #### " + Environment.NewLine +
27-
" # ### # # # # # ## # # # # " + Environment.NewLine +
28-
"# # # # # # # # # # # # # # " + Environment.NewLine +
29-
" ## # # #### ### #### ### # # # # " + Environment.NewLine);
24+
" ## ### #### ### # ## # # # # " + Environment.NewLine +
25+
" # # # # # # # # # # # # # " + Environment.NewLine +
26+
" # # # # ### # # ## #### " + Environment.NewLine +
27+
" # ### # # # # # ## # # # # " + Environment.NewLine +
28+
"# # # # # # # # # # # # # # " + Environment.NewLine +
29+
" ## # # #### ### #### ### # # # # " + Environment.NewLine);
3030
[Fact] public void Day14() => AssertDay("2345", "2432786807053");
3131
[Fact] public void Day15() => AssertDay("540", "2879");
3232
[Fact] public void Day16() => AssertDay("960", "12301926782560");

test/AdventOfCode.Test/Solutions/Year2022DaySolutionTest.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ public Year2022DaySolutionTest(ITestOutputHelper testOutputHelper) : base(2022,
1717
[Fact] public void Day08() => AssertDay("1792", "334880");
1818
[Fact] public void Day09() => AssertDay("6181", "2386");
1919
[Fact] public void Day10() => AssertDay("13440", """
20-
███ ███ ████ ██ ███ ██ ████ ██
21-
█ █ █ █ █ █ █ █ █ █ █ █ █ █
22-
█ █ ███ █ █ █ █ █ █ █ █ █
23-
███ █ █ █ █ ██ ███ ████ █ ████
24-
█ █ █ █ █ █ █ █ █ █ █ █ █
25-
█ ███ ████ ███ █ █ █ █ ████ █ █
26-
27-
""");
20+
███ ███ ████ ██ ███ ██ ████ ██
21+
█ █ █ █ █ █ █ █ █ █ █ █ █ █
22+
█ █ ███ █ █ █ █ █ █ █ █ █
23+
███ █ █ █ █ ██ ███ ████ █ ████
24+
█ █ █ █ █ █ █ █ █ █ █ █ █
25+
█ ███ ████ ███ █ █ █ █ ████ █ █
26+
27+
""");
2828
[Fact] public void Day11() => AssertDay("182293", "54832778815");
2929
[Fact] public void Day12() => AssertDay("380", "375");
3030
[Fact] public void Day13() => AssertDay("5806", "23600");

test/AdventOfCode.Test/Solutions/Year2023DaySolutionTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Threading.Tasks;
12
using Xunit;
23
using Xunit.Abstractions;
34

@@ -8,6 +9,7 @@ public class Year2023DaySolutionTest : YearTests
89
public Year2023DaySolutionTest(ITestOutputHelper testOutputHelper) : base(2023, testOutputHelper) {}
910

1011
[Fact] public void Day01() => AssertDay("55971", "54719");
12+
//[Fact] public Task Day01() => AssertDayAsync("55971", "54719");
1113
[Fact] public void Day02() => AssertDay("2416", "63307");
1214
[Fact] public void Day03() => AssertDay("550064", "85010461");
1315
[Fact] public void Day04() => AssertDay("28538", "9425061");

0 commit comments

Comments
 (0)