Skip to content

Commit

Permalink
Add ability to publish commands to devices via Meadow.Cloud
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenkuhn committed Aug 18, 2023
1 parent 7a3123e commit 03b6785
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 113 deletions.
14 changes: 6 additions & 8 deletions Meadow.CLI.Core/CloudServices/CloudServiceBase.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
using Meadow.CLI.Core.Exceptions;
using Meadow.CLI.Core.Identity;
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Text;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Meadow.CLI.Core.CloudServices
{
public abstract class CloudServiceBase
{
IdentityManager _identityManager;
readonly IdentityManager _identityManager;

protected CloudServiceBase(IdentityManager identityManager)
{
_identityManager = identityManager;
}

protected async Task<HttpClient> AuthenticatedHttpClient()
protected async Task<HttpClient> GetAuthenticatedHttpClient(CancellationToken cancellationToken = default)
{
var authToken = await _identityManager.GetAccessToken();
var authToken = await _identityManager.GetAccessToken(cancellationToken);
if (string.IsNullOrEmpty(authToken))
{
throw new MeadowCloudAuthException();
}

HttpClient client = new HttpClient();
HttpClient client = new();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

return client;
Expand Down
27 changes: 6 additions & 21 deletions Meadow.CLI.Core/CloudServices/CollectionService.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
using System;
using Meadow.CLI.Core.CloudServices.Messages;
using Meadow.CLI.Core.Identity;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Meadow.CLI.Core.CloudServices.Messages;
using Meadow.CLI.Core.DeviceManagement.Tools;
using Meadow.CLI.Core.Exceptions;
using Meadow.CLI.Core.Identity;
using Microsoft.Extensions.Configuration;

namespace Meadow.CLI.Core.CloudServices;

public class CollectionService : CloudServiceBase
{
IConfiguration _config;
IdentityManager _identityManager;
readonly IConfiguration _config;

public CollectionService(IConfiguration config, IdentityManager identityManager) : base(identityManager)
{
_config = config;
_identityManager = identityManager;
}

public async Task<List<Collection>> GetOrgCollections(string orgId, string host, CancellationToken cancellationToken)
Expand All @@ -32,16 +24,9 @@ public async Task<List<Collection>> GetOrgCollections(string orgId, string host,
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var authToken = await _identityManager.GetAccessToken(cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(authToken))
{
throw new MeadowCloudAuthException();
}

HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

var result = await httpClient.GetStringAsync($"{host}/api/orgs/{orgId}/collections");
return JsonSerializer.Deserialize<List<Collection>>(result);
return JsonSerializer.Deserialize<List<Collection>>(result) ?? new List<Collection>();
}
}
54 changes: 54 additions & 0 deletions Meadow.CLI.Core/CloudServices/CommandService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Meadow.CLI.Core.Exceptions;
using Meadow.CLI.Core.Identity;
using Microsoft.Extensions.Configuration;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Meadow.CLI.Core.CloudServices
{
public class CommandService : CloudServiceBase
{
readonly IConfiguration _config;
readonly IdentityManager _identityManager;

public CommandService(IConfiguration config, IdentityManager identityManager) : base(identityManager)
{
_config = config;
_identityManager = identityManager;
}

public async Task PublishCommandForCollection(
string collectionId,
string commandName,
JsonDocument? arguments = null,
int qualityOfService = 0,
string? host = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(host))
{
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

var payload = new
{
commandName,
args = arguments,
qos = qualityOfService
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{host}/api/collections/{collectionId}/commands", content, cancellationToken);

if (!response.IsSuccessStatusCode)
{
var message = await response.Content.ReadAsStringAsync();
throw new MeadowCloudException(message);
}
}
}
}
16 changes: 7 additions & 9 deletions Meadow.CLI.Core/CloudServices/DeviceService.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
using System;
using Meadow.CLI.Core.Identity;
using Microsoft.Extensions.Configuration;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Meadow.CLI.Core.Exceptions;
using Meadow.CLI.Core.Identity;
using Microsoft.Extensions.Configuration;

namespace Meadow.CLI.Core.CloudServices
{
public class DeviceService : CloudServiceBase
{
IConfiguration _config;
readonly IConfiguration _config;

public DeviceService(IConfiguration config, IdentityManager identityManager) : base(identityManager)
{
_config = config;
}

public async Task<(bool isSuccess, string message)> AddDevice(string orgId, string id, string publicKey, string collectionId, string host)
public async Task<(bool isSuccess, string message)> AddDevice(string orgId, string id, string publicKey, string collectionId, string host, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(host))
{
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var httpClient = await AuthenticatedHttpClient();
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

dynamic payload = new
{
Expand All @@ -39,7 +37,7 @@ public DeviceService(IConfiguration config, IdentityManager identityManager) : b
var json = JsonSerializer.Serialize<dynamic>(payload);

var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{host}/api/devices", content);
var response = await httpClient.PostAsync($"{host}/api/devices", content, cancellationToken);

if (response.IsSuccessStatusCode)
{
Expand Down
103 changes: 43 additions & 60 deletions Meadow.CLI.Core/CloudServices/PackageService.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
using Meadow.CLI.Core.Identity;
using System.Text.Json;
using Meadow.CLI.Core.CloudServices.Messages;
using Meadow.CLI.Core.DeviceManagement.Tools;
using Meadow.CLI.Core.Exceptions;
using Meadow.CLI.Core.Identity;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Meadow.CLI.Core.Exceptions;
using System.Text.Json;
using System.Threading;
using Meadow.CLI.Core.CloudServices.Messages;
using Microsoft.Extensions.Configuration;
using Meadow.CLI.Core.DeviceManagement.Tools;
using System.Threading.Tasks;

namespace Meadow.CLI.Core.CloudServices
{
public class PackageService : CloudServiceBase
{
IConfiguration _config;
IdentityManager _identityManager;
readonly IConfiguration _config;

public PackageService(IConfiguration config, IdentityManager identityManager) : base(identityManager)
{
_config = config;
_identityManager = identityManager;
}

public async Task<Package> UploadPackage(string mpakPath, string orgId, string description, string host, CancellationToken cancellationToken)
Expand All @@ -37,39 +35,38 @@ public async Task<Package> UploadPackage(string mpakPath, string orgId, string d
{
throw new ArgumentException($"Invalid path: {mpakPath}");
}

var httpClient = await AuthenticatedHttpClient();

using (var multipartFormContent = new MultipartFormDataContent())
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

using var multipartFormContent = new MultipartFormDataContent();

var fileStreamContent = new StreamContent(File.OpenRead(mpakPath));
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

var fi = new FileInfo(mpakPath);
var crcFileHash = await CrcTools.CalculateCrc32FileHash(mpakPath);

dynamic payload = new {
orgId,
description = description ?? "",
crc = crcFileHash ?? "",
fileSize = fi.Length
};
var json = JsonSerializer.Serialize<dynamic>(payload);

multipartFormContent.Add(fileStreamContent, name: "file", fileName: fi.Name);
multipartFormContent.Add(new StringContent(json), "json");

var response = await httpClient.PostAsync($"{host}/api/packages", multipartFormContent);
if (response.IsSuccessStatusCode)
{
var package = JsonSerializer.Deserialize<Package>(await response.Content.ReadAsStringAsync());
return package!;
}
else
{
var fileStreamContent = new StreamContent(File.OpenRead(mpakPath));
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

var fi = new FileInfo(mpakPath);
var crcFileHash = await CrcTools.CalculateCrc32FileHash(mpakPath);

dynamic payload = new {
orgId,
description = description ?? "",
crc = crcFileHash ?? "",
fileSize = fi.Length
};
var json = JsonSerializer.Serialize<dynamic>(payload);

multipartFormContent.Add(fileStreamContent, name: "file", fileName: fi.Name);
multipartFormContent.Add(new StringContent(json), "json");

var response = await httpClient.PostAsync($"{host}/api/packages", multipartFormContent);
if (response.IsSuccessStatusCode)
{
var package = JsonSerializer.Deserialize<Package>(await response.Content.ReadAsStringAsync());
return package;
}
else
{
var message = await response.Content.ReadAsStringAsync();
throw new MeadowCloudException($"{response.StatusCode} {message}");
}
var message = await response.Content.ReadAsStringAsync();
throw new MeadowCloudException($"{response.StatusCode} {message}");
}
}

Expand All @@ -79,19 +76,12 @@ public async Task PublishPackage(string packageId, string collectionId, string m
{
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var authToken = await _identityManager.GetAccessToken(cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(authToken))
{
throw new MeadowCloudAuthException();
}

HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

var payload = new { metadata, collectionId };
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{host}/api/packages/{packageId}/publish", content);
var response = await httpClient.PostAsync($"{host}/api/packages/{packageId}/publish", content, cancellationToken);

if (!response.IsSuccessStatusCode)
{
Expand All @@ -106,18 +96,11 @@ public async Task<List<Package>> GetOrgPackages(string orgId, string host, Cance
{
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var authToken = await _identityManager.GetAccessToken(cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(authToken))
{
throw new MeadowCloudAuthException();
}

HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

var result = await httpClient.GetStringAsync($"{host}/api/orgs/{orgId}/packages");
return JsonSerializer.Deserialize<List<Package>>(result);
return JsonSerializer.Deserialize<List<Package>>(result) ?? new List<Package>();
}
}
}
20 changes: 7 additions & 13 deletions Meadow.CLI.Core/CloudServices/UserService.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
using Meadow.CLI.Core.CloudServices.Messages;
using Meadow.CLI.Core.Identity;
using System;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Text.Json;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Meadow.CLI.Core.Exceptions;
using Microsoft.Extensions.Configuration;

namespace Meadow.CLI.Core.CloudServices
{
public class UserService : CloudServiceBase
{
IConfiguration _config;
readonly IConfiguration _config;

public UserService(IConfiguration config, IdentityManager identityManager) : base(identityManager)
{
Expand All @@ -30,14 +24,14 @@ public async Task<List<UserOrg>> GetUserOrgs(string host, CancellationToken canc
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var httpClient = await AuthenticatedHttpClient();
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

var response = await httpClient.GetAsync($"{host}/api/users/me/orgs");
var response = await httpClient.GetAsync($"{host}/api/users/me/orgs", cancellationToken);

if (response.IsSuccessStatusCode)
{
var message = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<UserOrg>>(message);
return JsonSerializer.Deserialize<List<UserOrg>>(message) ?? new List<UserOrg>();
}
else
{
Expand All @@ -52,7 +46,7 @@ public async Task<List<UserOrg>> GetUserOrgs(string host, CancellationToken canc
host = _config[Constants.MEADOW_CLOUD_HOST_CONFIG_NAME];
}

var httpClient = await AuthenticatedHttpClient();
var httpClient = await GetAuthenticatedHttpClient(cancellationToken);

var response = await httpClient.GetAsync($"{host}/api/users/me");

Expand Down
Loading

0 comments on commit 03b6785

Please sign in to comment.