/// <summary> /// Sends the asynchronous. /// </summary> /// <param name="rockMessage">The rock message.</param> /// <param name="mediumEntityTypeId">The medium entity type identifier.</param> /// <param name="mediumAttributes">The medium attributes.</param> /// <returns></returns> /// <exception cref="System.NotImplementedException"></exception> public async Task <SendMessageResult> SendAsync(RockMessage rockMessage, int mediumEntityTypeId, Dictionary <string, string> mediumAttributes) { var sendMessageResult = new SendMessageResult(); var smsMessage = rockMessage as RockSMSMessage; if (smsMessage != null) { // Validate From Number if (smsMessage.FromNumber == null) { sendMessageResult.Errors.Add("A From Number was not provided."); return(sendMessageResult); } string accountSid = GetAttributeValue(TwilioAttributeKey.Sid); string authToken = GetAttributeValue(TwilioAttributeKey.AuthToken); TwilioClient.Init(accountSid, authToken); // Common Merge Field var mergeFields = Lava.LavaHelper.GetCommonMergeFields(null, rockMessage.CurrentPerson); foreach (var mergeField in rockMessage.AdditionalMergeFields) { mergeFields.AddOrReplace(mergeField.Key, mergeField.Value); } int?throttlingWaitTimeMS = null; if (this.IsLongCodePhoneNumber(smsMessage.FromNumber.Value)) { throttlingWaitTimeMS = this.GetAttributeValue(TwilioAttributeKey.LongCodeThrottling).AsIntegerOrNull(); } List <Uri> attachmentMediaUrls = GetAttachmentMediaUrls(rockMessage.Attachments.AsQueryable()); if (throttlingWaitTimeMS.HasValue) { foreach (var recipient in rockMessage.GetRecipients()) { var result = await SendToRecipientAsync(recipient, mergeFields, smsMessage, attachmentMediaUrls, mediumEntityTypeId, mediumAttributes).ConfigureAwait(false); sendMessageResult.Errors.AddRange(result.Errors); sendMessageResult.Errors.AddRange(result.Warnings); sendMessageResult.MessagesSent += result.MessagesSent; await Task.Delay(throttlingWaitTimeMS.Value).ConfigureAwait(false); } } else { var sendingTask = new List <Task <SendMessageResult> >(); using (var mutex = new SemaphoreSlim(MaxParallelization)) { foreach (var recipient in rockMessage.GetRecipients()) { var startMutexWait = System.Diagnostics.Stopwatch.StartNew(); await mutex.WaitAsync().ConfigureAwait(false); sendingTask.Add(ThrottleHelper.ThrottledExecute(() => SendToRecipientAsync(recipient, mergeFields, smsMessage, attachmentMediaUrls, mediumEntityTypeId, mediumAttributes), mutex)); } /* * Now that we have fired off all of the task, we need to wait for them to complete, get their results, * and then process that result. Once all of the task have been completed we can continue. */ while (sendingTask.Count > 0) { var completedTask = await Task.WhenAny(sendingTask).ConfigureAwait(false); sendingTask.Remove(completedTask); var result = await completedTask.ConfigureAwait(false); sendMessageResult.Errors.AddRange(result.Errors); sendMessageResult.Errors.AddRange(result.Warnings); sendMessageResult.MessagesSent += result.MessagesSent; } } } } return(sendMessageResult); }
/// <summary> /// Sends the asynchronous. /// </summary> /// <param name="communication">The communication.</param> /// <param name="mediumEntityTypeId">The medium entity type identifier.</param> /// <param name="mediumAttributes">The medium attributes.</param> /// <returns></returns> /// <exception cref="System.NotImplementedException"></exception> public async Task SendAsync(Model.Communication communication, int mediumEntityTypeId, Dictionary <string, string> mediumAttributes) { var fromPhone = string.Empty; var unprocessedRecipientCount = 0; var mergeFields = new Dictionary <string, object>(); Person currentPerson = null; var attachmentMediaUrls = new List <Uri>(); var personEntityTypeId = 0; var communicationCategoryId = 0; var communicationEntityTypeId = 0; using (var rockContext = new RockContext()) { // Requery the Communication communication = new CommunicationService(rockContext).Get(communication.Id); if (communication != null && communication.Status == Model.CommunicationStatus.Approved && (!communication.FutureSendDateTime.HasValue || communication.FutureSendDateTime.Value.CompareTo(RockDateTime.Now) <= 0)) { var qryRecipients = new CommunicationRecipientService(rockContext).Queryable(); unprocessedRecipientCount = qryRecipients .Where(r => r.CommunicationId == communication.Id && r.Status == Model.CommunicationRecipientStatus.Pending && r.MediumEntityTypeId.HasValue && r.MediumEntityTypeId.Value == mediumEntityTypeId) .Count(); } if (unprocessedRecipientCount == 0) { return; } fromPhone = communication.SMSFromDefinedValue?.Value; if (string.IsNullOrWhiteSpace(fromPhone)) { // just in case we got this far without a From Number, throw an exception throw new Exception("A From Number was not provided for communication: " + communication.Id.ToString()); } currentPerson = communication.CreatedByPersonAlias?.Person; mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null, currentPerson); personEntityTypeId = EntityTypeCache.Get <Person>().Id; communicationEntityTypeId = EntityTypeCache.Get <Model.Communication>().Id; communicationCategoryId = CategoryCache.Get(SystemGuid.Category.HISTORY_PERSON_COMMUNICATIONS.AsGuid(), rockContext).Id; var smsAttachmentsBinaryFileIdList = communication.GetAttachmentBinaryFileIds(CommunicationType.SMS); if (smsAttachmentsBinaryFileIdList.Any()) { attachmentMediaUrls = this.GetAttachmentMediaUrls(new BinaryFileService(rockContext).GetByIds(smsAttachmentsBinaryFileIdList)); } } int?throttlingWaitTimeMS = null; if (this.IsLongCodePhoneNumber(fromPhone)) { throttlingWaitTimeMS = GetAttributeValue(TwilioAttributeKey.LongCodeThrottling).AsIntegerOrNull(); } var globalAttributes = GlobalAttributesCache.Get(); string publicAppRoot = globalAttributes.GetValue("PublicApplicationRoot"); var callbackUrl = publicAppRoot + "Webhooks/Twilio.ashx"; var accountSid = GetAttributeValue(TwilioAttributeKey.Sid); var authToken = GetAttributeValue(TwilioAttributeKey.AuthToken); TwilioClient.Init(accountSid, authToken); if (throttlingWaitTimeMS.HasValue) { // If throttlingWaitTime has a value we need to send all text synchronously so that the throttle is respected. var recipientFound = true; while (recipientFound) { // make a new rockContext per recipient var recipient = GetNextPending(communication.Id, mediumEntityTypeId, communication.IsBulkCommunication); // This means we are done, break the loop if (recipient == null) { recipientFound = false; continue; } await SendToCommunicationRecipient(communication, fromPhone, mergeFields, currentPerson, attachmentMediaUrls, personEntityTypeId, communicationCategoryId, communicationEntityTypeId, publicAppRoot, callbackUrl, recipient).ConfigureAwait(false); await Task.Delay(throttlingWaitTimeMS.Value).ConfigureAwait(false); } } else { var sendingTask = new List <Task>(unprocessedRecipientCount); var asyncTransport = this as IAsyncTransport; var maxParallelization = asyncTransport?.MaxParallelization ?? 10; using (var mutex = new SemaphoreSlim(maxParallelization)) { var recipientFound = true; while (recipientFound) { // make a new rockContext per recipient var recipient = GetNextPending(communication.Id, mediumEntityTypeId, communication.IsBulkCommunication); // This means we are done, break the loop if (recipient == null) { recipientFound = false; continue; } await mutex.WaitAsync().ConfigureAwait(false); sendingTask.Add(ThrottleHelper.ThrottledExecute(() => SendToCommunicationRecipient(communication, fromPhone, mergeFields, currentPerson, attachmentMediaUrls, personEntityTypeId, communicationCategoryId, communicationEntityTypeId, publicAppRoot, callbackUrl, recipient), mutex)); } /* * Now that we have fired off all of the task, we need to wait for them to complete. * Once all of the task have been completed we can continue. */ while (sendingTask.Count > 0) { var completedTask = await Task.WhenAny(sendingTask).ConfigureAwait(false); sendingTask.Remove(completedTask); } } } }