Skip to content

Commit

Permalink
Merge pull request #23 from Regenhardt/master
Browse files Browse the repository at this point in the history
Upgrade to Microsoft To Do and .NET 8
  • Loading branch information
mehmetseckin authored Feb 23, 2024
2 parents bb05810 + 0b9569a commit 4be003b
Show file tree
Hide file tree
Showing 27 changed files with 719 additions and 441 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

# Todo CLI

A cross-platform command-line interface to interact with Microsoft To Do, built using .NET Core 3.
A cross-platform command-line interface to interact with Microsoft To Do, built using .NET 8.

## Build Status

| Platform | Status |
| ------ | ------------ |
| CI | [![CI build status](https://dev.azure.com/mtseckin/todo-cli/_apis/build/status/CI)](https://dev.azure.com/mtseckin/todo-cli/_build/latest?definitionId=1) |
| Windows 10 (x64) | [![Windows 10 (x64) build status](https://dev.azure.com/mtseckin/todo-cli/_apis/build/status/CD?stageName=win10_x64)](https://dev.azure.com/mtseckin/todo-cli/_build/latest?definitionId=5) |
| Windows (x64) | [![Windows (x64) build status](https://dev.azure.com/mtseckin/todo-cli/_apis/build/status/CD?stageName=win_x64)](https://dev.azure.com/mtseckin/todo-cli/_build/latest?definitionId=5) |
| Linux (x64) | [![Linux (x64) build status](https://dev.azure.com/mtseckin/todo-cli/_apis/build/status/CD?stageName=linux_x64)](https://dev.azure.com/mtseckin/todo-cli/_build/latest?definitionId=5) |
| macOS X (x64) | [![macO X (x64) build status](https://dev.azure.com/mtseckin/todo-cli/_apis/build/status/CD?stageName=osx_x64)](https://dev.azure.com/mtseckin/todo-cli/_build/latest?definitionId=5) |

Expand Down Expand Up @@ -62,7 +62,7 @@ Be nice to people, give constructive feedback, and have fun!

This project is built using the following nuggets of awesomeness, and many more. Many thanks to the folks who are working on and maintaining these products.

- [.NET Core 3](https://github.com/dotnet/core)
- [.NET 8](https://github.com/dotnet/core)
- [System.CommandLine](https://github.com/dotnet/command-line-api)
- [Microsoft Graph Beta SDK](https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet)
- [Inquirer.cs](https://github.com/agolaszewski/Inquirer.cs)
- [Inquirer.cs](https://github.com/hayer/Inquirer.cs)
14 changes: 7 additions & 7 deletions pipelines/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ trigger:
stages:
- template: templates/stages/build.yml
parameters:
name: 'win10_x64'
runtimeIdentifier: 'win10-x64'
name: 'win_x64'
runtimeIdentifier: 'win-x64'
vmImage: 'windows-latest'
- template: templates/stages/build.yml
parameters:
Expand All @@ -27,10 +27,10 @@ stages:
vmImage: 'ubuntu-latest'
variables:
artifactsDirectory: '$(Pipeline.Workspace)/artifacts'
win10_x64_artifactPath: '$(artifactsDirectory)/todo.$(build.buildNumber).win10-x64'
win_x64_artifactPath: '$(artifactsDirectory)/todo.$(build.buildNumber).win-x64'
linux_x64_artifactPath: '$(artifactsDirectory)/todo.$(build.buildNumber).linux-x64'
osx_x64_artifactPath: '$(artifactsDirectory)/todo.$(build.buildNumber).osx-x64'
dependsOn: ['win10_x64', 'linux_x64', 'osx_x64']
dependsOn: ['win_x64', 'linux_x64', 'osx_x64']
jobs:
- job: 'release_job'
steps:
Expand All @@ -41,11 +41,11 @@ stages:
inputs:
targetPath: '$(artifactsDirectory)'
- task: ArchiveFiles@2
displayName: 'Create $(win10_x64_artifactPath).zip'
displayName: 'Create $(win_x64_artifactPath).zip'
inputs:
rootFolderOrFile: '$(win10_x64_artifactPath)'
rootFolderOrFile: '$(win_x64_artifactPath)'
includeRootFolder: false
archiveFile: '$(win10_x64_artifactPath).zip'
archiveFile: '$(win_x64_artifactPath).zip'
- task: ArchiveFiles@2
displayName: 'Create $(linux_x64_artifactPath).tar.gz'
inputs:
Expand Down
2 changes: 1 addition & 1 deletion pipelines/templates/stages/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
parameters:
name: ''
runtimeIdentifier: 'win10-x64'
runtimeIdentifier: 'win-x64'
vmImage: 'windows-latest'
dependsOn: []

Expand Down
39 changes: 21 additions & 18 deletions src/Todo.CLI/Auth/TodoCliAuthenticationProviderFactory.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Graph;
using Microsoft.Graph.Auth;

namespace Todo.CLI.Auth;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Client;
using Microsoft.Kiota.Abstractions.Authentication;

namespace Todo.CLI.Auth
static class TodoCliAuthenticationProviderFactory
{
static class TodoCliAuthenticationProviderFactory
public static IAuthenticationProvider GetAuthenticationProvider(IServiceProvider factory)
{
public static IAuthenticationProvider GetAuthenticationProvider(IServiceProvider factory)
{
var config = (TodoCliConfiguration)factory.GetService(typeof(TodoCliConfiguration));
var config = factory.GetRequiredService<TodoCliConfiguration>();

IPublicClientApplication app = PublicClientApplicationBuilder
.Create(config.ClientId)
.WithRedirectUri("http://localhost") // Only loopback redirect uri is supported, see https://aka.ms/msal-net-os-browser for details
.Build();

TokenCacheHelper.EnableSerialization(app.UserTokenCache);

IPublicClientApplication app = PublicClientApplicationBuilder
.Create(config.ClientId)
.WithRedirectUri("http://localhost") // Only loopback redirect uri is supported, see https://aka.ms/msal-net-os-browser for details
.Build();

TokenCacheHelper.EnableSerialization(app.UserTokenCache);
var login = app.AcquireTokenInteractive(config.Scopes).WithPrompt(Prompt.NoPrompt).ExecuteAsync()
.GetAwaiter().GetResult();
var token = login.AccessToken;

return new InteractiveAuthenticationProvider(app, config.Scopes);
}
return new ApiKeyAuthenticationProvider("Bearer " + token, "Authorization",
ApiKeyAuthenticationProvider.KeyLocation.Header);
}
}
}
70 changes: 33 additions & 37 deletions src/Todo.CLI/Auth/TokenCacheHelper.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,51 @@
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Todo.CLI.Auth
namespace Todo.CLI.Auth;

static class TokenCacheHelper
{
static class TokenCacheHelper
public static void EnableSerialization(ITokenCache tokenCache)
{
public static void EnableSerialization(ITokenCache tokenCache)
{
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}

/// <summary>
/// Path to the token cache
/// </summary>
public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";
/// <summary>
/// Path to the token cache
/// </summary>
public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin";

private static readonly object FileLock = new object();
private static readonly object FileLock = new object();


private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
lock (FileLock)
{
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
}

private static void AfterAccessNotification(TokenCacheNotificationArgs args)
private static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
lock (FileLock)
{
lock (FileLock)
{
// reflect changesgs in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser)
);
}
// reflect changesgs in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser)
);
}
}
}
}
}
43 changes: 27 additions & 16 deletions src/Todo.CLI/Commands/AddCommand.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Text;
using Todo.CLI.Handlers;

namespace Todo.CLI.Commands
namespace Todo.CLI.Commands;

public class AddCommand : Command
{
public class AddCommand : Command
public AddCommand(IServiceProvider serviceProvider) : base("add", "Adds a to do item or list.")
{
public AddCommand(IServiceProvider serviceProvider) : base("add")
{
Description = "Adds a to do item.";
Add(new AddListCommand(serviceProvider));
Add(new AddItemCommand(serviceProvider));
}

internal class AddListCommand : Command
{
private static readonly Argument<string> NameArgument = new("name", "The name of the new to do list.");

AddArgument(GetSubjectArgument());
public AddListCommand(IServiceProvider serviceProvider) : base("list", "Adds a new to do list.")
{
AddArgument(NameArgument);

Handler = AddCommandHandler.Create(serviceProvider);
this.SetHandler(AddCommandHandler.List.Create(serviceProvider), NameArgument);
}
}

private Argument GetSubjectArgument()
internal class AddItemCommand : Command
{
private static readonly Argument<string> ListArgument = new("list", "The list to add the to do item to.");
private static readonly Argument<string> SubjectArgument = new("subject", "The subject of the new to do item.");

public AddItemCommand(IServiceProvider serviceProvider) : base("item", "Adds a new to do item to the given list.")
{
return new Argument("subject")
{
Description = "The subject of the new to do item.",
ArgumentType = typeof(string)
};
AddArgument(ListArgument);
AddArgument(SubjectArgument);

this.SetHandler(AddCommandHandler.Item.Create(serviceProvider), ListArgument, SubjectArgument);
}
}
}
}
30 changes: 15 additions & 15 deletions src/Todo.CLI/Commands/CompleteCommand.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
using System;
using System.CommandLine;
using Todo.CLI.Handlers;
using Todo.Core;
using Todo.Core.Model;

namespace Todo.CLI.Commands
namespace Todo.CLI.Commands;

public class CompleteCommand : Command
{
public class CompleteCommand : Command
{
public CompleteCommand(IServiceProvider serviceProvider) : base("complete")
{
Description = "Completes a to do item.";
private static readonly Argument<string> ItemArg =
new("name",
"The name of the todo item to complete. If multiple lists have this item, the first one will be completed.")
{ Arity = ArgumentArity.ZeroOrOne };
private static readonly Option<string> ListOpt = new(["--list", "-l"], "The name of the list to complete the item in.")
{ Arity = ArgumentArity.ZeroOrOne };

AddOption(GetItemOption());
public CompleteCommand(IServiceProvider serviceProvider) : base("complete")
{
Description = "Completes a to do item.";

Handler = CompleteCommandHandler.Create(serviceProvider);
}
Add(ItemArg);
Add(ListOpt);

private Option GetItemOption()
{
return new Option(new string[] { "id", "item-id" }, "The unique identifier of the todo item to complete.");
}
this.SetHandler(CompleteCommandHandler.Create(serviceProvider), ItemArg, ListOpt);
}
}
44 changes: 24 additions & 20 deletions src/Todo.CLI/Commands/ListCommand.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
using System;
using System.CommandLine;
using Todo.CLI.Handlers;
using Todo.Core;

namespace Todo.CLI.Commands
namespace Todo.CLI.Commands;

public class ListCommand : Command
{
public class ListCommand : Command
{
public ListCommand(IServiceProvider serviceProvider) : base("list")
{
Description = "Retrieves a list of the to do items.";
private static readonly Option<bool> GetAllOption =
new(["-a", "--all"], "Lists all to do items including the completed ones.");

private static readonly Option<bool> NoStatusOption = new(["--no-status"],
"Suppresses the bullet indicating whether the item is completed or not.");

AddOption(GetAllOption());
AddOption(GetNoStatusOption());
private static readonly Option<DateTime?> OlderThanOption =
new(["--older-than"], "Only items completed before this date.");

private static readonly Argument<string> ListNameArgument = new("list-name", "Only list tasks of this To-Do list.")
{
Arity = ArgumentArity.ZeroOrOne
};

Handler = ListCommandHandler.Create(serviceProvider);
}

private Option GetAllOption()
{
return new Option(new string[] { "-a", "--all" }, "Lists all to do items including the completed ones.");
}
public ListCommand(IServiceProvider serviceProvider) : base("list")
{
Description = "Retrieves a list of the to do items across all To-Do lists.";

private Option GetNoStatusOption()
{
return new Option(new string[] { "--no-status" }, "Suppresses the bullet indicating whether the item is completed or not.");
}
Add(GetAllOption);
Add(NoStatusOption);
Add(OlderThanOption);
Add(ListNameArgument);

this.SetHandler(ListCommandHandler.Create(serviceProvider), GetAllOption, NoStatusOption, OlderThanOption, ListNameArgument);
}
}
}
18 changes: 9 additions & 9 deletions src/Todo.CLI/Commands/RemoveCommand.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using System;
using System.CommandLine;
using Todo.CLI.Handlers;
using Todo.Core;
using Todo.Core.Model;

namespace Todo.CLI.Commands
namespace Todo.CLI.Commands;

public class RemoveCommand : Command
{
public class RemoveCommand : Command
private static readonly Option<string> ListOpt = new(["--list", "-l"], "The name of the list to remove the item from.");
private static readonly Option<DateTime?> OlderThanOpt = new(new[] { "--older-than" }, "Only items completed before this date.");
public RemoveCommand(IServiceProvider serviceProvider) : base("remove", "Deletes a to do item.")
{
public RemoveCommand(IServiceProvider serviceProvider) : base("remove")
{
Description = "Deletes a to do item.";
Handler = RemoveCommandHandler.Create(serviceProvider);
}
Add(ListOpt);
Add(OlderThanOpt);
this.SetHandler(RemoveCommandHandler.Create(serviceProvider), ListOpt, OlderThanOpt);
}
}
Loading

0 comments on commit 4be003b

Please sign in to comment.