private static void PrepareTicketEventNotificationForDelivery(TicketDataDataContext ctx, TicketEventNotification note, List <TicketEventNotification> consolidations) { string resendDelaySetting = ConfigurationManager.AppSettings["EmailResendDelayMinutes"]; double resendDelay = 5D; if (!string.IsNullOrEmpty(resendDelaySetting)) { resendDelay = Convert.ToDouble(resendDelay); } string maxRetriesSetting = ConfigurationManager.AppSettings["EmailMaxDeliveryAttempts"]; int maxRetries = 5; if (!string.IsNullOrEmpty(maxRetriesSetting)) { maxRetries = Convert.ToInt32(maxRetriesSetting); } note.DeliveryAttempts += 1; if (note.DeliveryAttempts < maxRetries) { note.Status = "pending"; note.NextDeliveryAttemptDate = DateTime.Now.AddMinutes(resendDelay * note.DeliveryAttempts); int backTrackSeconds = -1; foreach (var consolidateNote in consolidations.OrderByDescending(c => c.CommentId)) { //this will reorder the next delivery attempt date for consolidations in descending order keeping the relative order for them all consolidateNote.NextDeliveryAttemptDate = note.NextDeliveryAttemptDate.Value.AddSeconds(backTrackSeconds); consolidateNote.DeliveryAttempts += 1; backTrackSeconds--; } } else { note.Status = "final-attempt"; note.NextDeliveryAttemptDate = null; foreach (var consolidateNote in consolidations) { consolidateNote.NextDeliveryAttemptDate = null; consolidateNote.DeliveryAttempts += 1; } } ctx.SubmitChanges();//submit changes before email attempt... this way if there is an unhandled failure, the ticket will still have incremented the delivery attempt and next retry values (prevents accidental spam) /* Unhandled edge case: * We've set the next delivery date for the note we are sending to a future date in case something seriously goes wrong during sending the email. * This is important because if the sending routine exits and we hadn't committed a next delivery date and all that, it would potentially cause * an inifite retry-loop. * * The edge case is that new notes could have been queued after we read the queue from the DB into memory. Those notes would have a delivery * date much lower than the retry we just scheduled for this note. We could try to predict and work around this potential case, but it is * such an extreme edge case (something has to queue AND an unhandled exception has to happen in the sending routine both). * * By not handling this here, the worst that can happen is that the new queued note will simply cause a retry far sooner than we wanted. * A work-around for this case has a significant perforance impact though, and so we are not going to implement that here... besides, if * there is an unhandled exception in the send routine it will likely be something so severe that future send attemps are all going to fail * for this user's notes indefinitly until the issue is debugged or a major data corruption issue is fixed anyway. */ }
private void DeleteTicketAttachment(int fileId) { TicketDataDataContext ctx = new TicketDataDataContext(); var file = ctx.TicketAttachments.SingleOrDefault(ta => ta.FileId == fileId); ctx.TicketAttachments.DeleteOnSubmit(file); ctx.SubmitChanges(); }
private static void SendTicketEventNotificationEmail(TicketDataDataContext ctx, TicketEventNotification note, List <TicketEventNotification> consolidations) { sendingLock = new object(); lock (sendingLock) { PrepareTicketEventNotificationForDelivery(ctx, note, consolidations); DeliverTicketEventNotificationEmail(ctx, note, consolidations); } }
protected void TicketsLinqDataSource_Selecting(object sender, LinqDataSourceSelectEventArgs e) { TicketDataDataContext ctx = new TicketDataDataContext(); var q = from t in ctx.Tickets select t; var qfs = q.ApplyListViewSettings(ListSettings, 0); e.Result = qfs; }
public string[] GetTagCompletionList(string prefixText, int count) { List <string> returnList = new List <string>(); if (count == 0) { count = 10; } string[] tags = prefixText.Replace(" ,", ",").Replace(", ", ",").Split(','); //eliminate extra spaces around commas before split string textToCheck = tags[tags.Length - 1]; //last element if (textToCheck.Trim().Length > 1) //only check if user has typed more than 1 character for the last item of the taglist { string fixedText = string.Empty; //all that stuff the user typed before the last comma in the text if (tags.Length > 1) { StringBuilder sb = new StringBuilder(); for (int x = 0; x < tags.Length - 1; x++) { sb.Append(tags[x] + ","); } fixedText = sb.ToString(); } TicketDataDataContext context = new TicketDataDataContext(); var distinctTagsQuery = ( from tag in context.TicketTags orderby tag.TagName where tag.TagName.StartsWith(textToCheck) select tag.TagName).Distinct().Take(count); foreach (var distinctTag in distinctTagsQuery) { if (tags.Count(t => t.ToUpperInvariant() == distinctTag.ToUpperInvariant()) < 1) //eliminate any tags that were already used (that are in the fixedText). { returnList.Add(fixedText + distinctTag); //append the other items in the list plus the new tag possibilitiy } } } return(returnList.ToArray()); }
protected void CreateTicketButton_Click(object sender, EventArgs e) { if (Page.IsValid) { TicketDataDataContext context = new TicketDataDataContext(); Ticket ticket = NewTicketForm.GetNewTicket(); if (ticket != null) { //if there is an existing ticket in DB with "New" status and the same title don't submit... this is probably a duplicate submission. if (context.Tickets.Count(t => (t.Title == ticket.Title && t.CurrentStatus == "New")) < 1) { context.Tickets.InsertOnSubmit(ticket); context.SubmitChanges(); NotificationService.QueueTicketEventNotification(ticket.TicketComments[0]); Page.Response.Redirect(string.Format("ViewTicket.aspx?id={0}", ticket.TicketId), true); } else { MessageLabel.Text = "Failed to create ticket. Another ticket was recently created with the same title."; } } MessageLabel.Text = "Unable to create ticket."; } }
/// <summary> /// Queues the ticket event notification. /// </summary> /// <param name="comment">The comment associated with the event.</param> public static void QueueTicketEventNotification(TicketComment comment) { lock (registerLock) { string enabled = ConfigurationManager.AppSettings["EnableEmailNotifications"]; if (!string.IsNullOrEmpty(enabled) && Convert.ToBoolean(enabled)) { TicketDataDataContext ctx = new TicketDataDataContext(); var ticket = comment.Ticket; var newNotes = ticket.CreateTicketEventNotificationsForComment(comment.CommentId, comment.CommentedBy); foreach (var note in newNotes) { note.CreatedDate = DateTime.Now; note.DeliveryAttempts = 0; if (note.NotifyEmail == "invalid")//notes with invalid email still added to table, but squash delivery schedule. { note.Status = "invalid"; note.NextDeliveryAttemptDate = null; note.LastDeliveryAttemptDate = DateTime.Now; } else { note.Status = "queued"; var now = DateTime.Now; note.NextDeliveryAttemptDate = now; if (note.NotifyUserReason != "HelpDesk")// for non-broadcasts to helpdesk schedule on the delay { string delay = ConfigurationManager.AppSettings["EmailNotificationInitialDelayMinutes"]; if (!string.IsNullOrEmpty(delay)) { note.NextDeliveryAttemptDate = now.AddMinutes(Convert.ToDouble(delay)); } } } } ctx.TicketEventNotifications.InsertAllOnSubmit(newNotes); ctx.SubmitChanges(); //Deliver helpdesk broadcasts NOW! New ticket notifications for help desk only // happen for brand-new tickets. These users aren't included on further changes // to the ticket either so there is no reason to wait for consolidations. // // Since the creation of notes ensures that tickets with a reason of "HelpDesk" // are only for tickets where the recipient is not the owner/assigned user we // don't have to worry about suppressions or consolidations; we can directly // send without worrying about the pre-processing that happens with timer // triggered mechanism. foreach (var note in newNotes) { if (note.NotifyUserReason == "HelpDesk" && note.NextDeliveryAttemptDate != null) { SendTicketEventNotificationEmail(ctx, note, new List <TicketEventNotification>()); } } } } }
private static void DeliverTicketEventNotificationEmail(TicketDataDataContext ctx, TicketEventNotification note, List <TicketEventNotification> consolidations) { bool status = false; try { //TODO: Modify body generation to flag comments for the current event and all consolidations. TicketComment comment = note.TicketComment; Ticket ticket = comment.Ticket; SmtpClient client = new SmtpClient(); string url = null; string rootUrl = ConfigurationManager.AppSettings["WebRootUrlForEmailLinks"]; if (!string.IsNullOrEmpty(rootUrl)) { if (!rootUrl.EndsWith("/")) { rootUrl += "/"; } url = string.Format("{0}ViewTicket.aspx?id={1}", rootUrl, ticket.TicketId.ToString()); } int minComment = note.CommentId; if (consolidations.Count() > 0) { minComment = consolidations.Min(c => c.CommentId); } string body = NotificationUtilities.GetHTMLBody(ticket, url, note.NotifyUser, minComment); string displayFrom = ConfigurationManager.AppSettings["FromEmailDisplayName"]; string addressFrom = ConfigurationManager.AppSettings["FromEmailAddress"]; string blindCopyTo = ConfigurationManager.AppSettings["BlindCopyToEmailAddress"]; MailAddress bccAddr = null; if (!string.IsNullOrEmpty(blindCopyTo)) { bccAddr = new MailAddress(blindCopyTo); } MailAddress fromAddr = new MailAddress(addressFrom, displayFrom); string tStat = ticket.CurrentStatus; if (string.IsNullOrEmpty(ticket.AssignedTo)) { tStat = (ticket.TicketComments.Count() < 2)? "New" : "Unassigned"; } string subject = string.Format("Ticket {0} ({1}): {2}", ticket.TicketId.ToString(), tStat, ticket.Title); MailAddress toAddr = new MailAddress(note.NotifyEmail, note.NotifyUserDisplayName); MailMessage msg = new MailMessage(fromAddr, toAddr); if (bccAddr != null) { msg.Bcc.Add(bccAddr); } msg.Subject = subject; msg.Body = body; //body; msg.IsBodyHtml = true; client.Send(msg); status = true; } catch (Exception ex) { Exception newEx = new ApplicationException("Failure in Email Delivery Timer", ex); HttpContext mockContext = (new TicketDesk.Engine.MockHttpContext(false)).Context; Elmah.ErrorLog.GetDefault(mockContext).Log(new Elmah.Error(newEx)); } DateTime now = DateTime.Now; note.LastDeliveryAttemptDate = now; foreach (var consolidateNote in consolidations) { consolidateNote.LastDeliveryAttemptDate = now; } if (!status) { if (note.Status == "final-attempt") { note.Status = "failed"; foreach (var consolidateNote in consolidations) { consolidateNote.Status = "consolidated-failed"; } } if (note.Status == "pending") { note.Status = "queued"; } //check for notes that queued after the send process first started here, and increment their nextDeliveryDate so they don't cause // resends to happen sooner than the next delivery retry interval warrants. TicketDataDataContext ctxNew = new TicketDataDataContext();//existing context will have cached data from the past read. Do this on new context var newNotes = from tn in ctxNew.TicketEventNotifications where tn.CommentId > note.CommentId && tn.NextDeliveryAttemptDate <= note.NextDeliveryAttemptDate select tn; if (newNotes.Count() > 0) { int advanceSeconds = 1; foreach (var newNote in newNotes) { newNote.NextDeliveryAttemptDate = note.NextDeliveryAttemptDate.Value.AddSeconds(advanceSeconds); advanceSeconds++; } ctxNew.SubmitChanges(); } } else { note.Status = "sent"; note.NextDeliveryAttemptDate = null; foreach (var consolidateNote in consolidations) { consolidateNote.Status = "consolidated"; consolidateNote.NextDeliveryAttemptDate = null; } } ctx.SubmitChanges();//submit changes ticket-by-ticket so a failure later doesn't scrap the status update for tickets previously sent }
/// <summary> /// Sends the waiting ticket event notifications. /// </summary> public static void ProcessWaitingTicketEventNotifications() { processLock = new object(); lock (processLock) { var now = DateTime.Now; TicketDataDataContext ctx = new TicketDataDataContext(); var queuedNotes = from n in ctx.TicketEventNotifications where n.NextDeliveryAttemptDate.HasValue orderby n.NextDeliveryAttemptDate group n by new { n.NotifyUser, n.TicketId } into userTicketNotes select new { userTicketNotes.Key.NotifyUser, userTicketNotes.Key.TicketId, userTicketNotes }; foreach (var ut in queuedNotes) { string maxConsolidationWait = ConfigurationManager.AppSettings["EmailMaxConsolidationWaitMinutes"]; double consolidationWait = 10D;//10 minutes is default max wait if (!string.IsNullOrEmpty(maxConsolidationWait)) { consolidationWait = Convert.ToDouble(maxConsolidationWait); } if (ut.userTicketNotes.Count(n => n.NextDeliveryAttemptDate <= now) > 0) // at least 1 note ready, process { var minNextDeliveryTime = ut.userTicketNotes.Min(n => n.NextDeliveryAttemptDate); //gets the min next delivery date for pending notes var minPending = ut.userTicketNotes.SingleOrDefault ( note => (note.CommentId == (ut.userTicketNotes.Min(n => n.CommentId))) );// user's oldest pending comment (by comment ID just in case next delivery dates get out of relative order by accident) var maxPendingDeliveryTime = ut.userTicketNotes.Max(n => n.NextDeliveryAttemptDate);// gets the max next delivery date for pending notes var maxPending = ut.userTicketNotes.SingleOrDefault ( note => (note.CommentId == (ut.userTicketNotes.Max(n => n.CommentId))) );// user's most recent pending comment (by comment ID just in case next delivery dates get out of relative order by accident) if (ut.userTicketNotes.Count(n => n.EventGeneratedByUser != n.NotifyUser) > 0)// at least 1 note generated by user other than recipient { if ( (minNextDeliveryTime.Value.AddMinutes(consolidationWait) <= now) || // min delivery date is past the maximum allowable delay (maxPendingDeliveryTime <= now) //max pending is ready to go... commit them all ) { List <TicketEventNotification> consolitations = new List <TicketEventNotification>(); foreach (var note in ut.userTicketNotes) { if (note != maxPending) { note.Status = "consolidating"; consolitations.Add(note); } } SendTicketEventNotificationEmail(ctx, maxPending, consolitations);//consolitations will be null if only 1 message } else //max pending not yet ready... continue to wait until it is ready before sending { foreach (var note in ut.userTicketNotes) { if (note != maxPending) { note.Status = "consolidating"; } // else : max pending not ready yet - do nothing for max pending (will get sent later) } ctx.SubmitChanges(); } } else // all waiting are for events generated by recipient { if ( (minNextDeliveryTime.Value.AddMinutes(consolidationWait) <= now) || // min delivery date is past the maximum allowable delay (maxPendingDeliveryTime <= now) //max pending is ready to go... commit them all ) { //max ready or min past max wait time, but all notes are for events by recipient... kill them all... foreach (var note in ut.userTicketNotes) { note.NextDeliveryAttemptDate = null; note.Status = "suppressed"; note.LastDeliveryAttemptDate = now; } ctx.SubmitChanges(); } //else max note ready, do nothing } } //else : no notes ready - do nothing } } }
public SyndicationFeedFormatter CreateFeed() { SyndicationFeed feed = null; bool isEnabled = false; string enabledString = ConfigurationManager.AppSettings["EnableRSS"]; if (!string.IsNullOrEmpty(enabledString)) { isEnabled = Convert.ToBoolean(enabledString); } if (!isEnabled) { feed = new SyndicationFeed("TicketDesk Feed Disabled", "FeedDisabled", null, "FeedDisabled", DateTimeOffset.Now, null); } else { TicketDataDataContext ctx = new TicketDataDataContext(); // Create a new Syndication Feed. IQueryable <Ticket> tickets = from t in ctx.Tickets select t; // Create a new Syndication Item. string idArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["id"]; if (idArray != null) { tickets = from t in tickets where idArray.Split(',').Contains(t.TicketId.ToString()) select t; } string categoryArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["category"]; if (categoryArray != null) { tickets = from t in tickets where categoryArray.Split(',').Contains(t.Category) select t; } string tagArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["tag"]; if (tagArray != null) { tickets = from t in tickets join tags in ctx.TicketTags on t.TicketId equals tags.TicketId where tagArray.Split(',').Contains(tags.TagName) select t; } string priorityArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["priority"]; if (priorityArray != null) { tickets = from t in tickets where priorityArray.Split(',').Contains(t.Priority) select t; } string assignedArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["assigned"]; if (assignedArray != null) { tickets = from t in tickets where assignedArray.Split(',').Contains(t.AssignedTo) select t; } string ownerArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["owner"]; if (ownerArray != null) { tickets = from t in tickets where ownerArray.Split(',').Contains(t.Owner) select t; } string statusArray = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["status"]; if (statusArray != null) { tickets = from t in tickets where statusArray.Split(',').Contains(t.CurrentStatus) select t; } string closed = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["closed"]; if ((statusArray == null) && ((closed == null) || ((closed != "yes") && (closed != "1")))) { tickets = from t in tickets where t.CurrentStatus != "Closed" select t; } List <SyndicationItem> items = new List <SyndicationItem>(); foreach (Ticket ticket in tickets.Distinct().OrderByDescending(t => t.LastUpdateDate)) { SyndicationItem item = GetRSSEntryForTicket(ticket); items.Add(item); } feed = new SyndicationFeed("TicketDesk RSS", "TicketDeskRSS", null, "TicketDeskRSS", DateTimeOffset.Now, items); } string query = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"]; SyndicationFeedFormatter formatter = null; if (query == "atom") { formatter = new Atom10FeedFormatter(feed); } else { formatter = new Rss20FeedFormatter(feed); } return(formatter); }
private void SendFileToClient(HttpContext context, int fileId) { HttpResponse httpResponse = context.Response; int bufferSize = 100; byte[] outByte = new byte[bufferSize]; long startIndex = 0; TicketDataDataContext ctx = new TicketDataDataContext(); var query = from ta in ctx.TicketAttachments where ta.FileId == fileId select new { ta.FileName, ta.FileSize, ta.FileType, ta.FileContents }; DbCommand cmd = ctx.GetCommand(query); DbConnection conn = cmd.Connection; conn.Open(); DbDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); while (reader.Read()) { string filename = reader.GetString(0); int filelength = reader.GetInt32(1);//for some reason GetString doesn't convert int32 to string, throws an error. Using alternate access via indexer. string contenttype = reader.GetString(2); // set the mime type: must occur before getting the file content. httpResponse.ContentType = contenttype; httpResponse.Clear(); httpResponse.BufferOutput = false; context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + filename + "\";"); httpResponse.AppendHeader("Content-Length", filelength.ToString()); BinaryWriter writer = new BinaryWriter(httpResponse.OutputStream); bool fileComplete = false; while (!fileComplete) { if (startIndex + bufferSize >= filelength) { fileComplete = true; } startIndex += reader.GetBytes(3, startIndex, outByte, 0, bufferSize); writer.Write(outByte); writer.Flush(); } writer.Close(); httpResponse.OutputStream.Close(); } reader.Close(); conn.Close(); }