ArcGIS Runtime: How to identify the topmost feature across all feature layers?

From ArcGIS Runtime Java API documentation, there is an&nbsp;<a href="https://developers.arcgis.com/java/latest/api-reference/reference/com/esri/arcgisruntime/mapping/view/GeoView.html#identifyLayersAsync(javafx.geometry.Point2D,%20double,%20boolean)" target="_blank">identifyLayersAsync() method</a>.

From ArcGIS Runtime Java API documentation, there is an identifyLayersAsync() method.

From documentation:

Asynchronously identifies the topmost visible geo-element in each identifiable layer attached to the map, near given screen point.

Is there a way to for ArcGIS Runtime to return a single geo-element belonging to any layer, such that it is the topmost element?

P.S. Also asked on gis.stackexchange.

Edit

I'm still having this problem. To rephrase it, I need to find out the feature (that belongs to any layer within a list of layers) that the user has clicked on. The feature returned must be feature that the user thought he/she has clicked on (based on visual judgement).

Additionally, the method must work for both maps and scenes.

I'm going to offer bounty on both sites. At this point I'm quite sure that the API doesn't do this out-of-the-box, so I'm looking for work-around solutions.

WPF and WinForms Will Run on .NET Core 3

WPF and WinForms Will Run on .NET Core 3

If you haven't had a chance to explore the new capabilities of .NET Core 3, read along with an expert in the framework to discover what it brings to the table.


Maybe you already heard or read about the fact that Microsoft brought WinForms and WPF to .NET Core 3.0. Maybe you already saw the presentations on the Connect conference, or any other conference or recording when Scott Hanselman shows how to run a pretty old WPF application on .NET Core. I saw a demo where he ran BabySmash on .NET Core.

BTW: My oldest son really loved that BabySmash when he was a baby.
WPF and WinForms on .NET Core?

I was really wondering about this step, even because I wrote an article for a German .NET magazine some months before where I mentioned that Microsoft won’t build a UI Stack for .NET Core. There were some other UI stacks built by the community. The most popular is Avalonia.

But this step makes sense anyway. Since the .NET Standards move the API of .NET Core more to the same level of the .NET framework, making the APIs almost equal was simply a question of time. WPF and WinForms are based on .NET Libraries, so it should run on .NET Core.


Does This Mean it Runs on Linux and Mac?

Nope! Since WinForms and WPF uses Windows only technology in the background, it cannot run on Linux or Mac. It is really dependent on Windows. The sense of running it on .NET Core is performance and to be independent of any framework. .NET Core is optimized for performance so you can run super fast web applications in the cloud. .NET Core is also independent of the installed framework on the machine. Just deploy the runtime together with your application.

You are now able to run fast and self-contained Windows desktop applications. That’s awesome, isn’t it!?


The .NET CLI

Every time I install a new version of the .NET Core runtime I try dotnet new and I was positively shocked about what I saw this time:

You are now able to create a Windows Form or a WPF application using the .NET CLI. This is cool. And I for sure needed to try it out:

dotnet new -n WpfTest -o WpfTest dotnet new -n WpfTest -o WpfTest 

And yes, it is working as you can see here in Visual Studio Code:

And this is the WinForms project in VS Code

Running dotnet run on the WPF project:

And again on the WinForms GUI:


IDE

Visual Studio Code isn’t the right editor for this kind of project. If you know XAML pretty well, it will work, but WinForms definitely won’t work well. You need to write the designer code manually and there isn’t any designer support yet. Maybe there will be some in the future, but I’m not sure.

The best choice to work with WinForms and WPF on .NET Core is Visual Studio 2017 or newer.


Last Words

I don’t think I will now start to write desktop apps on .NET Core 3, because I’m a web guy. But it is a really nice option to build apps like this on .NET Core.

BTW: Even EF 6 will work in .NET Core 3, which means you also don’t need to rewrite the database access part of your desktop application.

