Skip to content

Commit

Permalink
Merge pull request #103 from simon-brooke/develop
Browse files Browse the repository at this point in the history
Release candidate based on build 3.0.21.46
  • Loading branch information
simon-brooke authored Aug 29, 2019
2 parents 7dbfe0b + 16d6e75 commit 9c2c234
Show file tree
Hide file tree
Showing 28 changed files with 1,247 additions and 648 deletions.
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PROJECT_NAME = "SuiteCRM Outlook Add-in"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = 3.0.20.0
PROJECT_NUMBER = 3.0.21.46

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### What's in this repository

SuiteCRM Outlook Plug-In v 3.0.20.0
SuiteCRM Outlook Plug-In v 3.0.21.46

This repository has been created to allow community members to collaborate and contribute to the project.

Expand Down
32 changes: 32 additions & 0 deletions SuiteCRMAddIn/BusinessLogic/AppointmentSyncState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace SuiteCRMAddIn.BusinessLogic
using System.Runtime.InteropServices;
using SuiteCRMClient.Logging;
using SuiteCRMClient;
using Exceptions;

public abstract class AppointmentSyncState: SyncState<Outlook.AppointmentItem>
{
Expand Down Expand Up @@ -103,6 +104,37 @@ public override string Description
public override Outlook.UserProperties OutlookUserProperties =>
OutlookItem != null && OutlookItem.IsValid() ? OutlookItem.UserProperties : null;

/// <summary>
/// #6034: occasionally we get spurious ItemChange events where the
/// value of Duration appear as zero, although nothing has occured to
/// make this change. This is a hack around the problem while we try
/// to understand it better.
/// </summary>
/// <returns>false if duration was set to zero; as a side effect,
/// resets Duration to its last known good value.</returns>
internal override bool ShouldPerformSyncNow()
{
bool result;

try
{
result = base.ShouldPerformSyncNow();
}
catch (DurationSetToZeroException dz)
{
Outlook.AppointmentItem appt = (this.OutlookItem as Outlook.AppointmentItem);

if (appt != null)
{
appt.Duration = dz.Duration;
}

result = false;
}

return result;
}

public override void DeleteItem()
{
this.OutlookItem.Delete();
Expand Down
13 changes: 8 additions & 5 deletions SuiteCRMAddIn/BusinessLogic/AppointmentsSynchroniser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,16 +462,19 @@ internal override CrmId AddOrUpdateItemFromOutlookToCrm(SyncState<AppointmentIte
{
result = base.AddOrUpdateItemFromOutlookToCrm(syncState);

if (CrmId.IsInvalid(result))
Log.Warn(
"AppointmentSyncing.AddOrUpdateItemFromOutlookToCrm: Invalid CRM Id returned; item may not have been stored.");
else if (CrmId.IsValid(olItem.GetCrmId()))
if (CrmId.IsValid(result) && CrmId.IsValid(olItem.GetCrmId()))
{
if (syncState is CallSyncState)
{
SetCrmRelationshipFromOutlook(Globals.ThisAddIn.CallsSynchroniser, result, "Users",
CrmId.Get(RestAPIWrapper.GetUserId()));
}
else
{
SetCrmRelationshipFromOutlook(Globals.ThisAddIn.MeetingsSynchroniser, result, "Users",
CrmId.Get(RestAPIWrapper.GetUserId()));
}
}
}
else
{
Expand Down Expand Up @@ -543,7 +546,7 @@ protected override void LinkOutlookItems(MAPIFolder appointmentsFolder)
{
var olPropertyModified = olItem.UserProperties[SyncStateManager.ModifiedDatePropertyName];
var olPropertyType = olItem.UserProperties[SyncStateManager.TypePropertyName];
var olPropertyEntryId = olItem.UserProperties[SyncStateManager.CrmIdPropertyName];
var olPropertyEntryId = olItem.GetCrmId();
if (olPropertyModified != null &&
olPropertyType != null &&
olPropertyEntryId != null)
Expand Down
9 changes: 4 additions & 5 deletions SuiteCRMAddIn/BusinessLogic/MeetingSyncState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace SuiteCRMAddIn.BusinessLogic
using SuiteCRMClient.RESTObjects;
using SuiteCRMClient.Logging;
using SuiteCRMClient;
using Exceptions;

/// <summary>
/// A sync state which syncs an appointment which is a meeting.
Expand Down Expand Up @@ -76,21 +77,19 @@ internal override bool ReallyChanged()
{
bool result = base.ReallyChanged();

#if DEBUG
if (result)
{
var cached = this.Cache as ProtoAppointment<MeetingSyncState>;
var current = this.CreateProtoItem() as ProtoAppointment<MeetingSyncState>;

if (cached.Duration != current.Duration && current.Duration == 0)
if (cached != null && cached.Duration != current.Duration && current.Duration == 0)
{
Globals.ThisAddIn.Log.Warn(
$"Meeting id {this.OutlookItemEntryId} (CRM id {this.CrmEntryId}) changed to zero duration");
throw new DurationSetToZeroException(cached.Duration);
}
}
#endif

if (!result)
else
{
var cacheValues = this.Cache as ProtoAppointment<MeetingSyncState>;

Expand Down
35 changes: 34 additions & 1 deletion SuiteCRMAddIn/BusinessLogic/SyncStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace SuiteCRMAddIn.BusinessLogic
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Outlook = Microsoft.Office.Interop.Outlook;

Expand All @@ -61,7 +62,39 @@ public class SyncStateManager
/// The name of the CRM ID synchronisation property.
/// </summary>
/// <see cref="SuiteCRMAddIn.Extensions.MailItemExtensions.CrmIdPropertyName"/>
public const string CrmIdPropertyName = "SEntryID";
public static string CrmIdPropertyName => string.IsNullOrWhiteSpace(Properties.Settings.Default.CurrentCrmIdPropertyName)? ConstructAndSetCrmEntryIdPropertyName() : Properties.Settings.Default.CurrentCrmIdPropertyName;

/// <summary>
/// Construct a new name for the CRM id property based on the hist url,
/// set it in settings, and return it.
/// </summary>
/// <remarks>
/// <para>#6661: when we change the CRM URL, we also need to change the name
/// of the property on which the CRM Id is held, in order neither to
/// use the wrong CRM ids, nor to spend a lot of time clearing old ones.</para>
/// <para>This is a private method, which cannot be called directly from
/// outside this class. To force a reset of the property name, set the setting
/// value to null or <see cref="string.Empty"/>.</para>
/// </remarks>
/// <seealso cref="Properties.Settings.Default.CurrentCrmIdPropertyName"/>
/// <returns>The value set.</returns>
private static string ConstructAndSetCrmEntryIdPropertyName()
{
string previous = string.IsNullOrWhiteSpace(Properties.Settings.Default.CurrentCrmIdPropertyName) ?
LegacyCrmIdPropertyName :
Properties.Settings.Default.CurrentCrmIdPropertyName;
byte[] bytes = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(Properties.Settings.Default.Host));

string result = $"CrmId{BitConverter.ToString(bytes)}".Replace("-", string.Empty);
Properties.Settings.Default.CurrentCrmIdPropertyName = result;
Properties.Settings.Default.Save();

Globals.ThisAddIn.Log.Info($"Updated CRM Id property name from {previous} to {result}");

return result;
}

public const string LegacyCrmIdPropertyName = "SEntryID";

/// <summary>
/// If set, don't sync with CRM.
Expand Down
27 changes: 20 additions & 7 deletions SuiteCRMAddIn/BusinessLogic/SyncState{1}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,24 @@ public SyncState(ItemType item, string itemId, CrmId crmId, DateTime modifiedDat
}

/// <remarks>
/// The state transition engine.
/// The state transition engine. If we're building a DEBUG build, log all state transitions;
/// OBVIOUSLY, the semantics of this, apart from the side effect, must be identical between
/// DEBUG and non DEBUG builds.
/// </remarks>
#if DEBUG
private TransmissionState ts = TransmissionState.NewFromOutlook;
internal TransmissionState TxState
{
get { return this.ts; }
private set {
Globals.ThisAddIn.Log.Debug(
$"{this.GetType().Name} '{this.Cache?.Description}': transition {this.ts} => {value}");
this.ts = value;
}
}
#else
internal TransmissionState TxState { get; private set; } = TransmissionState.NewFromOutlook;
#endif


/// <summary>
Expand Down Expand Up @@ -227,11 +242,13 @@ public override bool IsDeletedInOutlook
/// </remarks>
/// <returns>True if this item should be synced with CRM, there has been a real change,
/// and some time has elapsed.</returns>
internal bool ShouldPerformSyncNow()
internal virtual bool ShouldPerformSyncNow()
{
DateTime utcNow = DateTime.UtcNow;
double modifiedSinceSeconds = Math.Abs((utcNow - OModifiedDate).TotalSeconds);
ILogger log = Globals.ThisAddIn.Log;

bool result;
bool reallyChanged = this.ReallyChanged();
bool isSyncable = this.ShouldSyncWithCrm;
string prefix = $"SyncState.ShouldPerformSyncNow: {this.CrmType} {this.CrmEntryId}";
Expand All @@ -240,8 +257,6 @@ internal bool ShouldPerformSyncNow()
log.Debug(isSyncable ? $"{prefix} is syncable." : $"{ prefix} is not syncable.");
log.Debug(IsManualOverride ? $"{prefix} is on manual override." : $"{prefix} is not on manual override.");

bool result;

lock (this.txStateLock)
{
/* result is set within the lock to prevent one thread capturing another thread's
Expand Down Expand Up @@ -328,7 +343,7 @@ internal void SetPending(bool iSwearThatTransmissionHasFailed = false)
case TransmissionState.PresentAtStartup:
/* any new item may, and often will, be set to 'Pending'. */
case TransmissionState.Pending:
/* If 'Pending', may remain 'Pending'. */
/* If 'Pending', may remain 'Pending'. */
case TransmissionState.Queued:
/* If it's in the queue and is modified, it's probably best to
* go back to pending, because other modifications are likely */
Expand Down Expand Up @@ -513,7 +528,6 @@ internal bool MayBeUpdatedFromCRM()

private void LogAndSetTxState(TransmissionState newState)
{
#if DEBUG
try
{
if (this.Cache == null)
Expand All @@ -527,7 +541,6 @@ private void LogAndSetTxState(TransmissionState newState)
{
// ignore. It doesn't matter. Although TODO: I'd love to know what happens.
}
#endif
this.TxState = newState;
}
}
Expand Down
105 changes: 59 additions & 46 deletions SuiteCRMAddIn/BusinessLogic/Synchroniser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -936,59 +936,72 @@ protected void RemoveItemSyncState(SyncState<OutlookItemType> item)
/// <param name="itemsToResolve">The list of items to resolve.</param>
protected virtual void ResolveUnmatchedItems(IEnumerable<SyncState<OutlookItemType>> itemsToResolve)
{
foreach (var unresolved in itemsToResolve)
switch (unresolved.TxState)
{
case TransmissionState.PendingDeletion:
/* If it's to resolve and marked pending deletion, we delete it
* (unresolved on two successive iterations): */
RemoveItemAndSyncState(unresolved);
break;

case TransmissionState.Synced:
if (unresolved.ExistedInCrm)
unresolved.SetPendingDeletion();
break;

case TransmissionState.Pending:
case TransmissionState.PresentAtStartup:
if (unresolved.ShouldSyncWithCrm)
try
{
/* if it's unresolved, pending, and should be synced send it. */
unresolved.SetQueued();
AddOrUpdateItemFromOutlookToCrm(unresolved);
}
catch (BadStateTransition)
{
// ignore.
}
break;

case TransmissionState.Queued:
if (unresolved.ShouldSyncWithCrm)
try
{
foreach (var unresolved in itemsToResolve)
switch (unresolved.TxState)
{
case TransmissionState.PendingDeletion:
/* If it's to resolve and marked pending deletion, we delete it
* (unresolved on two successive iterations): */
RemoveItemAndSyncState(unresolved);
break;

case TransmissionState.Synced:
if (unresolved.ExistedInCrm)
unresolved.SetPendingDeletion();
break;

case TransmissionState.Pending:
case TransmissionState.PresentAtStartup:
if (unresolved.ShouldSyncWithCrm)
try
{
/* if it's unresolved, pending, and should be synced send it. */
unresolved.SetQueued();
AddOrUpdateItemFromOutlookToCrm(unresolved);
}
catch (BadStateTransition)
{
// ignore.
}
break;

case TransmissionState.Queued:
if (unresolved.ShouldSyncWithCrm)
try
{
/* if it's queued and should be synced send it. */
AddOrUpdateItemFromOutlookToCrm(unresolved);
}
catch (BadStateTransition bst)
{
ErrorHandler.Handle($"Failure while seeking to resolve unmatched items", bst);
}
break;

default:
try
{
/* if it's queued and should be synced send it. */
AddOrUpdateItemFromOutlookToCrm(unresolved);
unresolved.SetPending();
}
catch (BadStateTransition bst)
{
ErrorHandler.Handle($"Failure while seeking to resolve unmatched items", bst);
if (bst.From != TransmissionState.Transmitted)
ErrorHandler.Handle($"Failure while seeking to resolve unmatched items", bst);
}
break;

default:
unresolved.SetPending();
break;
}

foreach (SyncState resolved in SyncStateManager.Instance.GetSynchronisedItems<SyncStateType>()
.Where(s => s.TxState == TransmissionState.PendingDeletion &&
!itemsToResolve.Contains(s)))
/* finally, if there exists an item which had been marked pending deletion, but it has
break;
}
}
finally
{
foreach (SyncState resolved in SyncStateManager.Instance.GetSynchronisedItems<SyncStateType>()
.Where(s => s.TxState == TransmissionState.PendingDeletion &&
!itemsToResolve.Contains(s)))
/* finally, if there exists an item which had been marked pending deletion, but it has
* been found in CRM (i.e. not in unresolved), mark it as synced */
((SyncState<OutlookItemType>) resolved).SetSynced();
((SyncState<OutlookItemType>)resolved).SetSynced();
}
}

/// <summary>
Expand Down
Loading

0 comments on commit 9c2c234

Please sign in to comment.