StorageStandby
- 8 Devlogs
- 52 Total hours
A full-stack native Windows background application that automatically backups selected folders and files to online-based drives. Created after a very painful experience of bricking my old laptop š.
A full-stack native Windows background application that automatically backups selected folders and files to online-based drives. Created after a very painful experience of bricking my old laptop š.
Iāve mostly been working on database schemas and models since the last devlog, and Iāve created APIs to allow the frontend to access this information from the database as well. A lot of systems design work has been going on in the background. As Iām still a beginner to such concepts, a lot of rewriting code has also been going on in the background!
To be honest, progress has slowed since Iāve been splitting myself between working on the frontend and backend. I often find myself getting a bit stuck or lost in whatās happening. I think Iāll work on completely finishing one thing before moving onto the next for the next devlog.
Below are snippets of what Iāve been working on, including API methods for the frontend, the API endpoints for the backend, and some peeks at some of the changes Iāve made to existing models/new models that Iāve created.
Iāll be using the built-in C# FileSystemWatcher in order to track which files and folders the user has made changes to. The alternative is polling for these changes on-demand (e.g. checking the folderās āLast-Modifiedā date). However, the problem with such an approach is that Windows does not automatically update a folderās āLast Modifiedā date exactly when a file inside that folder is changed. Thus, to confirm whether any changes actually occurred to a folder or not, the program would have to poll ALL subdirectories and files, which could be performance-intensive. On the other hand, FileSystemWatcher is an abstraction over OS-level kernel APIs. The kernel itself registers a listener on an attached folder when FileSystemWatcher is used. Thus it is very efficient and much more accurate than polling a folderās properties.
In my specific architecture, API calls from frontend to backend work like so:
fetch() call from Vite frontend --> intercepted by WPF WebService2 middleware shell --> forwards down NamedPipe --> received by Kestrel (cross-platform web server) on the ASP.NET Core C# backend.
For the longest time, I just couldnāt get API calls from the frontend to reach the backend. I would always end up with a TypeError: failed to fetch(), which, as I found out, could mean a lot of things. Initially, I thought it was a CORS issue with the backend. I added builder.Services.AddCors() to allow cross-origin requests, and also adjusted the content-headers included with the fetch() request on the frontend, but I still experienced the same issue.
Eventually, I discovered that it was a CORS issue with the MIDDLEWARE. It turns out after the WPF WebView2 shell forwards the fetch() request down the NamedPipe and retrieves a response from the backend, it actually creates a new web request to return it to the frontend with createWebResourceRequest(). This new response DOESNāT COPY the CORS headers from the backend, meaning the Access-Control-Allow-Origin header was missing from the response despite the backend returning a 200 OK status code. This blocks the frontend from reading the response, and a generic TypeError: Failed to fetch is thrown.
Since the desktop app is essentially a proxy/interceptor - itās creating its own HTTP response, so it needs to handle CORS headers itself.
I was also missing an OPTIONS preflight responder on the backend, so I added that in as well.
When OAuth is requested from the frontend to the backend, the backend adds a listener to the providerās OAuth function call. This way, say when the user completes Googleās OAuth process, Google redirects to our listener url, and we can display a Success page to the user.
I had a minor logic error in my listener callback implementation where the same port was always used, even if it was unavailable. For this reason, if the user were to leave the OAuth page and retry the OAuth process, they would encounter an error if the listener was still not cleaned up. I implemented a dynamic port searcher to automatically serve the user a port that was free and unused.
Iām currently planning to implement the core of the backend functionality, which is to monitor file changes occuring to āwatchedā folders and upload them asynchronously and autonomously to providers the user has signed into.
A great way Iāve found to map out future app functionality is to map out schemas for anything that needs to be stored locally. Currently, Iām working on schemas for SystemSettings, SyncEvents, and FileSystemWatcherWorker.
Below is a short overview of the current basic features of the app, including OAuth account management + a notification/error-logging system.
These past days Iāve been focused on cleaning up the auth code and setting up frontend-backend communication.
.NET (modern C#) features native dependency injection, where abstractions can be mapped directly to a class, in order to allow that class to receive its required objects from an external source in a more efficient and cleaner manner.
In ASP.NET Core C#, you can register classes as a couple of different types of services to be injected into other classes. I mainly use singletons and scoped objects. Singletons, like their name suggests, are long-lived classes, of which a single instance exists throughout the applicationās lifecycle. By contrast, scoped objects are created once per client request before being destroyed.
The problem is that when you use dependency injection, you cannot inject a shorter-lifespan class into a longer-lifespan class (e.g. a scoped into a singleton). This necessitates the use of IServiceScopeFactory to automatically manage the lifetimes of these child classes.
By default, the System.Collections.Generic library in C# is NOT threadsafe. That means that, if multiple scoped objects or singletons access the same Dictionary, that could throw an error. Thus, I switched them out for ConcurrentDictionaries throughout my app.
I further integrated functionality that allows my app to store MULTIPLE accounts per provider (e.g. 3 OneDrive accounts, 4 Google Drive accounts). This required refactoring the CloudToken object to include a new AccountId prop, retrieving that prop from the OAuth2.0 protocol, and rewriting some logic in TokenManager to include AccountId validity checks.
I added a method to revoke OAuth by terminating the refresh and access tokens using the revoke OAuth2.0 endpoint.
With the basics of the auth protocol API and TokenManager API setup, Iāve begun attempting to facilitate API calls between the React Vite frontend and the ASP.NET Core C# backend. Itās still a WIP, but things are moving! I also added some additional UI to allow the user to select and manage their accounts on different providers.
Iām currently on vacation, but Iām trying to stay productive by working on some software projects.
The past few days, Iāve been working on setting up some basic OAuth functionality for Google and Google Drive. I thought it would be very simple at first, but perhaps since Iām new to C# and on vacation, it took me a bit to get used to the C#/ASP.NET features and development patterns, such as dependency injection.
Additionally, thereās plenty more to using OAuth 2.0 that I was not aware of at first. Step 1) involved acquiring ClientId and Clientsecret values from the Google Dev Console page. These were stored in User Secrets in Visual Studio along with the project.
For desktop clients, clients use the providerās OAuth API link to apply for a long-lived Refresh Token. This Refresh Token, once encrypted and stored securely, is used to continuously request for short-lived Access Tokens, which is what is actually used for Googleās API services, like the Google Drive API.
Encryption was setup using the .NET data protection API, DPAPI. I created two classes: a GoogleDriveProvider to manage Google-specific OAuth setup and Drive API calls, and a generic TokenManager that handles the refresh-access token exchange for all OAuth-related providers, including ones I will add in the future such as OneDrive and Dropbox.
For the HTTPClients used to make the web requests, I decided to add an HTTPClient to GoogleDriveProvider using builder.Services.AddHttpClient().
Finally, all pertinent information to the app is stored within an SQLite database using EF Core. Refresh Tokens are stored within an object called CloudTokens.
I have the basic functionality for the OAuth setup, so my next steps will just be to test it out and debug for any errors.
Below are some snapshots from the code I wrote.
Succeeded in porting (?) the Vite frontend to Wpf WebView2! Iām just happy it works now. There was an issue with the Configuration Manager/build setup, where WebView2 turned out to be incompatible with the Any CPU build, and so I had to convert the solution to an x64 platform build, which works out since thatās what I intended to do in the first place.
For now, the source of the WebView2 WPF frame is just a localhost url. When prod version comes out, Iāll be bundling a folder containing the Vite frontend along with the app, which will then become the new source.
Iāve finished the skeleton and main UI features of my frontend Vite app (unfortunately took longer than expected). Nothing actually works cause itās just a frontend for now.
I addedā¦
No functionality actually exists yet as itās just for planning.
Iāll be implementing the backend next and the WebView2 functionality, before establishing communication between the frontend and backend via Windows pipes.
Iām in the middle of transitioning from a Winforms + Windows Service backend architecture to a decoupled C# ASP.NET Web Service + React Vite frontend with WebView2.
WebView2 allows developers to embed web content (HTML/CSS/JS) directly into native Windows apps. I considered using Electron, but WebView2 is widely seen as more performant as it uses the Chromium runtime already built into Windows, as opposed to Electron which bundles a SEPARATE instance of Chromium and Node.js into every app, resulting in a large footprint.
Iāve redone the UI in Vite and setup the basic ASP.NET endpoints using pipes. The next steps would be to finalize a good-looking UI and create the WPF project that would host the WebView capabilities.
Setup a basic UI in Winforms, which took longer than I thought. Unforunately, at the time I started this project, I didnāt know about the possibilities of a ASP.NET + React setup. I wanted to avoid Electron due to the heavy resource consumption, but since the possibility of using WebView2 exists for me now, I might transition to that soon.
I just donāt mesh very well with Winformsā¦