As I wrote, you can now use this super fast framework and the option to create self-contained apps. I would suggest you try it out and play around with it. Do you have an older desktop application based on WPF or WinForms? I would be curious about whether you can run it on .NET Core 3. Tell me how easy it was to get it running on .NET Core 3.


Learn More

The Complete ASP.NET MVC 5 Course

Build a Real-world App with ASP.NET Core and Angular 2 (4+)

ASP NET Core (ASP.NET 5),MVC 6,C#,Angular2 & EF Crash Course

Rest Api’s in Asp.Net and C#

Hands on ASP .Net Core 2

Originally published by Juergen Gutsch at https://dzone.com

.NET or Java for Web Development

.NET or Java for Web Development

Developers are often in a dilemma when trying to choose between technologies for their projects. Comparing two or more is quite daunting and confusing at times. In this article, we will focus on comparing .NET and Java.

Thinking of adopting one of these technologies for your next web development project? Read on for a basic overview.

Developers are often in a dilemma when trying to choose between technologies for their projects. Comparing two or more is quite daunting and confusing at times. In this article, we will focus on comparing .NET and Java.

The two are quite different in structure but are predisposed to comparison because of their demand when there is a call for complex and large-scale applications.

While .NET is a framework that can use several languages, Java, on the other hand, is a programming language itself.

A debate is often sparked during comparison among business owners and developers when it comes to choosing between the two. In fact, it is common for people to switch between the two periodically.

.NET, under the auspices of its framework, encompasses C#, which is basically Java, and here lies the challenge in contrasting between the better of the two. This article describes how the two are different while showing their similarity.

What Is Common to Both Java and .NET?

They Are Developed for Heavy Applications

Heavy execution, complex architecture, systems with high loads, and applications with big data specifications are some of the things that can be executed by both Java and C# in the .NET framework. This accords them the high adoption rates in technologies in the enterprise level which proves their scalability and reliability.

They Are Multipurpose

Ranging from subtle to overt, both Java and the .NET framework can be run on desktops, mobile applications, and servers. They are both widely used in the enterprise market. Both Java and C# are 'write one run anywhere' type languages.

They Are Similar in Their Syntax

C# in the .NET framework is derived from the Java language. This proves that the syntaxes of Java and C# are the same except for some basic modifications that were made. A basic syntax is common between the two since Java uses syntax from C++ and C# uses a variety of C languages and style including C, C++, and Java.

Both Are Object-Oriented

A common standard, object-oriented programming, is a basic principle of software development. Having their structure in modules, both Java and .NET are flexible and allow for code reuse while having good troubleshooting expansions.

Garbage Collection

Common to low-level languages, recalling is necessary if you have to free up space during memory administration which makes it a repetitive process. However, in .NET and Java there are no memory spills since objects that are not in use are evacuated.

Single Inheritance

Both C# and Java reinforce single inheritance. This means that a single path is always available from a base class to an inferred class.

Interfaces

Whenever methods are unique in an interface for a dynamic class, it defines an interface. A dynamic method on the other and is one that does not encompass its usage point of interest. Any interface characterization, in terms of the property, has code overseeing it, which is provided for actualization based on a specified class for its actualization.

Some of the Differences

Both Are Portable but One Exceeds the Other

Migrating between Java platforms is easy compared to the .NET framework, which is time-consuming and difficult.

Both Have Fundamental Data Types but One Exceeds the Other

.NET incorporates more fundamental data types than Java. It also allows for more extension of these value types.

They Interact Well With Different Languages

Both Java and .NET have their own languages, which they can easily integrate and work with. Examples for Java include Clojure and Groovy while .NET includes visual basic, F# and C#, among others.

Choosing .NET Core or Java for your web development project is really dependent on the type of project, the resources you have, and many other factors.

Happy web developing!

How to make a Windows Service from .Net Core 3.0

How to make a Windows Service from .Net Core 3.0

NET Core 3.0, it's a lot easier to create Windows Services: just a single line of code ... If the application runs on a Windows system, the method ..

In this blog post, we will create a demo Windows Service application which includes a set of features such as reading configurations, logging to files, dependency injection, file system watcher, and so on. The application will be written in .NET Core 3.0, which introduces new concepts like generic host, worker service, background service, and so on. We will go over the installation process of our Windows Service application as well.

