diff --git a/app/HealthyHabitat/App.config b/app/HealthyHabitat/App.config
index 99f37dd..679641c 100644
--- a/app/HealthyHabitat/App.config
+++ b/app/HealthyHabitat/App.config
@@ -14,9 +14,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/HealthyHabitat/HealthyHabitat.csproj b/app/HealthyHabitat/HealthyHabitat.csproj
index dc899d1..98d3de7 100644
--- a/app/HealthyHabitat/HealthyHabitat.csproj
+++ b/app/HealthyHabitat/HealthyHabitat.csproj
@@ -60,6 +60,9 @@
..\packages\Microsoft.Azure.Storage.Common.11.1.0\lib\net452\Microsoft.Azure.Storage.Common.dll
+
+ ..\packages\Microsoft.Identity.Client.4.4.0\lib\net45\Microsoft.Identity.Client.dll
+
..\packages\MvvmDialogs.5.3.0\lib\net45\MvvmDialogs.dll
@@ -69,6 +72,9 @@
+
+
+
..\packages\MvvmLightLibs.5.4.1.1\lib\net45\System.Windows.Interactivity.dll
diff --git a/app/HealthyHabitat/ViewModels/MainWindowViewModel.cs b/app/HealthyHabitat/ViewModels/MainWindowViewModel.cs
index 1835c5f..44aa1af 100644
--- a/app/HealthyHabitat/ViewModels/MainWindowViewModel.cs
+++ b/app/HealthyHabitat/ViewModels/MainWindowViewModel.cs
@@ -20,6 +20,8 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
+ using Microsoft.Identity.Client;
+ using Microsoft.Azure.Storage.Auth;
public class MainWindowViewModel : ViewModelBase
{
@@ -48,6 +50,18 @@ public RelayCommand DropCommand
private set;
}
+ public RelayCommand SignInCommand
+ {
+ get;
+ private set;
+ }
+
+ public RelayCommand SignOutCommand
+ {
+ get;
+ private set;
+ }
+
public ICommand ShowMessageBoxWithMessageCommand { get; }
#endregion Commands
@@ -137,8 +151,75 @@ public bool LocationDialogVisible
}
}
+ private bool _signInButtonVisible;
+ public bool SignInButtonVisible
+ {
+ get { return _signInButtonVisible; }
+ set
+ {
+ _signInButtonVisible = value;
+ RaisePropertyChanged("SignInButtonVisible");
+ }
+ }
+
+ private bool _signOutButtonVisible;
+ public bool SignOutButtonVisible
+ {
+ get { return _signOutButtonVisible; }
+ set
+ {
+ _signOutButtonVisible = value;
+ RaisePropertyChanged("SignOutButtonVisible");
+ }
+ }
+
+ private string _signedInUserName;
+ public string SignedInUserName
+ {
+ get { return _signedInUserName; }
+ set
+ {
+ _signedInUserName = value;
+ RaisePropertyChanged("SignedInUserName");
+ }
+ }
+
+ private string _displayMessage;
+ public string DisplayMessage
+ {
+ get { return _displayMessage; }
+ set
+ {
+ _displayMessage = value;
+ RaisePropertyChanged("DisplayMessage");
+ }
+ }
+
+ // Auth definitions -------------------------------------------------------------------
+ private readonly string ClientId = ConfigurationManager.AppSettings["ApplicationId"].ToString();
+ private readonly string ClientScopes = ConfigurationManager.AppSettings["ClientScopes"].ToString();
+
+ private IPublicClientApplication PublicClientApp { get; set; }
+ private AuthenticationResult AuthResult { get; set; }
+
+ // End Auth definitions -------------------------------------------------------------------
+
public MainWindowViewModel(IDialogService dialogService)
{
+ // Auth init -------------------------------------------------------------------
+ SignInButtonVisible = true;
+ DisplayMessage = "Please sign in.";
+ SignedInUserName = "";
+ PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
+ .WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
+ .WithLogging((level, message, containsPii) =>
+ {
+ Debug.WriteLine($"MSAL: {level} {message} ");
+ }, Microsoft.Identity.Client.LogLevel.Warning, enablePiiLogging: false, enableDefaultPlatformLogging: true)
+ .Build();
+
+ // End Auth init -------------------------------------------------------------------
+
NameValueCollection locationSection = (NameValueCollection)ConfigurationManager.GetSection("locations");
Locations = new ObservableCollection();
@@ -152,6 +233,11 @@ public MainWindowViewModel(IDialogService dialogService)
UploadClickCommand = new RelayCommand(() =>
{
+ if (this.SelectedLocation == null)
+ {
+ return;
+ }
+
LocationDialogVisible = false;
ProgressVisible = true;
@@ -175,8 +261,97 @@ public MainWindowViewModel(IDialogService dialogService)
LocationDialogVisible = true;
});
+
+ SignInCommand = new RelayCommand(async () =>
+ {
+ await Authorize();
+ DisplayBasicTokenInfo();
+ });
+
+ SignOutCommand = new RelayCommand(async () =>
+ {
+ await DeAuthorize();
+ DisplayBasicTokenInfo();
+ });
+ }
+
+ // Auth -------------------------------------------------------------------
+ private async Task Authorize()
+ {
+ AuthResult = null;
+
+ // It's good practice to not do work on the UI thread, so use ConfigureAwait(false) whenever possible.
+ IEnumerable accounts = await PublicClientApp.GetAccountsAsync().ConfigureAwait(true);
+ IAccount firstAccount = accounts.FirstOrDefault();
+
+ try
+ {
+ AuthResult = await PublicClientApp.AcquireTokenSilent(ClientScopes.Split(), firstAccount)
+ .ExecuteAsync();
+ }
+ catch (MsalUiRequiredException ex)
+ {
+ // A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenAsync to acquire a token
+ System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
+
+ try
+ {
+ AuthResult = await PublicClientApp.AcquireTokenInteractive(ClientScopes.Split())
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+ }
+ catch (MsalException msalex)
+ {
+ DisplayMessage = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
+ }
+ }
+ catch (Exception ex)
+ {
+ DisplayMessage = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
+ return;
+ }
+
+ if (AuthResult != null)
+ {
+ SignedInUserName = $"Signed in as: {AuthResult.Account.Username}";
+ SignInButtonVisible = false;
+ SignOutButtonVisible = true;
+ }
}
+ private async Task DeAuthorize()
+ {
+ IEnumerable accounts = await PublicClientApp.GetAccountsAsync().ConfigureAwait(false);
+ IAccount firstAccount = accounts.FirstOrDefault();
+
+ try
+ {
+ await PublicClientApp.RemoveAsync(firstAccount).ConfigureAwait(false);
+ AuthResult = null;
+ SignedInUserName = "";
+ SignOutButtonVisible = false;
+ SignInButtonVisible = true;
+ }
+ catch (MsalException ex)
+ {
+ DisplayMessage = $"Error signing-out user: {ex.Message}";
+ }
+ }
+
+ public void DisplayBasicTokenInfo()
+ {
+ if (AuthResult != null)
+ {
+ DisplayMessage = $"User Name: {AuthResult.Account.Username}" + Environment.NewLine + $"Token Expires: {AuthResult.ExpiresOn.ToLocalTime()}" + Environment.NewLine;
+ }
+ else
+ {
+ DisplayMessage = "You have been signed out.";
+ }
+ }
+
+ // End Auth -------------------------------------------------------------------
+
private async void CopyFilesToLocalCache(string[] files, string location, string season)
{
@@ -228,11 +403,145 @@ private async void CopyFilesToLocalCache(string[] files, string location, string
string[] subFolders = Directory.GetDirectories(cacheLocation);
+ await CreateStorageContainer();
foreach (string folder in subFolders)
{
- await InitiateAzCopy(folder, cacheLocation);
+ //await InitiateAzCopy(folder, cacheLocation);
+ await CreateStorageBlobs(folder, cacheLocation);
+ }
+ }
+
+ private async Task CreateStorageContainer()
+ {
+ if (AuthResult == null)
+ {
+ await Authorize();
+ }
+
+ if (AuthResult == null)
+ {
+ return;
+ }
+
+ int retryLimit = System.Convert.ToInt32(ConfigurationManager.AppSettings["RetryLimit"].ToString());
+ int retrySeconds = System.Convert.ToInt32(ConfigurationManager.AppSettings["RetrySeconds"].ToString());
+ string storageAccountName = ConfigurationManager.AppSettings["StorageAccountName"].ToString();
+ string storageContainerName = ConfigurationManager.AppSettings["ContainerName"].ToString();
+
+ TokenCredential tokenCredential = new TokenCredential(AuthResult.AccessToken);
+ StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
+
+ CloudBlobContainer container = new CloudBlobContainer(
+ new Uri($"https://{storageAccountName}.blob.core.windows.net/{storageContainerName}"),
+ storageCredentials);
+
+ DisplayMessage = $"Creating storage container {container.Uri.ToString()}" + Environment.NewLine;
+
+ int createContainerOK = 0;
+ while (createContainerOK < retryLimit)
+ {
+ try
+ {
+ container.CreateIfNotExists();
+ createContainerOK = retryLimit + 1;
+ }
+ catch
+ {
+ createContainerOK++;
+ DisplayMessage += $"...Retry {createContainerOK} of {retryLimit} will start in {retrySeconds} seconds." + Environment.NewLine;
+ await Task.Delay(retrySeconds * 1000);
+ }
+
+ if (createContainerOK == retryLimit)
+ {
+ DisplayMessage += $"Error creating container, check that you have the correct permissions for this storage account." + Environment.NewLine;
+ }
+ }
+ }
+
+ private async Task CreateStorageBlobs(string sourceDirectory, string cacheDirectory)
+ {
+ if (AuthResult == null)
+ {
+ return;
+ }
+
+ int retryLimit = System.Convert.ToInt32(ConfigurationManager.AppSettings["RetryLimit"].ToString());
+ int retrySeconds = System.Convert.ToInt32(ConfigurationManager.AppSettings["RetrySeconds"].ToString());
+ string storageAccountName = ConfigurationManager.AppSettings["StorageAccountName"].ToString();
+ string storageContainerName = ConfigurationManager.AppSettings["ContainerName"].ToString();
+
+ TokenCredential tokenCredential = new TokenCredential(AuthResult.AccessToken);
+ StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
+
+ string sourceDirName = new DirectoryInfo(sourceDirectory).Name;
+
+ foreach (string dir in Directory.GetDirectories(sourceDirectory))
+ {
+ string dirName = new DirectoryInfo(dir).Name;
+ DisplayMessage += $"Uploading {dir}" + Environment.NewLine;
+
+ int uploadDirectoryOK = 0;
+ foreach (string file in Directory.GetFiles(dir))
+ {
+ string fileName = new FileInfo(file).Name;
+
+ CloudBlockBlob blob = new CloudBlockBlob(
+ new Uri($"https://{storageAccountName}.blob.core.windows.net/{storageContainerName}/{sourceDirName}/{dirName}/{fileName}"),
+ storageCredentials);
+
+ DisplayMessage += $"Creating storage blob {blob.Uri.ToString()}" + Environment.NewLine;
+
+ int createBlobOK = 0;
+ while (createBlobOK < retryLimit)
+ {
+ try
+ {
+ await blob.UploadFromFileAsync(file);
+ createBlobOK = retryLimit + 1;
+ File.Delete(file);
+ }
+ catch
+ {
+ createBlobOK++;
+ DisplayMessage += $"...Retry {createBlobOK} of {retryLimit} will start in {retrySeconds} seconds." + Environment.NewLine;
+ await Task.Delay(retrySeconds * 1000);
+ }
+
+ if (createBlobOK == retryLimit)
+ {
+ DisplayMessage += $"Error creating blob, check that you have the correct permissions for this storage account." + Environment.NewLine;
+ }
+ }
+
+ if (createBlobOK != retryLimit + 1)
+ {
+ uploadDirectoryOK++;
+ }
+
+ if (uploadDirectoryOK == retryLimit)
+ {
+ DisplayMessage += $"Failed upload, too many consecutive errors." + Environment.NewLine;
+ break;
+ }
+ }
+
+ if (Directory.GetFiles(dir).Length == 0)
+ {
+ DisplayMessage += "Upload successful.";
+ Directory.Delete(dir, true);
+ }
+ }
+
+ if (Directory.GetDirectories(sourceDirectory).Length == 0)
+ {
+ Directory.Delete(sourceDirectory, true);
+ }
+
+ if (Directory.GetDirectories(cacheDirectory).Length == 0)
+ {
+ Directory.Delete(cacheDirectory, true);
}
-
}
private async Task InitiateAzCopy(string sourceDir, string parentDir)
diff --git a/app/HealthyHabitat/Views/MainWindow.xaml b/app/HealthyHabitat/Views/MainWindow.xaml
index 286b135..7bfbb50 100644
--- a/app/HealthyHabitat/Views/MainWindow.xaml
+++ b/app/HealthyHabitat/Views/MainWindow.xaml
@@ -243,5 +243,94 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/HealthyHabitat/packages.config b/app/HealthyHabitat/packages.config
index 9ad31ba..c69ea12 100644
--- a/app/HealthyHabitat/packages.config
+++ b/app/HealthyHabitat/packages.config
@@ -4,7 +4,11 @@
+
+
+
+
\ No newline at end of file