Commits (3)
......@@ -18,8 +18,8 @@ namespace pEp.DPE
/// </summary>
/// <param name="patch">The patch to reject.</param>
/// <param name="me">The own identity that rejects the patch.</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="HttpRequestException" />
/// <exception cref="ArgumentNullException">The post URI is null.</exception>
/// <exception cref="HttpRequestException">The HTTP request failed.</exception>
/// <returns>The Http response.</returns>
public static async Task<HttpResponseMessage> RejectPatch(Patch patch, PEPIdentity me)
{
......
......@@ -145,7 +145,7 @@ namespace pEp.DPE
{
Outlook.Folder inbox = null;
Outlook.Items items = null;
try
{
inbox = Globals.ThisAddIn.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) as Outlook.Folder;
......@@ -168,7 +168,7 @@ namespace pEp.DPE
// Now find the one with the respective patch id
string filter = string.Format("[{0}] = '{1}'", MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_ID, patch.Id);
items = items.Restrict(filter);
if (items.Count > 1)
{
Log.Error("GetPatchMailItem: More than one item found with patch id " + patch.Id);
......@@ -206,42 +206,32 @@ namespace pEp.DPE
/// <param name="patch">The patch to post.</param>
/// <param name="me">The own identity.</param>
/// <param name="postAction">The POST action to perform.</param>
/// <returns></returns>
private async Task<Exception> PostAsync(Patch patch, PEPIdentity me, PostAction postAction)
/// <exception cref="HttpRequestException">The HTTP request failed.</exception>
private async Task PostAsync(Patch patch, PEPIdentity me, PostAction postAction)
{
// Determine the correct method to execute
Task<HttpResponseMessage> postTask = null;
HttpResponseMessage httpResponseMessage = null;
switch (postAction)
{
case PostAction.Reject:
postTask = DPEWebClient.RejectPatch(patch, me);
httpResponseMessage = await DPEWebClient.RejectPatch(patch, me);
break;
case PostAction.Suggest:
postTask = DPEWebClient.SuggestPatch(patch, me);
httpResponseMessage = await DPEWebClient.SuggestPatch(patch, me);
break;
case PostAction.Support:
postTask = DPEWebClient.SupportPatch(patch, me);
httpResponseMessage = await DPEWebClient.SupportPatch(patch, me);
break;
default:
Log.ErrorAndFailInDebugMode("PostAsync: Unknown post action.");
break;
}
// Execute the task and return status
return await postTask.ContinueWith((task) =>
if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
{
if (task.Exception != null)
{
Log.Error("ExecuteAsync: Error executing POST of type " + Enum.GetName(typeof(PostAction), postAction));
return task.Exception;
}
else
{
return (task.Result.StatusCode != HttpStatusCode.OK) ?
new Exception(Enum.GetName(typeof(HttpStatusCode), task.Result.StatusCode)) :
null;
}
});
Log.Error("PostAsync: Status code is " + Enum.GetName(typeof(HttpStatusCode), httpResponseMessage.StatusCode));
throw new HttpRequestException(Enum.GetName(typeof(HttpStatusCode), httpResponseMessage.StatusCode));
}
}
/// <summary>
......@@ -249,15 +239,27 @@ namespace pEp.DPE
/// </summary>
/// <param name="patch">The patch to reject.</param>
/// <param name="me">The own identity that rejects the patch.</param>
/// <exception cref="HttpRequestException">The HTTP request failed.</exception>
/// <exception cref="UpdatePatchMailItemException">The update of the mail item failed.</exception>
public async Task Reject(Patch patch, PEPIdentity me)
{
if (await this.PostAsync(patch, me, PostAction.Reject) is Exception ex)
try
{
await this.PostAsync(patch, me, PostAction.Reject);
}
catch (HttpRequestException ex)
{
throw ex;
}
this.UpdatePatchMailItem(patch, PatchStatus.Rejected);
AdapterExtensions.ShowNotification("Patch rejected", patch.CommitMessage);
if (this.UpdatePatchMailItem(patch, PatchStatus.Rejected))
{
AdapterExtensions.ShowNotification("Patch rejected", patch.CommitMessage);
}
else
{
throw new UpdatePatchMailItemException("Error updating patch mail item.");
}
}
/// <summary>
......@@ -281,6 +283,11 @@ namespace pEp.DPE
omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE, DateTime.UtcNow);
omi.Save();
}
else
{
Log.Error("UpdatePatchMailItem: patch mail item not found.");
return false;
}
}
catch (Exception ex)
{
......@@ -307,9 +314,14 @@ namespace pEp.DPE
/// </summary>
/// <param name="patch">The patch to suggest.</param>
/// <param name="me">The own identity that suggests the patch.</param>
/// <exception cref="HttpRequestException">The HTTP request failed.</exception>
public async Task Suggest(Patch patch, PEPIdentity me)
{
if (await this.PostAsync(patch, me, PostAction.Suggest) is Exception ex)
try
{
await this.PostAsync(patch, me, PostAction.Suggest);
}
catch (HttpRequestException ex)
{
throw ex;
}
......@@ -322,15 +334,27 @@ namespace pEp.DPE
/// </summary>
/// <param name="patch">The patch to support.</param>
/// <param name="me">The own identity that supports the patch.</param>
/// <exception cref="HttpRequestException">The HTTP request failed.</exception>
/// <exception cref="UpdatePatchMailItemException">The update of the mail item failed.</exception>
public async Task Support(Patch patch, PEPIdentity me)
{
if (await this.PostAsync(patch, me, PostAction.Support) is Exception ex)
try
{
await this.PostAsync(patch, me, PostAction.Support);
}
catch (HttpRequestException ex)
{
throw ex;
}
this.UpdatePatchMailItem(patch, PatchStatus.Supported);
AdapterExtensions.ShowNotification("Patch supported", patch.CommitMessage);
if (this.UpdatePatchMailItem(patch, PatchStatus.Supported))
{
AdapterExtensions.ShowNotification("Patch supported", patch.CommitMessage);
}
else
{
throw new UpdatePatchMailItemException("Error updating patch mail item.");
}
}
/// <summary>
......@@ -345,5 +369,15 @@ namespace pEp.DPE
}
#endregion
/// <summary>
/// Custom exception for when the update of the patch mail item fails.
/// </summary>
public class UpdatePatchMailItemException : Exception
{
public UpdatePatchMailItemException(string message) : base(message)
{
}
}
}
}
\ No newline at end of file
using pEp.DPE;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace pEp.UI.Models
{
......@@ -71,8 +72,7 @@ namespace pEp.UI.Models
/// <param name="patchAction">The action to perform with the patch.</param>
/// <param name="patch">The patch to manage.</param>
/// <param name="isEditable">Whether the patch can be edited.</param>
/// <returns>The dialog result.</returns>
public static System.Windows.Forms.DialogResult ShowDialog(PatchDialog.PatchAction patchAction, Patch patch, bool isEditable = false)
public static async Task ShowDialog(PatchDialog.PatchAction patchAction, Patch patch, bool isEditable = false)
{
// If needed, make sure we have a valid own identity
PEPIdentity me = null;
......@@ -82,43 +82,40 @@ namespace pEp.UI.Models
true) != Globals.ReturnStatus.Success))
{
Log.Error("ShowDialog: Error getting own identity.");
return System.Windows.Forms.DialogResult.Cancel;
return;
}
// Open dialog and retrieve result
PatchDialog patchDialog = new PatchDialog(patchAction, patch, isEditable);
System.Windows.Forms.DialogResult dialogResult = new DialogHost(patchDialog).ShowDialog();
// Perform the action the user selected
switch (patchAction)
if (new DialogHost(patchDialog).ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
case PatchDialog.PatchAction.NewPatch:
case PatchDialog.PatchAction.EditPatch:
// Perform the action the user selected
if (patchAction == PatchDialog.PatchAction.NewPatch)
{
try
{
if (dialogResult == System.Windows.Forms.DialogResult.OK)
{
Globals.ThisAddIn.DistributedPolicyEngine.Suggest(patchDialog.Patch, me);
}
await Globals.ThisAddIn.DistributedPolicyEngine.Suggest(patchDialog.Patch, me);
}
break;
case PatchDialog.PatchAction.SupportOrRejectPatch:
catch (Exception ex)
{
if (dialogResult == System.Windows.Forms.DialogResult.OK)
{
Globals.ThisAddIn.DistributedPolicyEngine.Support(patchDialog.Patch, me);
}
else if (dialogResult == System.Windows.Forms.DialogResult.No)
Log.Error("ShowDialog: Error occured. " + ex);
while (ex.InnerException != null)
{
Globals.ThisAddIn.DistributedPolicyEngine.Reject(patchDialog.Patch, me);
ex = ex.InnerException;
}
CustomMessageBox.ShowDialog(ex.Message, "Error suggesting patch", "OK");
return;
}
break;
case PatchDialog.PatchAction.ShowPatch:
default:
break;
}
return dialogResult;
AdapterExtensions.ShowNotification("Patch successfully suggested", patch.CommitMessage);
}
else
{
Log.ErrorAndFailInDebugMode("ShowDialog: Unsupported patch action " + Enum.GetName(typeof(PatchDialog.PatchAction), patchAction));
}
}
}
}
}
......@@ -2,8 +2,8 @@
using pEp.UI.Models;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace pEp.UI.ViewModels
......@@ -18,6 +18,7 @@ namespace pEp.UI.ViewModels
private FlowDocument _DisplayDiff = null;
private DateTime? _EditDate = null;
private string _Explanation = null;
private bool _IsLoading = false;
private DistributedPolicyEngine.PatchStatus _Status = DistributedPolicyEngine.PatchStatus.Open;
#endregion
......@@ -75,6 +76,11 @@ namespace pEp.UI.ViewModels
/// </summary>
public string Explanation { get => this._Explanation; set => this.SetProperty(ref this._Explanation, value); }
/// <summary>
/// Gets or sets whether the screen is loading.
/// </summary>
public bool IsLoading { get => this._IsLoading; set => this.SetProperty(ref this._IsLoading, value); }
/// <summary>
/// Gets whether the OK button is visible.
/// </summary>
......@@ -167,27 +173,55 @@ namespace pEp.UI.ViewModels
#region Methods
/// <summary>
/// Rejects this patch.
/// Executes a patch task (support/reject) and shows an error message if necessary.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private async void RejectPatch(object parameter)
/// <param name="patchTask">The task to execute.</param>
/// <returns>True if the patch task was executed successfully, otherwise false.</returns>
private async Task<bool> ExecutePatchTask(Task patchTask)
{
try
{
await Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity());
// Block UI and show loading cursor
this.IsLoading = true;
Mouse.OverrideCursor = Cursors.Wait;
// Execute the task
await patchTask;
// Unblock UI again
this.IsLoading = false;
Mouse.OverrideCursor = null;
}
catch (Exception ex)
{
this.IsLoading = false;
Mouse.OverrideCursor = null;
Log.Error("Error occured. " + ex);
// Get the innermost exception message and show message box
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
CustomMessageBox.ShowDialog(ex.Message, "Error", "OK");
return;
return false;
}
this.UpdateView(DistributedPolicyEngine.PatchStatus.Rejected);
return true;
}
/// <summary>
/// Rejects this patch.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private async void RejectPatch(object parameter)
{
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity())))
{
this.UpdateView(DistributedPolicyEngine.PatchStatus.Rejected);
}
}
/// <summary>
......@@ -225,24 +259,10 @@ namespace pEp.UI.ViewModels
/// <param name="parameter">The command parameter.</param>
private async void SupportPatch(object parameter)
{
try
{
await Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, new PEPIdentity());
}
catch (Exception ex)
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, new PEPIdentity())))
{
Log.Error("SupportPatch: Error occured. " + ex);
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
CustomMessageBox.ShowDialog(ex.Message, "Error", "OK");
return;
this.UpdateView(DistributedPolicyEngine.PatchStatus.Supported);
}
this.UpdateView(DistributedPolicyEngine.PatchStatus.Supported);
}
/// <summary>
......
......@@ -214,7 +214,7 @@ namespace pEp.UI.ViewModels
{
if (this._CommandButtonNewPatch == null)
{
this._CommandButtonNewPatch = new RelayCommand(p => PatchDialog.ShowDialog(PatchDialog.PatchAction.NewPatch, new Patch(), true));
this._CommandButtonNewPatch = new RelayCommand(async p => await PatchDialog.ShowDialog(PatchDialog.PatchAction.NewPatch, new Patch(), true));
}
return this._CommandButtonNewPatch;
......@@ -624,11 +624,6 @@ namespace pEp.UI.ViewModels
}
}
/// <summary>
/// Gets the collection of existing patches.
/// </summary>
public ObservableCollection<PatchViewModel> Patches { get; } = new ObservableCollection<PatchViewModel>();
/// <summary>
/// Gets or sets the pEp copyright used in the about information.
/// </summary>
......
using pEp.DPE;
using pEp.UI.Models;
using System;
using System.Linq;
using System.Windows.Media;
namespace pEp.UI.ViewModels
{
internal class PatchViewModel
{
private Patch patch;
private PEPIdentity _Me = null;
/// <summary>
/// Gets the background color for this patch.
/// </summary>
public Brush Background => this.IsEditable ? Brushes.White : Brushes.WhiteSmoke;
/// <summary>
/// Gets the foreground color for this patch.
/// </summary>
public Brush Foreground => this.IsEditable ? Brushes.Black : Brushes.Gray;
/// <summary>
/// Gets the commit message of this patch.
/// </summary>
public string CommitMessage => this.patch?.CommitMessage;
/// <summary>
/// Gets whether the patch is editable.
/// </summary>
public bool IsEditable { get; }
/// <summary>
/// Gets a string stating the patch's last update.
/// </summary>
public string LastUpdate => this.GetLastUpdateString();
/// <summary>
/// Gets the own identity to use to support/reject patches.
/// </summary>
public PEPIdentity Me
{
get
{
if ((this._Me == null) &&
(PEPIdentity.GetOwnIdentity(Globals.ThisAddIn.Settings.AccountSettingsList.First(),
out PEPIdentity me,
true) == Globals.ReturnStatus.Success))
{
this._Me = me;
}
return this._Me;
}
}
/// <summary>
/// Primar constructor.
/// </summary>
/// <param name="patch">The patch that this view model is based on.</param>
public PatchViewModel(Patch patch, bool isEditable)
{
this.patch = patch;
this.IsEditable = isEditable;
}
/// <summary>
/// Gets a string referencing the patch's last update.
/// </summary>
/// <returns>A string describing the last update.</returns>
private string GetLastUpdateString()
{
TimeSpan elapsedTime = DateTime.UtcNow - (this.patch?.CreationDate ?? DateTime.UtcNow);
if (elapsedTime.Days > 0)
{
return (elapsedTime.Days == 1) ? "1 day ago" : $"{elapsedTime.Days} days ago";
}
else if (elapsedTime.Hours > 0)
{
return (elapsedTime.Hours == 1) ? "1 hour ago" : $"{elapsedTime.Hours} hours ago";
}
else if (elapsedTime.Minutes > 0)
{
return (elapsedTime.Minutes == 1) ? "1 minute ago" : $"{elapsedTime.Minutes} minutes ago";
}
else
{
return "just now";
}
}
/// <summary>
/// Opens the patch dialog.
/// </summary>
/// <returns>The dialog result.</returns>
public System.Windows.Forms.DialogResult OpenPatchDialog(PatchDialog.PatchAction patchAction)
{
return PatchDialog.ShowDialog(patchAction, this.patch, this.IsEditable);
}
/// <summary>
/// Supports the patch.
/// </summary>
public void SupportPatch()
{
Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, this.Me);
}
/// <summary>
/// Rejects the patch.
/// </summary>
public void RejectPatch()
{
Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, this.Me);
}
}
}
......@@ -41,6 +41,16 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
Grid.RowSpan="8"
Grid.ZIndex="100"
Fill="White"
Opacity="0.6"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}" />
<Label Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
......
......@@ -29,17 +29,5 @@ namespace pEp.UI.Views
Process.Start(e.Uri.AbsoluteUri);
e.Handled = true;
}
/// <summary>
/// Event handler for when a patch is double-clicked.
/// </summary>
private void ContentControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if ((sender as ContentControl)?.DataContext is PatchViewModel patchViewModel)
{
PatchDialog.PatchAction action = patchViewModel.IsEditable ? PatchDialog.PatchAction.EditPatch : PatchDialog.PatchAction.SupportOrRejectPatch;
patchViewModel.OpenPatchDialog(action);
}
}
}
}
......@@ -448,7 +448,6 @@
<Compile Include="UI\ViewModels\InputMessageBoxViewModel.cs" />
<Compile Include="UI\ViewModels\MessageBoxBaseViewModel.cs" />
<Compile Include="UI\ViewModels\PatchDialogViewModel.cs" />
<Compile Include="UI\ViewModels\PatchViewModel.cs" />
<Compile Include="UI\Views\CustomMessageBoxView.xaml.cs">
<DependentUpon>CustomMessageBoxView.xaml</DependentUpon>
</Compile>
......