The complete solution can be found in this GitHub repository. This application can be used as a bare-bones template for Windows Service applications or Console applications.

Why do we build Windows Service applications?

Microsoft Windows services allow us to create long-running executable applications that run in their own Windows sessions. Windows services don’t have any user interface, can be automatically started when the computer reboots, and can be paused and restarted.

Windows services are ideal for long-running functionality that does not interfere with other users who are working on the same computer. For example, in a Windows Service application, we can use a FileSystemWatcher to listen to the file system change notifications and raise events when a directory, or a file in a directory, changes. The beauty is that the Windows Service application handles all the events in background.

Practically, we usually run services in the security context of a specific user account that is different from the logged-on user or the default computer account. So a hacker cannot easily mess up the file system or the service related database through a compromised computer.

If you have created a Windows Service application in .NET framework, then you must remember the pain of debugging the Windows Service application. During those old days, the tool TopShelf helped us a little bit, but not much. Now, with the .NET Core 3.0, the experience of developing a Windows Service application is much more pleasant. In my opinion, the concept of a Windows Service is clearer as well.

In order to follow along, you need to have .NET Core 3.0 SDK installed. Also, you need to have Admin privilege in your computer or the hosting server, so that you can install the Windows Service and/or remove it.

Let’s first create a basic ASP.NET Core application and configure it to be able to be hosted in a Windows Service.

Create a bare-bones Windows Service application

We will use the worker service template from .NET Core as a starting point. If you are using Visual Studio, then you can follow the steps below:
(1) Create a new project.
(2) Select **Worker Service**. Select **Next**.
(3) Set the project name as “Demo”. Then select **Create**.
(4) In the **Create a new Worker service** dialog, select **Create**.

If you are using .NET CLI, then you can use the following command to create a solution which contains a Worker Service project.

dotnet new sln -o WindowsServiceDemo -n Demo
cd .\WindowsServiceDemo\
dotnet new worker -o Demo
dotnet sln add .\Demo\

