diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 3d3a0ba..14b9710 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,10 +3,11 @@
"isRoot": true,
"tools": {
"nuke.globaltool": {
- "version": "5.0.2",
+ "version": "8.0.0",
"commands": [
"nuke"
- ]
+ ],
+ "rollForward": false
}
}
}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index 1cd50c3..2f59d36 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,3 +5,85 @@ dotnet_analyzer_diagnostic.category-Globalization.severity = suggestion
# CA1031: Do not catch general exception types
dotnet_diagnostic.CA1031.severity = suggestion
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = file_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
diff --git a/.nuke b/.nuke
deleted file mode 100644
index 14ef02a..0000000
--- a/.nuke
+++ /dev/null
@@ -1 +0,0 @@
-Excursion360.Desktop.sln
\ No newline at end of file
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
new file mode 100644
index 0000000..4dba4dc
--- /dev/null
+++ b/.nuke/build.schema.json
@@ -0,0 +1,115 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$ref": "#/definitions/build",
+ "title": "Build Schema",
+ "definitions": {
+ "build": {
+ "type": "object",
+ "properties": {
+ "Configuration": {
+ "type": "string",
+ "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
+ "enum": [
+ "Debug",
+ "Release"
+ ]
+ },
+ "Continue": {
+ "type": "boolean",
+ "description": "Indicates to continue a previously failed build attempt"
+ },
+ "Help": {
+ "type": "boolean",
+ "description": "Shows the help text for this build assembly"
+ },
+ "Host": {
+ "type": "string",
+ "description": "Host for execution. Default is 'automatic'",
+ "enum": [
+ "AppVeyor",
+ "AzurePipelines",
+ "Bamboo",
+ "Bitbucket",
+ "Bitrise",
+ "GitHubActions",
+ "GitLab",
+ "Jenkins",
+ "Rider",
+ "SpaceAutomation",
+ "TeamCity",
+ "Terminal",
+ "TravisCI",
+ "VisualStudio",
+ "VSCode"
+ ]
+ },
+ "NoLogo": {
+ "type": "boolean",
+ "description": "Disables displaying the NUKE logo"
+ },
+ "Partition": {
+ "type": "string",
+ "description": "Partition to use on CI"
+ },
+ "Plan": {
+ "type": "boolean",
+ "description": "Shows the execution plan (HTML)"
+ },
+ "Profile": {
+ "type": "array",
+ "description": "Defines the profiles to load",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Root": {
+ "type": "string",
+ "description": "Root directory during build execution"
+ },
+ "Runtime": {
+ "type": "string"
+ },
+ "Skip": {
+ "type": "array",
+ "description": "List of targets to be skipped. Empty list skips all dependencies",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Clean",
+ "Compile",
+ "Publish",
+ "Restore"
+ ]
+ }
+ },
+ "Solution": {
+ "type": "string",
+ "description": "Path to a solution file that is automatically loaded"
+ },
+ "Target": {
+ "type": "array",
+ "description": "List of targets to be invoked. Default is '{default_target}'",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Clean",
+ "Compile",
+ "Publish",
+ "Restore"
+ ]
+ }
+ },
+ "Verbosity": {
+ "type": "string",
+ "description": "Logging verbosity during build execution. Default is 'Normal'",
+ "enum": [
+ "Minimal",
+ "Normal",
+ "Quiet",
+ "Verbose"
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/.nuke/parameters.json b/.nuke/parameters.json
new file mode 100644
index 0000000..3e643f8
--- /dev/null
+++ b/.nuke/parameters.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "./build.schema.json",
+ "Solution": "Excursion360.Desktop.sln"
+}
diff --git a/Excursion360.Desktop/ConsoleHelper.cs b/Excursion360.Desktop/ConsoleHelper.cs
index 252bb23..1d47715 100644
--- a/Excursion360.Desktop/ConsoleHelper.cs
+++ b/Excursion360.Desktop/ConsoleHelper.cs
@@ -1,42 +1,36 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
+namespace Excursion360.Desktop;
-namespace Excursion360.Desktop
+public static class ConsoleHelper
{
- public static class ConsoleHelper
+ public static int SelectOneFromArray(string headerLine, string[] values)
{
- public static int SelectOneFromArray(string headerLine, string[] values)
+ values = values ?? throw new ArgumentNullException(nameof(values));
+ var index = 0;
+ while (true)
{
- values = values ?? throw new ArgumentNullException(nameof(values));
- var index = 0;
- while (true)
+ Console.Clear();
+ Console.WriteLine(headerLine);
+ for (int i = 0; i < values.Length; i++)
{
- Console.Clear();
- Console.WriteLine(headerLine);
- for (int i = 0; i < values.Length; i++)
- {
- Console.BackgroundColor = index == i ? ConsoleColor.White : ConsoleColor.Black;
- Console.ForegroundColor = index == i ? ConsoleColor.Black : ConsoleColor.White;
- Console.WriteLine(values[i]);
- }
- Console.BackgroundColor = ConsoleColor.Black;
- Console.ForegroundColor = ConsoleColor.White;
- var key = Console.ReadKey();
- switch (key.Key)
- {
- case ConsoleKey.UpArrow:
- index = index <= 0 ? 0 : index - 1;
- break;
- case ConsoleKey.DownArrow:
- index = index + 1 >= values.Length ? values.Length - 1 : index + 1;
- break;
- case ConsoleKey.Enter:
- return index;
- default:
- break;
- }
+ Console.BackgroundColor = index == i ? ConsoleColor.White : ConsoleColor.Black;
+ Console.ForegroundColor = index == i ? ConsoleColor.Black : ConsoleColor.White;
+ Console.WriteLine(values[i]);
+ }
+ Console.BackgroundColor = ConsoleColor.Black;
+ Console.ForegroundColor = ConsoleColor.White;
+ var key = Console.ReadKey();
+ switch (key.Key)
+ {
+ case ConsoleKey.UpArrow:
+ index = index <= 0 ? 0 : index - 1;
+ break;
+ case ConsoleKey.DownArrow:
+ index = index + 1 >= values.Length ? values.Length - 1 : index + 1;
+ break;
+ case ConsoleKey.Enter:
+ return index;
+ default:
+ break;
}
}
}
diff --git a/Excursion360.Desktop/Excursion360.Desktop.csproj b/Excursion360.Desktop/Excursion360.Desktop.csproj
index 97b7e44..ea70768 100644
--- a/Excursion360.Desktop/Excursion360.Desktop.csproj
+++ b/Excursion360.Desktop/Excursion360.Desktop.csproj
@@ -2,8 +2,10 @@
Exe
- netcoreapp3.1
+ net8.0-windows
true
+ enable
+ enable
@@ -20,13 +22,13 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
+
diff --git a/Excursion360.Desktop/Extensions.cs b/Excursion360.Desktop/Extensions.cs
index 695d548..6c8a0e3 100644
--- a/Excursion360.Desktop/Extensions.cs
+++ b/Excursion360.Desktop/Extensions.cs
@@ -1,41 +1,23 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.Extensions.Hosting;
-using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
-using System.Linq;
+using Microsoft.AspNetCore.Http.Features;
-namespace Excursion360.Desktop
+namespace Excursion360.Desktop;
+
+static class Extensions
{
- static class Extensions
+ public static ILogger CreateLogger(this IWebHost host, string categoryName)
+ => host.Services.GetRequiredService().CreateLogger(categoryName);
+ public static Uri GetListeningUri(this IHost host)
{
- public static ILogger CreateLogger(this IWebHost host, string categoryName)
- {
- return host.Services.GetService().CreateLogger(categoryName);
- }
- public static Uri GetListeningUri(this IHost host)
- {
- return new Uri(host.Services
- .GetRequiredService()
- .Features
- .Get()
- .Addresses
- .Single(a => a.StartsWith("http:", StringComparison.Ordinal)));
- }
- public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector)
- {
- HashSet seenKeys = new HashSet();
- foreach (TSource element in source)
- {
- if (seenKeys.Add(keySelector(element)))
- {
- yield return element;
- }
- }
- }
+ return new Uri(host.Services
+ .GetRequiredService()
+ .Features
+ .GetRequiredFeature()
+ .Addresses
+ .Single(a => a.StartsWith("http:", StringComparison.Ordinal)));
}
+
+ public static string ExcursionDirectoryPath(this IConfiguration configuration)
+ => configuration.GetValue("excursionsPath", null) ?? Directory.GetCurrentDirectory();
}
diff --git a/Excursion360.Desktop/Program.cs b/Excursion360.Desktop/Program.cs
index 4681511..54bc4f7 100644
--- a/Excursion360.Desktop/Program.cs
+++ b/Excursion360.Desktop/Program.cs
@@ -1,175 +1,114 @@
-using Microsoft.AspNetCore;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using Microsoft.Extensions.Configuration;
-using Microsoft.AspNetCore.Hosting.Server.Features;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.Win32;
-using System.IO;
-using System.Diagnostics;
-using System.Threading;
-using System.Net.Http;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
using Excursion360.Desktop.Services.Firefox;
-using Microsoft.Extensions.Hosting;
-using Microsoft.AspNetCore.Hosting.Server;
-using System.Net.NetworkInformation;
using Microsoft.Extensions.FileProviders;
using Excursion360.Desktop.Services;
using System.Reflection;
using Excursion360.Desktop.Exceptions;
using MintPlayer.PlatformBrowser;
-using System.Collections.Generic;
+using Excursion360.Desktop;
-namespace Excursion360.Desktop
+Console.BackgroundColor = ConsoleColor.Black;
+Console.ForegroundColor = ConsoleColor.White;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Logging.SetMinimumLevel(LogLevel.Information);
+builder.Services.AddSingleton();
+builder.Services.AddResponseCompression();
+if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+{
+ builder.Services.AddHttpClient();
+}
+else
{
- class Program
+ builder.Services.AddHttpClient();
+}
+
+var app = builder.Build();
+var targetDirectory = GetExcursionDirectory(builder.Configuration.ExcursionDirectoryPath());
+
+app.MapGet("/eapi/preload.json", (IStateImagesMetricsStore stateImagesMetrics) => {
+ return new
{
- static async Task Main(string[] args)
- {
- Console.BackgroundColor = ConsoleColor.Black;
- Console.ForegroundColor = ConsoleColor.White;
-
-
- try
- {
- var targetDirectory = GetExcursionDirectory();
- using IHost host = CreateHost(args, targetDirectory);
- IBrowser browser = await SelectBrowser(args, targetDirectory, host).ConfigureAwait(false);
- var hostTask = host.RunAsync().ConfigureAwait(false);
- await browser.StartBrowser(host.GetListeningUri());
- await hostTask;
- }
- catch (IncorrectEnvironmentException ex)
- {
- Console.WriteLine(ex.Message);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Unexpected error, please tell us about that https://github.com/RTUITLab/Excursion360-Desktop/issues");
- Console.WriteLine(ex.Message);
- Console.WriteLine(ex.StackTrace);
- }
- Console.WriteLine("Press any key to exit");
- Console.ReadKey();
- }
+ images = stateImagesMetrics.MostPopularUrls(),
+ };
+});
- private static async Task SelectBrowser(string[] args, string targetDirectory, IHost host)
- {
- bool useFirefox = SelectFirefoxOrInstalled(args, targetDirectory, out var selectedBrowser);
- return useFirefox ?
- await SetupFirefox(host).ConfigureAwait(false)
- :
- new GenericBrowser(selectedBrowser);
- }
+var fso = new FileServerOptions
+{
+ FileProvider = new CompositeFileProvider(
+ new PhysicalFileProvider(Path.Combine(builder.Configuration.ExcursionDirectoryPath(), targetDirectory)),
+ new EmbeddedFileProvider(Assembly.GetExecutingAssembly()))
+};
+fso.DefaultFilesOptions.DefaultFileNames.Add("Resources/NotFound.html");
+fso.StaticFileOptions.OnPrepareResponse = (context) =>
+{
+ if (context.File.Name.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) && context.File.PhysicalPath is not null)
+ {
+ var stateImagesMetricsStore = context.Context.RequestServices.GetRequiredService();
+ stateImagesMetricsStore.IncrementImageHit(context.Context.Request.Path);
+ }
+};
+app.UseResponseCompression();
+app.UseFileServer(fso);
- private static string GetExcursionDirectory()
- {
- var dirs = Directory.GetDirectories(Directory.GetCurrentDirectory()).Select(d => Path.GetFileName(d)).ToArray();
- if (dirs.Length == 0)
- {
- throw new IncorrectEnvironmentException("You must place excursion files to the subdirectory with executable file");
- }
- if (dirs.Length == 1)
- {
- return dirs[0];
- }
- return dirs[ConsoleHelper.SelectOneFromArray("Select directory with excursion", dirs)];
- }
- private static async Task SetupFirefox(IHost host)
- {
- var firefoxInterop = host.Services.GetRequiredService();
- if (!await firefoxInterop.IsFirefoxInstalled())
- {
- if (!await firefoxInterop.TryInstallFirefoxAsync().ConfigureAwait(false))
- {
- throw new IncorrectEnvironmentException("Can't install firefox");
- }
- }
- return firefoxInterop;
- }
+IBrowser browser = await SelectBrowser(targetDirectory, app.Services, app.Configuration).ConfigureAwait(false);
+
+var hostTask = app.RunAsync();
+await browser.StartBrowser(app.GetListeningUri());
+await hostTask;
- ///
- ///
- ///
- ///
- ///
- /// true if need firefox, false if selected another browser
- private static bool SelectFirefoxOrInstalled(string[] args, string targetDirectory, out Browser browser)
- {
- browser = null;
- if (args.Contains("--firefox"))
- {
- return true;
- }
- var browsers = PlatformBrowser
- .GetInstalledBrowsers()
- .Where(b => !b.Name.Contains("Explorer"))
- .Where(b => !b.Name.Contains("Firefox"))
- .DistinctBy(b => b.Name)
- .ToArray();
- var browsersList = new List { "Use Firefox (install if not present)" };
- browsersList.AddRange(browsers.Select(b => b.Name));
-
- var browserMode = ConsoleHelper.SelectOneFromArray($"Select run option. Selected excursion: {targetDirectory}", browsersList.ToArray());
- Console.Clear();
- if (browserMode == 0) // Use furefox
- {
- return true;
- }
- else
- {
- browser = browsers[browserMode - 1];
- return false;
- }
- }
- private static IHost CreateHost(string[] args, string targetDirectory)
+async Task SelectBrowser(string targetDirectory, IServiceProvider serviceProvider, IConfiguration configuration)
+{
+ if (configuration.GetValue("--firefox"))
+ {
+ return await SetupFirefox(serviceProvider).ConfigureAwait(false);
+ }
+ var selectedBrowser = await SelectInstalledBrowserAsync(targetDirectory);
+ return new GenericBrowser(selectedBrowser);
+}
+
+string GetExcursionDirectory(string rootDir)
+{
+ var dirs = Directory.GetDirectories(rootDir).Select(d => Path.GetFileName(d)).ToArray();
+ if (dirs.Length == 0)
+ {
+ throw new IncorrectEnvironmentException("You must place excursion files to the subdirectory with executable file");
+ }
+ if (dirs.Length == 1)
+ {
+ return dirs[0];
+ }
+ return dirs[ConsoleHelper.SelectOneFromArray("Select directory with excursion", dirs)];
+}
+
+async Task SetupFirefox(IServiceProvider serviceProvider)
+{
+ var firefoxInterop = serviceProvider.GetRequiredService();
+ if (!await firefoxInterop.IsFirefoxInstalled())
+ {
+ if (!await firefoxInterop.TryInstallFirefoxAsync().ConfigureAwait(false))
{
- var host = Host
- .CreateDefaultBuilder(args)
- .ConfigureLogging(logs =>
- {
- logs.SetMinimumLevel(LogLevel.Information);
- })
- .ConfigureWebHostDefaults(webBuilder =>
- {
- var fso = new FileServerOptions
- {
- FileProvider = new CompositeFileProvider(
- new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), targetDirectory)),
- new EmbeddedFileProvider(Assembly.GetExecutingAssembly()))
- };
- fso.DefaultFilesOptions.DefaultFileNames.Add("Resources/NotFound.html");
- fso.StaticFileOptions.OnPrepareResponse = (context) =>
- {
- context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
- context.Context.Response.Headers.Add("Expires", "-1");
- };
- webBuilder.Configure(app =>
- {
- app.UseFileServer(fso);
- });
- })
- .ConfigureServices(services =>
- {
- services.AddHttpClient(nameof(IFirefoxInterop));
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- services.AddSingleton();
- }// TODO Add other OS
- else
- {
- services.AddSingleton();
- }
- })
- .Build();
- return host;
+ throw new IncorrectEnvironmentException("Can't install firefox");
}
}
+ return firefoxInterop;
+}
+
+
+async Task SelectInstalledBrowserAsync(string targetDirectory)
+{
+ var browsers = (await PlatformBrowser
+ .GetInstalledBrowsers())
+ .Where(b => !b.Name.Contains("Explorer"))
+ .DistinctBy(b => b.Name)
+ .ToArray();
+
+ var browsersList = new List { "Use Firefox (install if not present)" };
+ browsersList.AddRange(browsers.Select(b => b.Name));
+
+ var browserMode = ConsoleHelper.SelectOneFromArray($"Select run option. Selected excursion: {targetDirectory}", [.. browsersList]);
+ Console.Clear();
+ return browsers[browserMode - 1];
}
diff --git a/Excursion360.Desktop/Services/Firefox/IFirefoxInterop.cs b/Excursion360.Desktop/Services/Firefox/IFirefoxInterop.cs
index 7c2a491..53dae6c 100644
--- a/Excursion360.Desktop/Services/Firefox/IFirefoxInterop.cs
+++ b/Excursion360.Desktop/Services/Firefox/IFirefoxInterop.cs
@@ -1,13 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
+namespace Excursion360.Desktop.Services.Firefox;
-namespace Excursion360.Desktop.Services.Firefox
+public interface IFirefoxInterop : IBrowser
{
- public interface IFirefoxInterop : IBrowser
- {
- Task TryInstallFirefoxAsync();
- ValueTask IsFirefoxInstalled();
- }
+ Task TryInstallFirefoxAsync();
+ ValueTask IsFirefoxInstalled();
}
diff --git a/Excursion360.Desktop/Services/Firefox/UnsupportedOsFirefoxInterop.cs b/Excursion360.Desktop/Services/Firefox/UnsupportedOsFirefoxInterop.cs
index 459b999..01888c5 100644
--- a/Excursion360.Desktop/Services/Firefox/UnsupportedOsFirefoxInterop.cs
+++ b/Excursion360.Desktop/Services/Firefox/UnsupportedOsFirefoxInterop.cs
@@ -1,24 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
-namespace Excursion360.Desktop.Services.Firefox
+namespace Excursion360.Desktop.Services.Firefox;
+
+public class UnsupportedOsFirefoxInterop : IFirefoxInterop
{
- public class UnsupportedOsFirefoxInterop : IFirefoxInterop
+ public ValueTask IsFirefoxInstalled()
{
- public ValueTask IsFirefoxInstalled()
- {
- throw new NotSupportedException();
- }
+ throw new NotSupportedException();
+ }
- public ValueTask StartBrowser(Uri uri)
- {
- throw new NotSupportedException();
- }
+ public ValueTask StartBrowser(Uri uri)
+ {
+ throw new NotSupportedException();
+ }
- public Task TryInstallFirefoxAsync()
- {
- throw new NotSupportedException();
- }
+ public Task TryInstallFirefoxAsync()
+ {
+ throw new NotSupportedException();
}
}
diff --git a/Excursion360.Desktop/Services/Firefox/WindowsFirefoxInterop.cs b/Excursion360.Desktop/Services/Firefox/WindowsFirefoxInterop.cs
index 96c2bfe..7c6057b 100644
--- a/Excursion360.Desktop/Services/Firefox/WindowsFirefoxInterop.cs
+++ b/Excursion360.Desktop/Services/Firefox/WindowsFirefoxInterop.cs
@@ -1,31 +1,14 @@
-using Microsoft.Extensions.Logging;
-using Microsoft.Win32;
-using System;
+using Microsoft.Win32;
using System.Buffers;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
namespace Excursion360.Desktop.Services.Firefox
{
- public class WindowsFirefoxInterop : IFirefoxInterop
+ public class WindowsFirefoxInterop(HttpClient httpClient, ILogger logger) : IFirefoxInterop
{
private const string FirefosinstallerUri = "https://download-installer.cdn.mozilla.net/pub/firefox/releases/71.0/win64/ru/Firefox%20Setup%2071.0.msi";
private const string RegistryFirefoxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe";
- private readonly ILogger logger;
- private readonly HttpClient httpClient;
- public WindowsFirefoxInterop(
- ILogger logger,
- IHttpClientFactory httpClientFactory)
- {
- this.logger = logger;
- httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
- httpClient = httpClientFactory.CreateClient(nameof(IFirefoxInterop));
- }
public async Task TryInstallFirefoxAsync()
{
logger.LogInformation("Installing firefox...");
@@ -33,11 +16,11 @@ public async Task TryInstallFirefoxAsync()
string installerFilePath = Path.Combine(Directory.GetCurrentDirectory() + @"\firefox.msi");
Uri downloadUri = new Uri(FirefosinstallerUri);
- using (HttpResponseMessage response = httpClient.GetAsync(downloadUri, HttpCompletionOption.ResponseHeadersRead).Result)
+ using (var response = await httpClient.GetAsync(downloadUri, HttpCompletionOption.ResponseHeadersRead))
{
if (!response.IsSuccessStatusCode)
{
- logger.LogInformation($"The request returned with HTTP status code {response.StatusCode}");
+ logger.LogInformation("The request returned with HTTP status code {ResponseStatusCode}", response.StatusCode);
return false;
}
@@ -50,14 +33,14 @@ public async Task TryInstallFirefoxAsync()
do
{
- var read = await contentStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+ var read = await contentStream.ReadAsync(buffer).ConfigureAwait(false);
if (read == 0)
{
isMoreToRead = false;
}
else
{
- await fileStream.WriteAsync(buffer, 0, read).ConfigureAwait(false);
+ await fileStream.WriteAsync(buffer.AsMemory(0, read)).ConfigureAwait(false);
totalRead += read;
totalReads += 1;
@@ -68,7 +51,7 @@ public async Task TryInstallFirefoxAsync()
ArrayPool.Shared.Return(buffer);
}
- using Process installerProcess = new Process
+ using var installerProcess = new Process
{
StartInfo = new ProcessStartInfo("cmd.exe")
{
@@ -97,7 +80,7 @@ public ValueTask IsFirefoxInstalled()
{
logger.LogInformation("Checking for firefox...");
- object path = Registry.GetValue(RegistryFirefoxKey, "", null);
+ object? path = Registry.GetValue(RegistryFirefoxKey, "", null);
if (path != null) { logger.LogInformation("Firefox installed"); }
return new ValueTask(path != null);
@@ -106,9 +89,10 @@ public ValueTask IsFirefoxInstalled()
public ValueTask StartBrowser(Uri uri)
{
uri = uri ?? throw new ArgumentNullException(nameof(uri));
- logger.LogInformation($"Starting firefox on {uri.AbsoluteUri}");
-
- Process.Start(Registry.GetValue(RegistryFirefoxKey, "", null).ToString(), uri.AbsoluteUri);
+ logger.LogInformation("Starting firefox on {StartUrl}", uri.AbsoluteUri);
+ var firefoxPath = Registry.GetValue(RegistryFirefoxKey, "", null)?.ToString()
+ ?? throw new InvalidDataException($"Can't run firefox, not found registry key value {RegistryFirefoxKey}");
+ Process.Start(firefoxPath, uri.AbsoluteUri);
return new ValueTask();
}
}
diff --git a/Excursion360.Desktop/Services/GenericBrowser.cs b/Excursion360.Desktop/Services/GenericBrowser.cs
index 6dedbd9..9608af2 100644
--- a/Excursion360.Desktop/Services/GenericBrowser.cs
+++ b/Excursion360.Desktop/Services/GenericBrowser.cs
@@ -1,26 +1,14 @@
using MintPlayer.PlatformBrowser;
-using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-namespace Excursion360.Desktop.Services
+namespace Excursion360.Desktop.Services;
+
+public class GenericBrowser(Browser browser) : IBrowser
{
- public class GenericBrowser : IBrowser
+ public ValueTask StartBrowser(Uri uri)
{
- private readonly Browser browser;
-
- public GenericBrowser(Browser browser)
- {
- this.browser = browser;
- }
- public ValueTask StartBrowser(Uri uri)
- {
- uri = uri ?? throw new ArgumentNullException(nameof(uri));
- Process.Start(browser.ExecutablePath, uri.AbsoluteUri);
- return new ValueTask();
- }
+ uri = uri ?? throw new ArgumentNullException(nameof(uri));
+ Process.Start(browser.ExecutablePath, uri.AbsoluteUri);
+ return new ValueTask();
}
}
diff --git a/Excursion360.Desktop/Services/IBrowser.cs b/Excursion360.Desktop/Services/IBrowser.cs
index 3567271..9e983aa 100644
--- a/Excursion360.Desktop/Services/IBrowser.cs
+++ b/Excursion360.Desktop/Services/IBrowser.cs
@@ -1,10 +1,6 @@
-using System;
-using System.Threading.Tasks;
+namespace Excursion360.Desktop.Services;
-namespace Excursion360.Desktop.Services
+public interface IBrowser
{
- public interface IBrowser
- {
- ValueTask StartBrowser(Uri uri);
- }
+ ValueTask StartBrowser(Uri uri);
}
\ No newline at end of file
diff --git a/Excursion360.Desktop/Services/IStateImagesMetricsStore.cs b/Excursion360.Desktop/Services/IStateImagesMetricsStore.cs
new file mode 100644
index 0000000..ea6a1c6
--- /dev/null
+++ b/Excursion360.Desktop/Services/IStateImagesMetricsStore.cs
@@ -0,0 +1,47 @@
+using System.Collections.Concurrent;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+
+namespace Excursion360.Desktop.Services;
+
+public interface IStateImagesMetricsStore
+{
+ void IncrementImageHit(string path);
+ string[] MostPopularUrls();
+}
+
+public partial class InMemoryIStateImagesMetricsStore : IStateImagesMetricsStore
+{
+ ///
+ /// Количества получений картинок по адресу. ключ = адрес картинки. Значение - количество получений.
+ ///
+ private readonly ConcurrentDictionary
+ hitCounts = new();
+ private Regex stateAndImageRegex = GetStateAndImageRegex();
+ public void IncrementImageHit(string path)
+ {
+ var match = stateAndImageRegex.Match(path);
+ if (!match.Success)
+ {
+ return;
+ }
+ hitCounts.AddOrUpdate(path, 1, (_, old) => old + 1);
+ Console.WriteLine(JsonSerializer.Serialize(hitCounts, new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ }));
+ }
+ public string[] MostPopularUrls()
+ {
+ return hitCounts
+ .OrderByDescending(kvp => kvp.Value)
+ .ThenBy(kvp => kvp.Key)
+ .Select(kvp => kvp.Key)
+ .Take(10)
+ .ToArray();
+ }
+
+ [GeneratedRegex(@"state_.+[/\\].+\.jpg$")]
+ private static partial Regex GetStateAndImageRegex();
+
+}
diff --git a/README.md b/README.md
index 08d9022..e543713 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Desktop viewer for 360 excursions
## CLI Options
-Run executable file with `--firefox` to force using firefox instead selecting on start.
+Run executable file with `--firefox true` to force using firefox instead selecting on start.
## Build
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 24e213d..f7b1433 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1 +1,8 @@
-# Возможность выбирать используемый браузер из установленных на ПК
+- `v5.0`
+ - Добавлено сжатие ресурсов при отправке клиенту
+- `v4.0`
+ - Обновлен .NET/Nuke до 8, убраны все предупреждения
+ - Обновлена структура проекта до последней версии ASP.NET Core
+ - Базовая папка для выбора экскурсий может выбираться через `--excursionsPath путь_к_папке`
+ - Добавлена обработка `/eapi/preload.json`, по которому будут отдаваться адреса изображений, которые чаще всего просматриваются
+- `v3.0` Возможность выбирать используемый браузер из установленных на ПК
diff --git a/build.cmd b/build.cmd
old mode 100644
new mode 100755
index 8b8b89d..b08cc59
--- a/build.cmd
+++ b/build.cmd
@@ -4,4 +4,4 @@
:; exit $?
@ECHO OFF
-powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %*
+powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
diff --git a/build.ps1 b/build.ps1
index e5c8a44..4634dc0 100644
--- a/build.ps1
+++ b/build.ps1
@@ -14,15 +14,14 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
-$TempDirectory = "$PSScriptRoot\\.tmp"
+$TempDirectory = "$PSScriptRoot\\.nuke\temp"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
-$DotNetChannel = "Current"
+$DotNetChannel = "STS"
-$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
-$env:DOTNET_MULTILEVEL_LOOKUP = 0
+$env:DOTNET_NOLOGO = 1
###########################################################################
# EXECUTION
@@ -56,14 +55,20 @@ else {
# Install by channel or version
$DotNetDirectory = "$TempDirectory\dotnet-win"
if (!(Test-Path variable:DotNetVersion)) {
- ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
+ ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
- ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
+ ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
+ $env:PATH = "$DotNetDirectory;$env:PATH"
}
-Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
+Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
+
+if (Test-Path env:NUKE_ENTERPRISE_TOKEN) {
+ & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null
+ & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null
+}
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
diff --git a/build.sh b/build.sh
old mode 100644
new mode 100755
index 3d52643..fdff0c6
--- a/build.sh
+++ b/build.sh
@@ -10,15 +10,14 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
-TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
+TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
-DOTNET_CHANNEL="Current"
+DOTNET_CHANNEL="STS"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
-export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
-export DOTNET_MULTILEVEL_LOOKUP=0
+export DOTNET_NOLOGO=1
###########################################################################
# EXECUTION
@@ -54,9 +53,15 @@ else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
+ export PATH="$DOTNET_DIRECTORY:$PATH"
fi
-echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
+echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
+
+if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then
+ "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true
+ "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true
+fi
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
diff --git a/build/Build.cs b/build/Build.cs
index 7ab99d2..d11e367 100644
--- a/build/Build.cs
+++ b/build/Build.cs
@@ -15,7 +15,6 @@
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Logger;
-[CheckBuildProjectConfigurations]
[ShutdownDotNetAfterServerBuild]
class Build : NukeBuild
{
@@ -41,7 +40,7 @@ class Build : NukeBuild
.Before(Restore)
.Executes(() =>
{
- EnsureCleanDirectory(OutputDirectory);
+ OutputDirectory.CreateOrCleanDirectory();
});
Target Restore => _ => _
@@ -77,6 +76,7 @@ class Build : NukeBuild
.EnablePublishTrimmed()
.SetProperty("DebugType", "None")
.SetProperty("DebugSymbols", false)
+ .SetProperty("PublishIISAssets", false)
.EnableNoRestore());
var executableFile = System.IO.Directory.GetFiles(OutputDirectory)
diff --git a/build/_build.csproj b/build/_build.csproj
index 07fcbe0..5331a66 100644
--- a/build/_build.csproj
+++ b/build/_build.csproj
@@ -2,15 +2,16 @@
Exe
- netcoreapp3.1
+ net8.0
CS0649;CS0169
..
..
+ 1
-
+
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..26d18ee
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "8.0.0",
+ "rollForward": "minor"
+ }
+}