In order to enable the worker service app to run as a Windows Service, we need to update the project a little bit by doing the following steps:

  1. Add a NuGet package [Microsoft.Extensions.Hosting.WindowsServices](https://www.nuget.org/packages/Microsoft.Extensions.Hosting.WindowsServices).
  2. Update the Program.cs by adding the IHostBuilder.UseWindowsService() extension method to the CreateHostBuilder process. The code snippet below shows an example.

Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureAppConfiguration((context, config) =>
            {
                // configure the app here.
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
}

Add line 10 to make the worker service app to be able to host in a Windows Service

In the code above, line 10 is the key to creating a Windows Service app. When the application is hosted in a Windows Service, the extension method IHostBuilder.UseWindowsService() will set the ContentRoot, configure logging, set the host lifetime to WindowsServiceLifetime, and so on.

Voila~ Now we can build the app.

Through these simple steps, we have created an ASP.NET Core app that can be hosted in a Windows Service. Moreover, this app is automatically a Console application that we can run it directly via executing the Demo.exe file in the output folder. This setup allows us to debug the application as a Console app, and allows us to host the app in a Windows Service with minimum configurations.

Bonus: we can check if the app is running as a Windows Service or not using the bool WindowsServiceHelpers.IsWindowsService() method, which returns true if the current process is hosted as a Windows Service, otherwise false.

Add Serilog as a logging provider

Logging is essential for monitoring the status of our application. We definitely need logging for Windows Service applications, because they don’t have any interface and they are totally in the background. In this application, we will use Serilog to log messages to both Console output and physical files.

We will need to install the following NuGet packages that are related to Serilog: Serilog.Enrichers.Thread, Serilog.Extensions.Hosting, Serilog.Sinks.Console, and Serilog.Sinks.File. All of these NuGet packages should use their latest versions.

Then we update the Main method in the Program.cs file to be the code snippet below.

Program.cs

public static void Main(string[] args)
{
    const string loggerTemplate = @"{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u4}]<{ThreadId}> [{SourceContext:l}] {Message:lj}{NewLine}{Exception}";
    var baseDir = AppDomain.CurrentDomain.BaseDirectory;
    var logfile = Path.Combine(baseDir, "App_Data", "logs", "log.txt");
    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
        .Enrich.With(new ThreadIdEnricher())
        .Enrich.FromLogContext()
        .WriteTo.Console(LogEventLevel.Information, loggerTemplate, theme: AnsiConsoleTheme.Literate)
        .WriteTo.File(logfile, LogEventLevel.Information, loggerTemplate,
            rollingInterval: RollingInterval.Day, retainedFileCountLimit: 90)
        .CreateLogger();

    try
    {
        Log.Information("====================================================================");
        Log.Information($"Application Starts. Version: {System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version}");
        Log.Information($"Application Directory: {AppDomain.CurrentDomain.BaseDirectory}");
        CreateHostBuilder(args).Build().Run();
    }
    catch (Exception e)
    {
        Log.Fatal(e, "Application terminated unexpectedly");
    }
    finally
    {
        Log.Information("====================================================================\r\n");
        Log.CloseAndFlush();
    }
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureAppConfiguration((context, config) =>
        {
            // Configure the app here.
        })
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Worker>();
        })
        .UseSerilog();

add Serilog

In the code snippet above, we added two logging sinks: (1) Console (line 10) with color theme, and (2) plain text files (line 11) that are rolling every day. The logging message are enriched with ThreadID and LogContext, which are two common fields that can help us diagnosing issues if any.

In the end, we add the line 44, .UseSerilog(), to the HostBuilder, so that the host will use Serilog as a logging provider.

Caveat:
By default, the log file path (in line 11) should be able to use a relative path with respect to the assembly entry file. However, when an application is hosted in a Windows Service, the current working directory is set to the “ _C:\WINDOWS\system32_” folder, which is not a good place for log files. So I used an absolute path, with respect to AppDomain.CurrentDomain.BaseDirectory, to make sure the log files are written and saved into the proper location.

Now, if we run the application, we should be able to see both Console outputs and a log file with all logging messages.

We can implement the Windows Service app to run scheduled background tasks, execute long running jobs, and so on. In this blog post, we will utilized a FileSystemWatcher to run background tasks when some specific file system events raise. This process is useful to monitor shared network folders or SFTP folders, in which users drop files.

Add FileSystemWatcher

For those who are new to FileSystemWatcher, you can read more from this article in Microsoft Docs. We are going to add a FileSystemWatcher to listen to the file system change notifications when a new txt file is created in a directory, C:\temp.

The FileSystemWatcher is better to live in the Worker service, because they will have the same lifetime. We initialize the FileSystemWatcher when the Worker service starts, and we dispose the FileSystemWatcher when the Worker service disposes.

The code snippet below shows an example Worker.cs file.

Worker.cs

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private FileSystemWatcher _folderWatcher;
    private readonly string _inputFolder;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
        _inputFolder = @"C:\temp";
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.CompletedTask;
    }

    public override Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Service Starting");
        if (!Directory.Exists(_inputFolder))
        {
            _logger.LogWarning($"Please make sure the InputFolder [{_inputFolder}] exists, then restart the service.");
            return Task.CompletedTask;
        }

        _logger.LogInformation($"Binding Events from Input Folder: {_inputFolder}");
        _folderWatcher = new FileSystemWatcher(_inputFolder, "*.TXT")
        {
            NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName |
                              NotifyFilters.DirectoryName
        };
        _folderWatcher.Created += Input_OnChanged;
        _folderWatcher.EnableRaisingEvents = true;

        return base.StartAsync(cancellationToken);
    }

    protected void Input_OnChanged(object source, FileSystemEventArgs e)
    {
        if (e.ChangeType == WatcherChangeTypes.Created)
        {
            _logger.LogInformation($"InBound Change Event Triggered by [{e.FullPath}]");

            // do some work

            _logger.LogInformation("Done with Inbound Change Event");
        }
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stopping Service");
        _folderWatcher.EnableRaisingEvents = false;
        await base.StopAsync(cancellationToken);
    }

    public override void Dispose()
    {
        _logger.LogInformation("Disposing Service");
        _folderWatcher.Dispose();
        base.Dispose();
    }
}

A background service with a file system watcher

In line 28, we set the filter to be “*.TXT”, which tells the FileSystemWatcher to watch for specify the type of files in the input folder, which is the txt file type in this project. The FileSystemWatcher accepts event handlers for Changed, Created, Deleted, and Renamed events. For demo purposes, we only handle the new txt file Created events in this project.

If we run the application, then we are able to observe the effects of the Worker service and the FileSystemWatcher. Once a new txt file is created in the C:\temp folder, the application will get notified and the Input_OnChanged event delegate will be called.

Awesome. Now we have a background process to watch the file changes.

Note: In order to work on files or folders in the file system, it’s important to make sure that the current user (for debugging purposes) and the Log On As account have proper permission to the intended working directory.

Bonus: We can use WindowsIndentity.GetCurrent().Name to verify the current user name. I usually write the user name to the application logs as a tracking measure.

Add configuration files

You might have noticed that the input folder path is a magic string, C:\temp, in the code above. We can improve the code by loading a configuration file to get the input folder path.

We add a JSON object “AppSettings” to the appsettings.json file. For demo purposes, we only add one property, InputFolder, in the AppSettings object. The settings can be extended as needed.

appsettings.json

"AppSettings": {
    "InputFolder": "C:\\temp"
}

In order to bind the AppSettings JSON value, we create a C# class file, AppSettings.cs, as follows.

AppSettings.cs

public class AppSettings
{
    public string InputFolder { get; set; }
}

Then, we register the configuration, AppSettings, in the Dependency Injection (DI) container by adding a line in the Program.cs file as follows.

Program.cs

.ConfigureServices((hostContext, services) =>
{
    services.AddHostedService<Worker>();
    services.Configure<AppSettings>(hostContext.Configuration.GetSection("AppSettings"));
})

In the end, we can inject the settings to the Worker service like the following code snippet.

Worker.cs

public Worker(ILogger<Worker> logger, IOptions<AppSettings> settings)
{
    _logger = logger;
    _inputFolder = settings.Value.InputFolder;
}

In this way, we eliminated the magic string, and we have a more extendable code base.

Caveat: The Hosting Environment
It is known that ASP.NET Core web app uses the environment variable ASPNETCORE_ENVIRONMENT to determine the web host environment. However, in the case of Generic Host (link), the host environment is determined by the environment variable DOTNET_ENVIRONMEN by default, which is different from the one for Web applications. Well, you can always overwrite the HostEnvironment using a key-value pair with the key “environment” and its value.

We don’t need to explicitly add JSON file providers for the appsettings.json and appsettings.{environment}.json files, because they are automatically configured inside the Host.CreateDefaultBuilder(args) method. So you might want to make sure the environment name is correctly set, if you want to load correct settings in the appsettings.{environment}.json file.

Add a Scoped Service

In many cases, our applications depend on some short-lived services, for example, database connections, HTTP Client. We don’t want the application holds unnecessary stale resources, so we register those short-lived services as Scoped or Transient dependencies in the DI container. All these should be straightforward in Web applications. However, in Windows Service Applications, there’s some extra work to do.

For demo purposes, we will keep this application simple, and we add two contrived services in the Demo project. The following two code snippets show the SeriveA and ServiceB classes.

ServiceA.cs

public interface IServiceA
{
    void Run();
}

public class ServiceA : IServiceA
{
    private readonly ILogger<ServiceA> _logger;
    private readonly IServiceB _serviceB;

    public ServiceA(ILogger<ServiceA> logger, IServiceB serviceB)
    {
        _logger = logger;
        _serviceB = serviceB;
    }

    public void Run()
    {
        _logger.LogInformation("In Service A");
        _serviceB.Run();
    }
}

ServiceB.cs

public interface IServiceB
{
    void Run();
}

public class ServiceB : IServiceB
{
    private readonly ILogger<ServiceB> _logger;

    public ServiceB(ILogger<ServiceB> logger)
    {
        _logger = logger;
    }

    public void Run()
    {
        _logger.LogInformation("In Service B");
    }
}

We inject the ServiceB into the ServiceA, and we will use ServiceA as an entry point to run the process when a new file is created and detected by the FileSystemWatcher in the Worker service.

We register the ServiceA and the ServiceB in the Program.cs file as follows.
Program.cs

.ConfigureServices((hostContext, services) =>
{
    services.AddHostedService<Worker>();
    services.Configure<AppSettings>(hostContext.Configuration.GetSection("AppSettings"));
    services.AddScoped<IServiceA, ServiceA>();
    services.AddScoped<IServiceB, ServiceB>();
})

Then we inject the IServiceA to the Worker service and call serviceA.Run() as follows.

Worker.cs

public class Worker : BackgroundService
{
    // ...
    private readonly IServiceA _serviceA;

    public Worker(ILogger<Worker> logger, IOptions<AppSettings> settings, IServiceA serviceA)
    {
        // ...
        _serviceA = serviceA;
    }
    // ...
    protected void Input_OnChanged(object source, FileSystemEventArgs e)
    {
        if (e.ChangeType == WatcherChangeTypes.Created)
        {
            // ...
            _serviceA.Run();
            // ...
        }
    }
    //...
}

All done. Everything is hooked up. However, when we run the program, we will get an error immediately. The error message is similar to the following.

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: Demo.Worker': Cannot consume scoped service 'Demo.Services.IServiceA' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)

The key part of the error message is “cannot consume scoped service from singleton”. This error is due to the Worker service has a singleton lifetime, while the ServiceA has a scoped lifetime, which would be garbage collected before the Worker service. Thus they cause a violation in DI container.

In order to make it work, we can borrow the IServiceProvider to create a scope and resolve the scoped service. The following code snippet shows an example.

Worker.cs

public class Worker : BackgroundService
{
    // ...
    private readonly IServiceProvider _services;

    public Worker(ILogger<Worker> logger, IOptions<AppSettings> settings, IServiceProvider services)
    {
        // ...
        _services = services;
    }
    // ...
    protected void Input_OnChanged(object source, FileSystemEventArgs e)
    {
        if (e.ChangeType == WatcherChangeTypes.Created)
        {
            // ...
            using (var scope = _services.CreateScope())
            {
                var serviceA = scope.ServiceProvider.GetRequiredService<IServiceA>();
                serviceA.Run();
            }
            // ...
        }
    }
    //...
}

In this way, the DI container is able to resolve all dependencies. Problem solved!

Now, we are ready to deploy our application to a Windows Service.

Windows Service management

To achieve best performance, we first need to build our application in the Release mode.

If you have PowerShell 6.2+, then you can use a series of commands to install, start/stop, remove Windows Services. These commands include New-Service, Start-Service, Get-Service, Stop-Service, and Remove-Service.

BTW: Make sure you have Admin privilege to do these operations.

Here, we will take the old fashion approach to manage Windows Services using sc.exe in CMD. Note: in CMD environment only. The sc commands might not work in PowerShell environment.

The commands to create, start/stop, and delete a Windows Service are shown in the following code snippet.

cmd.bat

:: Create a Windows Service
sc create DemoService DisplayName="Demo Service" binPath="C:\full\path\to\Demo.exe"

:: Start a Windows Service
sc start DemoService

:: Stop a Windows Service
sc stop DemoService

:: Delete a Windows Service
sc delete DemoService

It is worth mentioning that the binPath needs to be the full path of the exe file of the application. All the commands should be easy to use.

Looks like we have covered all the stuff as planed. Hope you have learned something new, and I bet you are able to get started to create a Windows Service app in .NET Core 3 now.

If you need reference, you can find the full project in this GitHub repository.

Thanks for reading.