/// <summary>Converts the email message into an attachment.</summary> /// <param name="msg">The message.</param> /// <param name="ticketId">The ticket ID for the message.</param> /// <param name="authorId">The creator ID of the attachment.</param> /// <param name="msgDescription">Optional description for the attachment.</param> /// <returns>A remotedocument.</returns> private RemoteDocument getMsgAsAttachment(Message msg, long ticketId, long?authorId, string msgDescription) { //Add the file.. RemoteDocument retDoc = new RemoteDocument(); retDoc.ArtifactId = ticketId; retDoc.ArtifactTypeId = 6; retDoc.AttachmentTypeId = 1; retDoc.AuthorId = authorId; retDoc.FilenameOrUrl = "Message_" + msg.MessageUID + ".eml"; retDoc.UploadDate = DateTime.UtcNow; retDoc.Description = msgDescription; return(retDoc); }
/// <summary> /// Retrieves the automated test script from the server for the provided automated test run and populates /// the test run info as well as the test script attachment info /// </summary> /// <param name="remoteAutomatedTestRun">The api data object from the server</param> /// <param name="projectId">The id of the current project</param> /// <returns>The automated test run object that will be passed to the plugins</returns> public AutomatedTestRun PopulateAutomatedTestScript(RemoteAutomatedTestRun remoteAutomatedTestRun, int projectId) { try { //Create a new automated test run object AutomatedTestRun automatedTestRun = new AutomatedTestRun(); if (this._client.Connection_ConnectToProject(projectId)) { if (remoteAutomatedTestRun.AutomationAttachmentId.HasValue) { RemoteDocument remoteDocument = this._client.Document_RetrieveById(remoteAutomatedTestRun.AutomationAttachmentId.Value); if (remoteDocument == null) { string strMessage = "Could not retrieve attachment for TC:" + remoteAutomatedTestRun.TestCaseId + "."; Logger.LogMessage(strMessage, System.Diagnostics.EventLogEntryType.Warning); return(null); } else { //Populate the automated test run object from api test run object automatedTestRun.TestRunId = new Guid(); //Generates new random GUID //Standard Fields automatedTestRun.Name = remoteAutomatedTestRun.Name; automatedTestRun.AutomationEngineId = remoteAutomatedTestRun.AutomationEngineId; automatedTestRun.AutomationHostId = remoteAutomatedTestRun.AutomationHostId; automatedTestRun.AutomationEngineToken = remoteAutomatedTestRun.AutomationEngineToken; automatedTestRun.ReleaseId = remoteAutomatedTestRun.ReleaseId; automatedTestRun.TestCaseId = remoteAutomatedTestRun.TestCaseId; automatedTestRun.TestSetId = remoteAutomatedTestRun.TestSetId; automatedTestRun.TestSetTestCaseId = remoteAutomatedTestRun.TestSetTestCaseId; automatedTestRun.TestRunTypeId = remoteAutomatedTestRun.TestRunTypeId; automatedTestRun.TestSetTestCaseId = remoteAutomatedTestRun.TestSetTestCaseId; //Parameters if (remoteAutomatedTestRun.Parameters != null) { automatedTestRun.Parameters = new List <TestRunParameter>(); foreach (RemoteTestSetTestCaseParameter parameter in remoteAutomatedTestRun.Parameters) { TestRunParameter testRunParameter = new TestRunParameter(); testRunParameter.Name = parameter.Name; testRunParameter.Value = parameter.Value; automatedTestRun.Parameters.Add(testRunParameter); } } //Custom Properties automatedTestRun.Text01 = remoteAutomatedTestRun.Text01; automatedTestRun.Text02 = remoteAutomatedTestRun.Text02; automatedTestRun.Text03 = remoteAutomatedTestRun.Text03; automatedTestRun.Text04 = remoteAutomatedTestRun.Text04; automatedTestRun.Text05 = remoteAutomatedTestRun.Text05; automatedTestRun.Text06 = remoteAutomatedTestRun.Text06; automatedTestRun.Text07 = remoteAutomatedTestRun.Text07; automatedTestRun.Text08 = remoteAutomatedTestRun.Text08; automatedTestRun.Text09 = remoteAutomatedTestRun.Text09; automatedTestRun.Text10 = remoteAutomatedTestRun.Text10; automatedTestRun.List01 = remoteAutomatedTestRun.List01; automatedTestRun.List02 = remoteAutomatedTestRun.List02; automatedTestRun.List03 = remoteAutomatedTestRun.List03; automatedTestRun.List04 = remoteAutomatedTestRun.List04; automatedTestRun.List05 = remoteAutomatedTestRun.List05; automatedTestRun.List06 = remoteAutomatedTestRun.List06; automatedTestRun.List07 = remoteAutomatedTestRun.List07; automatedTestRun.List08 = remoteAutomatedTestRun.List08; automatedTestRun.List09 = remoteAutomatedTestRun.List09; automatedTestRun.List10 = remoteAutomatedTestRun.List10; //Populate the script info automatedTestRun.FilenameOrUrl = remoteDocument.FilenameOrUrl; automatedTestRun.Type = (AutomatedTestRun.AttachmentType)remoteDocument.AttachmentTypeId; automatedTestRun.Size = remoteDocument.Size; if (automatedTestRun.Type == AutomatedTestRun.AttachmentType.File) { //Need to actually download the test script automatedTestRun.TestScript = this._client.Document_OpenFile(remoteDocument.AttachmentId.Value); } //Return the populated test run return(automatedTestRun); } } else { string strMessage = "Could not find attachment for TC:" + remoteAutomatedTestRun.TestCaseId + ", ID #" + remoteAutomatedTestRun.AutomationAttachmentId + "."; Logger.LogMessage(strMessage, System.Diagnostics.EventLogEntryType.Warning); return(null); } } else { string strMessage = "Could not log on to system."; Logger.LogMessage(strMessage, System.Diagnostics.EventLogEntryType.Warning); return(null); } } catch (Exception ex) { Logger.LogMessage(ex); return(null); } }
static void Main(string[] args) { _options = new Settings(); if (Parser.Default.ParseArguments(args, _options)) { //Make sure that the Spira string is a URL. Uri serverUrl = null; string servicePath = _options.SpiraServer + ((_options.SpiraServer.EndsWith("/") ? "" : "/")) + "Services/v5_0/SoapService.svc"; if (!Uri.TryCreate(servicePath, UriKind.Absolute, out serverUrl)) { //Throw error: URL given not a URL. return; } //See if we have a mappings file specified, if so, make sure it exists List <ArtifactMapping> artifactMappings = null; string customPropertyField = null; if (!String.IsNullOrWhiteSpace(_options.SpiraMappingsFile)) { if (!File.Exists(_options.SpiraMappingsFile)) { //Throw error: Bad path. ConsoleLog(LogLevelEnum.Normal, "Cannot access the mapping file, please check the location and try again!"); Environment.Exit(-1); } artifactMappings = new List <ArtifactMapping>(); //Read in the lines, the first column should contain: //Filename,ArtifactTypeId,Custom_03 //where the number in the third column is the name of the custom property that the IDs will be using using (StreamReader streamReader = File.OpenText(_options.SpiraMappingsFile)) { string firstLine = streamReader.ReadLine(); string[] headings = firstLine.Split(','); //See if we have a match on the custom property number if (headings.Length > 2 && !String.IsNullOrWhiteSpace(headings[2])) { customPropertyField = headings[2].Trim(); //Now read in the rows of mappings while (!streamReader.EndOfStream) { string mappingLine = streamReader.ReadLine(); ArtifactMapping artifactMapping = new ArtifactMapping(); string[] components = mappingLine.Split(','); artifactMapping.Filename = components[0]; artifactMapping.ArtifactTypeId = Int32.Parse(components[1]); artifactMapping.ExternalKey = components[2]; artifactMappings.Add(artifactMapping); } streamReader.Close(); } } } //Make sure the path given is a real path.. try { Directory.GetCreationTime(_options.ImportPath); } catch { //Throw error: Bad path. ConsoleLog(LogLevelEnum.Normal, "Cannot access the import path, please check the location and try again!"); Environment.Exit(-1); } //Tell user we're operating. ConsoleLog(LogLevelEnum.Normal, "Importing files in " + _options.ImportPath); //Now run through connecting procedures. ConsoleLog(LogLevelEnum.Verbose, "Connecting to Spira server..."); SoapServiceClient client = CreateClient_Spira5(serverUrl); client.Open(); if (client.Connection_Authenticate2(_options.SpiraLogin, _options.SpiraPass, "DocumentImporter")) { ConsoleLog(LogLevelEnum.Verbose, "Selecting Spira project..."); var Projects = client.Project_Retrieve(); RemoteProject proj = Projects.FirstOrDefault(p => p.ProjectId == _options.SpiraProject); if (proj != null) { //Connect to the project. if (client.Connection_ConnectToProject((int)_options.SpiraProject)) { ConsoleLog(LogLevelEnum.Normal, "Uploading files..."); //Now let's get a list of all the files.. SearchOption opt = ((_options.PathRecursive) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); List <string> files = Directory.EnumerateFiles(_options.ImportPath, _options.ImportFilter, opt).ToList(); //Loop through each file and upload it. ConsoleLog(LogLevelEnum.Verbose, "Files:"); foreach (string file in files) { string conLog = Path.GetFileName(file); try { //Get the file details.. FileInfo fileInf = new FileInfo(file); string size = ""; if (fileInf.Length >= 1000000000) { size = (fileInf.Length / 1000000000).ToString() + "Gb"; } else if (fileInf.Length >= 1000000) { size = (fileInf.Length / 1000000).ToString() + "Mb"; } else if (fileInf.Length >= 1000) { size = (fileInf.Length / 1000).ToString() + "Kb"; } else { size = fileInf.Length.ToString() + "b"; } conLog += " (" + size + ")"; //Generate the RemoteDocument object. RemoteDocument newFile = new RemoteDocument(); newFile.AttachmentTypeId = 1; newFile.FilenameOrUrl = Path.GetFileName(file); newFile.Size = (int)fileInf.Length; newFile.UploadDate = DateTime.UtcNow; //Now we see if have mapped artifact ArtifactMapping mappedArtifact = artifactMappings.FirstOrDefault(m => m.Filename.ToLowerInvariant() == newFile.FilenameOrUrl.ToLowerInvariant()); if (mappedArtifact != null && !String.IsNullOrEmpty(customPropertyField)) { //We have to lookup the artifact, currently only incidents are supported if (mappedArtifact.ArtifactTypeId == 3) { //Retrieve the incident RemoteSort sort = new RemoteSort(); sort.PropertyName = "IncidentId"; sort.SortAscending = true; List <RemoteFilter> filters = new List <RemoteFilter>(); RemoteFilter filter = new RemoteFilter(); filter.PropertyName = customPropertyField; filter.StringValue = mappedArtifact.ExternalKey; filters.Add(filter); RemoteIncident remoteIncident = client.Incident_Retrieve(filters, sort, 1, 1).FirstOrDefault(); if (remoteIncident != null) { RemoteLinkedArtifact remoteLinkedArtifact = new SpiraService.RemoteLinkedArtifact(); remoteLinkedArtifact.ArtifactTypeId = mappedArtifact.ArtifactTypeId; remoteLinkedArtifact.ArtifactId = remoteIncident.IncidentId.Value; newFile.AttachedArtifacts = new List <RemoteLinkedArtifact>(); newFile.AttachedArtifacts.Add(remoteLinkedArtifact); } } else { ConsoleLog(LogLevelEnum.Normal, "Warning: Only incident mapped artifacts currently supported, so ignoring the mapped artifacts of type: " + mappedArtifact.ArtifactTypeId); } } //Read the file contents. (Into memory! Beware, large files!) byte[] fileContents = null; fileContents = File.ReadAllBytes(file); ConsoleLog(LogLevelEnum.Verbose, conLog); if (fileContents != null && fileContents.Length > 1) { newFile.AttachmentId = client.Document_AddFile(newFile, fileContents).AttachmentId.Value; } else { throw new FileEmptyException(); } } catch (Exception ex) { conLog += " - Error. (" + ex.GetType().ToString() + ")"; ConsoleLog(LogLevelEnum.Normal, conLog); } } } else { ConsoleLog(LogLevelEnum.Normal, "Cannot connect to project. Verify your Project Role."); Environment.Exit(-1); } } else { ConsoleLog(LogLevelEnum.Normal, "Cannot connect to project. Project #" + _options.SpiraProject.ToString() + " does not exist."); Environment.Exit(-1); } } else { ConsoleLog(LogLevelEnum.Normal, "Cannot log in. Check username and password."); Environment.Exit(-1); } } else { Environment.Exit(-1); } }
private void processSpiraAccount(Pop3Client clientPOP3, AccountDetails account, ApplicationSystem appServer) { const string METHOD = CLASS + "processSpiraAccount()"; this._eventLog.EntryLog(METHOD); //Create the application client and connect to our project and get users.. SoapServiceClient clientAppl = (SoapServiceClient)this.CreateApplicationClient(appServer, account); //Get users in the project.. List <RemoteProjectUser> spiraUsers = clientAppl.Project_RetrieveUserMembership(); //Get the known message IDs.. List <string> seenUIDs = this.readMessageIDsForAccount(account.AccountID.Value); //Get new emails from the client. List <Message> newMsgs = this.popGetNewMessages(clientPOP3, account, seenUIDs); //Get all projects.. List <RemoteProject> spiraProjs = clientAppl.Project_Retrieve(); //Loop through each email. foreach (Message msg in newMsgs) { this._eventLog.WriteTrace(METHOD, "Starting on message " + msg.MessageLogID + "..."); //Make sure we have a from address, otherwise skip (Delivery Returned messages have no FROM address) //First see if the message should be skipped. (Keywords, Headers, or Email Addresses) if (msg.Headers != null && msg.Headers.From != null && !String.IsNullOrWhiteSpace(msg.Headers.From.Address) && msg.Headers.From.Address.ToLowerInvariant().Trim() != account.AccountEmail.ToLowerInvariant().Trim()) { string filterMsg; if (this.doesMessageClearFilters(msg, out filterMsg)) { //First see if there's a header we can get the artifact ID from.. ArtifactTypeEnum artType = ArtifactTypeEnum.None; int artId = -1; if (msg.Headers.UnknownHeaders[Common.MESSAGEHEADER_SPIRA_ARTIFACT] != null && rgxArtifactToken.IsMatch(msg.Headers.UnknownHeaders[Common.MESSAGEHEADER_SPIRA_ARTIFACT])) { //Get the art type and the id.. Match matches = rgxArtifactToken.Match(msg.Headers.UnknownHeaders[Common.MESSAGEHEADER_SPIRA_ARTIFACT]); this.retrieveArtTypeAndId(matches.Groups["type"].Value, matches.Groups["id"].Value, out artType, out artId); } if (artId == -1 || artType == ArtifactTypeEnum.None) { if (rgxArtifactToken.IsMatch(msg.Headers.Subject)) { //Get the art type and the id.. Match matches = rgxArtifactToken.Match(msg.Headers.Subject); this.retrieveArtTypeAndId(matches.Groups["type"].Value, matches.Groups["id"].Value, out artType, out artId); } } //Change projects, if necessary, and if we're able to.. try { int projNum = clientAppl.System_GetProjectIdForArtifact((int)artType, artId); if (projNum != 0) { bool succNewProject = clientAppl.Connection_ConnectToProject(projNum); if (!succNewProject) { //Couldn't connect to the project this item belongs to. Throw an error. this._eventLog.WriteMessage("Message " + msg.MessageLogID + " contains information for a project [PR:" + projNum.ToString() + "] that the client could not connect to. Skipping.", System.Diagnostics.EventLogEntryType.Information); artType = ArtifactTypeEnum.Skip; artId = 0; } } } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "Message " + msg.MessageLogID + " - while trying to retrieve project number from artifact. Skipping."); } //If we have a match, find the user.. if (artId > 0 && artType != ArtifactTypeEnum.None) { //Detect if more than one user is found.. int userCnt = spiraUsers.Where(su => su.EmailAddress.ToLowerInvariant() == msg.Headers.From.MailAddress.Address.ToLowerInvariant()).Count(); if (userCnt == 1) { RemoteProjectUser selUser = spiraUsers.Where(su => su.EmailAddress.ToLowerInvariant() == msg.Headers.From.MailAddress.Address.ToLowerInvariant()).Single(); //See if the item exists in the server.. RemoteArtifact remArt = null; try { switch (artType) { case ArtifactTypeEnum.Requirement: remArt = clientAppl.Requirement_RetrieveById(artId); break; case ArtifactTypeEnum.Test_Case: remArt = clientAppl.TestCase_RetrieveById(artId); break; case ArtifactTypeEnum.Incident: remArt = clientAppl.Incident_RetrieveById(artId); break; case ArtifactTypeEnum.Release: remArt = clientAppl.Release_RetrieveById(artId); break; case ArtifactTypeEnum.Task: remArt = clientAppl.Task_RetrieveById(artId); break; case ArtifactTypeEnum.Test_Set: remArt = clientAppl.TestSet_RetrieveById(artId); break; } if (remArt == null) { throw new Exception("Artifact did not exist: " + artType.ToString() + " #" + artId.ToString()); } } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "For message " + msg.MessageLogID + ", referenced artifact did not exist."); continue; } try { //The artifact exists, let's add a comment.. RemoteComment comment = new RemoteComment(); comment.ArtifactId = artId; comment.CreationDate = DateTime.UtcNow; comment.UserId = selUser.UserId; comment.Text = this.getTextFromMessage(msg, true, true); switch (artType) { case ArtifactTypeEnum.Requirement: comment = clientAppl.Requirement_CreateComment(comment); break; case ArtifactTypeEnum.Test_Case: comment = clientAppl.TestCase_CreateComment(comment); break; case ArtifactTypeEnum.Incident: comment = clientAppl.Incident_AddComments(new List <RemoteComment>() { comment }).FirstOrDefault(); break; case ArtifactTypeEnum.Release: comment = clientAppl.Release_CreateComment(comment); break; case ArtifactTypeEnum.Task: comment = clientAppl.Task_CreateComment(comment); break; case ArtifactTypeEnum.Test_Set: comment = clientAppl.TestSet_CreateComment(comment); break; } if (comment != null && comment.CommentId.HasValue) { //Now check for attachments try { foreach (MessagePart attach in msg.FindAllAttachments().Where(aa => aa.IsMultiPart == false && aa.IsText == false)) { //Add the file.. RemoteLinkedArtifact artifactLink = new RemoteLinkedArtifact(); artifactLink.ArtifactId = comment.ArtifactId; artifactLink.ArtifactTypeId = (int)artType; RemoteDocument newDoc = new RemoteDocument(); newDoc.AttachedArtifacts = new List <RemoteLinkedArtifact>() { artifactLink }; newDoc.AttachmentTypeId = 1; newDoc.AuthorId = selUser.UserId; newDoc.FilenameOrUrl = attach.FileName; newDoc.UploadDate = DateTime.UtcNow; //Check for string overrun and add extension if necessary. if (newDoc.FilenameOrUrl.Length > 250) { newDoc.FilenameOrUrl = newDoc.FilenameOrUrl.Substring(0, 250); } if (string.IsNullOrWhiteSpace(Path.GetExtension(newDoc.FilenameOrUrl)) && attach.ContentType != null) { string tempFileExtension = Utils.GetExtensionFromMimeType(attach.ContentType.MediaType); if (!string.IsNullOrWhiteSpace(tempFileExtension)) { newDoc.FilenameOrUrl += "." + tempFileExtension; } } //Call the function to upload the file to Spira newDoc = clientAppl.Document_AddFile(newDoc, attach.Body); //Log it. this._eventLog.WriteMessage("Attachment #" + newDoc.AttachmentId.ToSafeString() + " created from file '" + attach.FileName + "' for Artifact " + artType + " #:" + comment.ArtifactId + " from message " + msg.MessageLogID + ".", System.Diagnostics.EventLogEntryType.Information); } } catch (Exception ex) { //Log and continue this._eventLog.WriteMessage(METHOD, ex, "Saving attachment for message " + msg.MessageLogID); } } } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "While trying to save message '" + msg.MessageLogID + "' as a new comment."); continue; } //If we get this far, mark the message as processed. try { //Add it to our list.. seenUIDs.Add(msg.MessageUID); if (account.RemoveFromServer) { clientPOP3.DeleteMessage(msg.MessageIndex); } } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "Trying to delete message " + msg.MessageLogID + " from server."); } } else { string msgLog = ""; if (userCnt == 0) { msgLog = "Message " + msg.MessageLogID + " was sent from a user not a mamber of the specified project. Not importing unknown users."; } else if (userCnt > 1) { msgLog = "Message " + msg.MessageLogID + " was sent from a user that did not have a unique email address. Cannot import to avoid selecting the wrong user."; } this._eventLog.WriteMessage(msgLog, System.Diagnostics.EventLogEntryType.Information); } } else { if (artType != ArtifactTypeEnum.Skip) { int userCnt = spiraUsers.Where(su => su.EmailAddress.ToLowerInvariant() == msg.Headers.From.MailAddress.Address.ToLowerInvariant()).Count(); if (userCnt == 1) { RemoteProjectUser selUser = spiraUsers.Where(su => su.EmailAddress.ToLowerInvariant() == msg.Headers.From.MailAddress.Address.ToLowerInvariant()).Single(); //Create a new Incident.. RemoteIncident newIncident = new RemoteIncident(); newIncident.CreationDate = DateTime.Now; newIncident.Description = this.getTextFromMessage(msg, true, false); newIncident.Name = msg.Headers.Subject; newIncident.OpenerId = selUser.UserId; //Check regex other projects if (account.UseRegexToMatch) { Regex regProj1 = new Regex(@"\[PR[\:\-\s](\d*?)\]", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); if (regProj1.IsMatch(newIncident.Description) || regProj1.IsMatch(newIncident.Name)) { //Someone used the PR tag in the email body, we'll use it, if possible. string matchNum = ""; if (regProj1.IsMatch(newIncident.Name)) { matchNum = regProj1.Matches(newIncident.Name)[0].Groups[1].Value; } else if (regProj1.IsMatch(newIncident.Description)) { matchNum = regProj1.Matches(newIncident.Description)[0].Groups[1].Value; } else { this._eventLog.WriteMessage("ERROR: At least one RegEx returned IsMatch, but none contained match.", System.Diagnostics.EventLogEntryType.Information); continue; } int projNum = 0; if (int.TryParse(matchNum, out projNum)) { //We had a number, let's see if it's a valid product. RemoteProject proj = spiraProjs.FirstOrDefault(prd => prd.ProjectId == projNum); if (proj != null) { //Connect to project.. if (clientAppl.Connection_ConnectToProject(proj.ProjectId.Value)) { newIncident.ProjectId = proj.ProjectId.Value; this._eventLog.WriteMessage("Message " + msg.MessageLogID + " changed to Project '" + proj.Name + "' due to having [PR:xx] tag.", System.Diagnostics.EventLogEntryType.Information); } else { this._eventLog.WriteMessage("Message " + msg.MessageLogID + " contained project token for project '" + proj.Name + "', but email import has no access to that project. Using default.", System.Diagnostics.EventLogEntryType.Information); } } else { this._eventLog.WriteMessage("Message '" + msg.MessageLogID + "' contained token for project " + projNum.ToString() + " but project was inaccessible. Using default.", System.Diagnostics.EventLogEntryType.Information); } } } else { foreach (RemoteProject prod in spiraProjs) { Regex regProd3 = new Regex(@"\b" + prod.Name + @"\b", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); if (regProd3.IsMatch(newIncident.Description) || regProd3.IsMatch(newIncident.Name)) { newIncident.ProjectId = prod.ProjectId.Value; this._eventLog.WriteMessage("Message " + msg.MessageLogID + " changed to Product '" + prod.Name + "' due to having Product name '" + prod.Name + "'", System.Diagnostics.EventLogEntryType.Information); break; } } } } //Now save the Incident.. try { newIncident = clientAppl.Incident_Create(newIncident); if (newIncident.IncidentId.HasValue) { //Now check for attachments try { foreach (MessagePart attach in msg.FindAllAttachments().Where(aa => aa.IsMultiPart == false && aa.IsText == false)) { //Add the file.. RemoteLinkedArtifact artifactLink = new RemoteLinkedArtifact(); artifactLink.ArtifactId = newIncident.IncidentId.Value; artifactLink.ArtifactTypeId = 3; /* Incident */ RemoteDocument newDoc = new RemoteDocument(); newDoc.AttachedArtifacts = new List <RemoteLinkedArtifact>() { artifactLink }; newDoc.AttachmentTypeId = 1; newDoc.AuthorId = selUser.UserId; newDoc.FilenameOrUrl = attach.FileName; newDoc.UploadDate = DateTime.UtcNow; //Check for string overrun and add extension if necessary. if (newDoc.FilenameOrUrl.Length > 250) { newDoc.FilenameOrUrl = newDoc.FilenameOrUrl.Substring(0, 250); } if (string.IsNullOrWhiteSpace(Path.GetExtension(newDoc.FilenameOrUrl)) && attach.ContentType != null) { string tempFileExtension = Utils.GetExtensionFromMimeType(attach.ContentType.MediaType); if (!string.IsNullOrWhiteSpace(tempFileExtension)) { newDoc.FilenameOrUrl += "." + tempFileExtension; } } //Call the function to upload the file to Spira newDoc = clientAppl.Document_AddFile(newDoc, attach.Body); //Log it. this._eventLog.WriteMessage("Attachment #" + newDoc.AttachmentId.ToSafeString() + " created from file '" + attach.FileName + "' for Incident IN:" + newIncident.IncidentId.Value + " from message " + msg.MessageLogID + ".", System.Diagnostics.EventLogEntryType.Information); } } catch (Exception ex) { //Log and continue this._eventLog.WriteMessage(METHOD, ex, "Saving attachment for message " + msg.MessageLogID); } } //If we get this far, mark the message as processed. try { //Add it to our list.. seenUIDs.Add(msg.MessageUID); if (account.RemoveFromServer) { clientPOP3.DeleteMessage(msg.MessageIndex); } } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "Trying to delete message " + msg.MessageLogID + " from server."); } } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "While trying to save message '" + msg.MessageLogID + "' as an incident."); } } else { string msgLog = ""; if (userCnt == 0) { msgLog = "Message " + msg.MessageLogID + " was sent from a user not a mamber of the specified project. Not importing unknown users."; } else if (userCnt > 1) { msgLog = "Message " + msg.MessageLogID + " was sent from a user that did not have a unique email address. Cannot import to avoid selecting the wrong user."; } this._eventLog.WriteMessage(msgLog, System.Diagnostics.EventLogEntryType.Information); } } else { } } } else { //Log it.. this._eventLog.WriteMessage("Message " + msg.MessageLogID + " on the server did not pass filters:" + Environment.NewLine + "Subject: " + msg.Headers.Subject + Environment.NewLine + "Reasons: " + Environment.NewLine + filterMsg, System.Diagnostics.EventLogEntryType.Warning); } } else { //Log it.. this._eventLog.WriteMessage("Message " + msg.MessageLogID + " had no From address or was from the import account --" + Environment.NewLine + "Subject: " + msg.Headers.Subject + Environment.NewLine + "Msg UID: " + msg.Headers.MessageId + Environment.NewLine + "For reason: From Email address same as importing account, or was null.", System.Diagnostics.EventLogEntryType.Warning); } } //Save the seen message IDs.. this.saveMessageIDsForAccount(account.AccountID.Value, seenUIDs); //Disconnect client and POP3.. try { clientPOP3.Disconnect(); clientAppl.Connection_Disconnect(); } catch (Exception ex) { this._eventLog.WriteMessage(METHOD, ex, "Trying to close connections."); } }
private void processKronoAccount(Pop3Client clientPOP3, AccountDetails account, ApplicationSystem appServer) { const string METHOD = CLASS + "processKronoAccount()"; _eventLog.EntryLog(METHOD); //Create the application client and connect to our project and get users.. SoapServiceClient clientAppl = (SoapServiceClient)this.CreateApplicationClient(appServer, account); //Get the products and settings from Kronodesk. List <RemoteProduct> krnProds = clientAppl.Product_Retrieve(false); //Get list of blocked emails.. List <string> blockedEmails = clientAppl.System_GetBlockedEmails(); //Get list of seperator lines.. List <string> seperatorLines = clientAppl.System_GetEmailSeparator(); //Get the known message IDs.. List <string> seenUIDs = readMessageIDsForAccount(account.AccountID.Value); //Get new emails from the client. List <Message> newMsgs = popGetNewMessages(clientPOP3, account, seenUIDs); //Loop through each email. foreach (Message msg in newMsgs) { try { _eventLog.WriteTrace(METHOD, "Starting on message " + msg.MessageLogID + "..."); //First see if the message should be skipped. (Keywords, Headers, or Email Addresses) if (msg.Headers != null && msg.Headers.From != null && !string.IsNullOrWhiteSpace(msg.Headers.From.Address) && msg.Headers.From.Address.ToLowerInvariant().Trim() != account.AccountEmail.ToLowerInvariant().Trim()) { string filterMsg; //Run it though our own filters. if (doesMessageClearFilters(msg, out filterMsg, blockedEmails)) { long?tktId = null; //See if we can get a ticket ID.. if ((!tktId.HasValue) && msg.Headers.UnknownHeaders[Common.MESSAGEHEADER_KRONO_TICKET] != null) { tktId = retrieveTicketId(msg.Headers.UnknownHeaders[Common.MESSAGEHEADER_KRONO_TICKET]); } if (!tktId.HasValue) { Regex rgx = new Regex(@"\[[a-zA-Z]{2}[\:-][0-9]+\]", RegexOptions.IgnoreCase | RegexOptions.Singleline); tktId = retrieveTicketId(rgx.Match(msg.Headers.Subject).Value); } //Get user that this email is from.. RemoteUser usrFrom = clientAppl.User_RetrieveByEmailAddress(msg.Headers.From.MailAddress.Address); //See if we have any CC users List <string> ccList = null; if (msg.Headers.Cc != null && msg.Headers.Cc.Count > 0) { ccList = msg.Headers.Cc.Select(c => c.MailAddress.Address).ToList(); } //Now process the email. RemoteTicket newTicket = null; RemoteTicketNote newTktNote = null; if (tktId.HasValue) { try { //The ticket exists, we're going to add the text as a new comment. //The flag on whether we need to make it a '3rd party' note. bool add3rd = (usrFrom == null); //If at least one user exists.. if (!add3rd) { //Create the note.. newTktNote = new RemoteTicketNote(); newTktNote.CreationDate = DateTime.UtcNow; newTktNote.AuthorId = usrFrom.UserId; newTktNote.TicketId = tktId.Value; newTktNote.Text = getTextFromMessage(msg, true, true, seperatorLines); try { newTktNote = clientAppl.Ticket_AddNote(newTktNote); } catch (Exception ex) { _eventLog.WriteMessage("Error adding note to message. Adding it as third-party."); add3rd = true; } if (!add3rd) { _eventLog.WriteMessage("Note #" + newTktNote.NoteId.ToSafeString() + " added to Ticket #" + newTktNote.TicketId.ToSafeString() + " created from message " + msg.MessageLogID + ".", System.Diagnostics.EventLogEntryType.Information); //If we get this far, mark the message as processed. try { if (account.RemoveFromServer) { clientPOP3.DeleteMessage(msg.MessageIndex); } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Trying to delete message " + msg.MessageLogID + " from server."); } //We processed the message, add it. seenUIDs.Add(msg.MessageUID); } } if (add3rd) { try { //An email was sent in from a third party. string strDescDetails = "Full Subject: " + msg.Headers.Subject + Environment.NewLine + "From Address: " + ((string.IsNullOrWhiteSpace(msg.Headers.From.Address)) ? "< empty >" : msg.Headers.From.Address) + Environment.NewLine + "From Name: " + ((string.IsNullOrWhiteSpace(msg.Headers.From.DisplayName)) ? "< empty >" : msg.Headers.From.DisplayName); RemoteDocument rawMsg = getMsgAsAttachment(msg, tktId.Value, null, "3rd Party Email:" + Environment.NewLine + strDescDetails); rawMsg = clientAppl.Document_AddFile(rawMsg, msg.RawMessage); //Add comment. newTktNote = new RemoteTicketNote(); newTktNote.CreationDate = DateTime.UtcNow; newTktNote.TicketId = tktId.Value; newTktNote.Text = "An email for this ticket was recieved from a third unknown party, and has been attached to this ticket as a file. Details:" + Environment.NewLine + "<pre>" + strDescDetails + Environment.NewLine + "Attachment: " + rawMsg.FilenameOrUrl + "</pre>"; newTktNote.Text = newTktNote.Text.Replace(Environment.NewLine, "<br />"); //Convert to HTML. newTktNote = clientAppl.Ticket_AddNote(newTktNote); _eventLog.WriteMessage("Message from 3rd party to ticket. Message " + msg.MessageLogID + " saved as Attachment #" + rawMsg.AttachmentId.ToSafeString() + " & Note #" + newTktNote.NoteId + " added.", System.Diagnostics.EventLogEntryType.Information); //If we get this far, mark the message as processed. try { if (account.RemoveFromServer) { clientPOP3.DeleteMessage(msg.MessageIndex); } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Trying to delete message " + msg.MessageLogID + " from server."); } //We processed the message, add it. seenUIDs.Add(msg.MessageUID); } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Error adding 3rd party email to ticket from message " + msg.MessageLogID); } } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Error adding note to ticket from message " + msg.MessageLogID + "."); break; } } else { try { //Create the fake user first.. if (usrFrom == null) { RemoteUser newUser = new RemoteUser(); newUser.Active = true; newUser.Approved = false; newUser.EmailAddress = msg.Headers.From.MailAddress.Address; newUser.Login = msg.Headers.From.MailAddress.Address; Dictionary <string, string> names = getFullNameFromAddress(msg.Headers.From); newUser.FirstName = names["first"]; newUser.LastName = names["last"]; //Check for too-long strings.. if (newUser.FirstName.Length > 49) { newUser.FirstName = newUser.FirstName.Substring(0, 49); } if (newUser.LastName.Length > 49) { newUser.LastName = newUser.LastName.Substring(0, 49); } if (newUser.Login.Length > 49) { newUser.Login = newUser.Login.Substring(0, 49); } if (newUser.EmailAddress.Length > 49) { newUser.EmailAddress = newUser.EmailAddress.Substring(0, 49); } usrFrom = clientAppl.User_Create(newUser, "abcdefghijkLMNOPQ1", "Registration Code", Membership.GeneratePassword(10, 0), new List <string>()); _eventLog.WriteMessage("Temporary user '" + newUser.Login + "' created, user ID#" + usrFrom.UserId.ToSafeString(), System.Diagnostics.EventLogEntryType.Information); } //Need to create a new ticket. newTicket = new RemoteTicket(); newTicket.CreationDate = DateTime.UtcNow; newTicket.Name = msg.Headers.Subject; newTicket.Description = getTextFromMessage(msg, true); newTicket.OpenerId = usrFrom.UserId; newTicket.ProductId = account.ProductOrProjectId.Value; //Add any CC users newTicket.CCList = ccList; //Add the source newTicket.SourceId = -2; //Email //Check regex other products if (account.UseRegexToMatch) { Regex regProd1 = new Regex(@"\[PR[\:\-\s](\d*?)\]", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); if (regProd1.IsMatch(newTicket.Description) || regProd1.IsMatch(newTicket.Name)) { //Someone used the PR tag in the email body, we'll use it, if possible. string matchNum = ""; if (regProd1.IsMatch(newTicket.Name)) { matchNum = regProd1.Matches(newTicket.Name)[0].Groups[1].Value; } else if (regProd1.IsMatch(newTicket.Description)) { matchNum = regProd1.Matches(newTicket.Description)[0].Groups[1].Value; } else { _eventLog.WriteMessage("ERROR: At least one RegEx returned IsMatch, but none contained match.", System.Diagnostics.EventLogEntryType.Information); continue; } int prodNum = 0; if (int.TryParse(matchNum, out prodNum)) { //We had a number, let's see if it's a valid product. RemoteProduct prod = krnProds.Where(prd => prd.ProductId == prodNum).SingleOrDefault(); if (prod != null) { newTicket.ProductId = (int)prod.ProductId.Value; _eventLog.WriteMessage("Message " + msg.MessageLogID + " changed to Product '" + prod.Name + "' due to having [PR:xx] tag.", System.Diagnostics.EventLogEntryType.Information); } } } else { bool foundToken = false; foreach (RemoteProduct prod in krnProds) { if (!string.IsNullOrWhiteSpace(prod.ProductToken)) { Regex regProd2 = new Regex(@"\b" + prod.ProductToken + @"\b", RegexOptions.CultureInvariant | RegexOptions.Compiled); if (regProd2.IsMatch(newTicket.Description) || regProd2.IsMatch(newTicket.Name)) { newTicket.ProductId = (int)prod.ProductId.Value; foundToken = true; _eventLog.WriteMessage("Message " + msg.MessageLogID + " changed to Product '" + prod.Name + "' due to having Product token '" + prod.ProductToken + "'", System.Diagnostics.EventLogEntryType.Information); break; } } } if (!foundToken) { foreach (RemoteProduct prod in krnProds) { if (!string.IsNullOrWhiteSpace(prod.Name)) { Regex regProd3 = new Regex(@"\b" + prod.Name + @"\b", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); if (regProd3.IsMatch(newTicket.Description) || regProd3.IsMatch(newTicket.Name)) { newTicket.ProductId = (int)prod.ProductId.Value; _eventLog.WriteMessage("Message " + msg.MessageLogID + " changed to Product '" + prod.Name + "' due to having Product name '" + prod.Name + "'", System.Diagnostics.EventLogEntryType.Information); break; } } } } } } //Check for string overrun.. if (newTicket.Name.Length > 254) { newTicket.Name = newTicket.Name.Substring(0, 254); } //Check for null (or empty) Name.. if (string.IsNullOrWhiteSpace(newTicket.Name)) { newTicket.Name = "(no subject)"; } newTicket = clientAppl.Ticket_Create(newTicket); tktId = newTicket.TicketId; _eventLog.WriteMessage("Ticket #" + newTicket.TicketId.ToSafeString() + " created from message " + msg.MessageLogID + ".", System.Diagnostics.EventLogEntryType.Information); //If we get this far, mark the message as processed. try { //Add it to our list.. seenUIDs.Add(msg.MessageUID); if (account.RemoveFromServer) { clientPOP3.DeleteMessage(msg.MessageIndex); } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Trying to delete message " + msg.MessageLogID + " from server."); } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Creating new ticket/user for message " + msg.MessageLogID); continue; } } //Check for attachments now.. if (tktId.HasValue) { //The list of contentID's mapped to URLs.. Dictionary <string, string> mappedURLs = new Dictionary <string, string>(); try { //Get existing attachments and add them to the list. List <RemoteDocument> tktDocs = clientAppl.Document_GetForArtifact(6, tktId.Value, true); Dictionary <string, RemoteDocument> savedHashes = new Dictionary <string, RemoteDocument>(); foreach (RemoteDocument doc in tktDocs) { string hash = System.Text.ASCIIEncoding.ASCII.GetString(doc.Hash).Trim(); if (!savedHashes.ContainsKey(hash)) { savedHashes.Add(hash.Trim(), doc); } } foreach (MessagePart attach in msg.FindAllAttachments().Where(aa => aa.IsMultiPart == false && aa.IsText == false)) { //First get the MD5 of the attachment.. string attHash; using (var md5 = new MD5CryptoServiceProvider()) attHash = System.Text.ASCIIEncoding.ASCII.GetString(md5.ComputeHash(attach.Body)).Trim(); //Make sure the hash dosen't already exist.. bool doesExist = false; if (savedHashes.ContainsKey(attHash)) { //It exists, so se tthe flag and add the URL and Id to our dictionary.. doesExist = true; if (!mappedURLs.ContainsKey(attach.ContentId)) { mappedURLs.Add(attach.ContentId, savedHashes[attHash].DocumentURL); } } if (!doesExist) { //Add the file.. RemoteDocument newDoc = new RemoteDocument(); newDoc.ArtifactId = tktId.Value; newDoc.ArtifactTypeId = 6; newDoc.AttachmentTypeId = 1; newDoc.AuthorId = usrFrom.UserId; newDoc.FilenameOrUrl = attach.FileName; newDoc.UploadDate = DateTime.UtcNow; //Check for string overrun and add extension if necessary. if (newDoc.FilenameOrUrl.Length > 250) { newDoc.FilenameOrUrl = newDoc.FilenameOrUrl.Substring(0, 250); } if (string.IsNullOrWhiteSpace(Path.GetExtension(newDoc.FilenameOrUrl)) && attach.ContentType != null) { string tempFileExtension = clientAppl.System_GetExtensionFromMimeType(attach.ContentType.MediaType); if (!string.IsNullOrWhiteSpace(tempFileExtension)) { newDoc.FilenameOrUrl += "." + tempFileExtension; } } //Call the function and update mappings.. newDoc = clientAppl.Document_AddFile(newDoc, attach.Body); savedHashes.Add(attHash, newDoc); if (!string.IsNullOrWhiteSpace(attach.ContentId)) { mappedURLs.Add(attach.ContentId, newDoc.DocumentURL); } //Loggit. _eventLog.WriteMessage("Attachment #" + newDoc.AttachmentId.ToSafeString() + " created from file '" + attach.FileName + "' for Ticket #" + tktId.ToSafeString() + " from message " + msg.MessageLogID + ".", System.Diagnostics.EventLogEntryType.Information); } else { _eventLog.WriteMessage("Attachment file '" + attach.FileName + "' not uploaded from message " + msg.MessageLogID + ", hash already exists in Ticket #" + tktId.ToSafeString() + ".", System.Diagnostics.EventLogEntryType.Information); } } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Saving attachment for message " + msg.MessageLogID); break; } //If the flag's set, get the entire message.. if (account.SaveRawEmailAsAttachment) { try { RemoteDocument rawMsg = getMsgAsAttachment(msg, tktId.Value, usrFrom.UserId, null); rawMsg = clientAppl.Document_AddFile(rawMsg, msg.RawMessage); _eventLog.WriteMessage("Message " + msg.MessageLogID + " saved as Attachment #" + rawMsg.AttachmentId.ToSafeString() + ".", System.Diagnostics.EventLogEntryType.Information); } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Saving message " + msg.MessageLogID + " as attachment:"); break; } } //Now loop through each saved attachment, and update URLs.. bool needsTktUpdating = false; bool needsNoteUpdating = false; foreach (KeyValuePair <string, string> kvpURL in mappedURLs) { //See if we need to updat ethe note or the ticket.. if (newTicket == null && newTktNote != null) { if (newTktNote.Text.Contains(kvpURL.Key)) { newTktNote.Text = newTktNote.Text.Replace("cid:" + kvpURL.Key, kvpURL.Value); needsNoteUpdating = true; } } else if (newTicket != null && newTktNote == null) { if (newTicket.Description.Contains(kvpURL.Key)) { newTicket.Description = newTicket.Description.Replace("cid:" + kvpURL.Key, kvpURL.Value); needsTktUpdating = true; } } else { //Log warning message here. _eventLog.WriteMessage("Both newTicket and newTktNote were null or not null while processing message " + msg.MessageLogID + ".", System.Diagnostics.EventLogEntryType.Warning); } } if (needsTktUpdating) { try { //The date is set to 4/26/1976 so that the KronoDesk code knows not to record history changes and send out another notification. //All we're doing is converting the description URLs for the newly-uploaded attachments. _eventLog.WriteTrace(METHOD, "Updating ticket description with attachment URLs for message " + msg.MessageLogID + "."); newTicket.LastUpdateDate = new DateTime(1976, 4, 26); clientAppl.Ticket_Update(newTicket); } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Trying to update ticket description for message " + msg.MessageLogID + "."); } } else if (needsNoteUpdating) { try { _eventLog.WriteTrace(METHOD, "Updating note text with attachment URLs for message " + msg.MessageLogID + "."); clientAppl.Ticket_UpdateNote(newTktNote); } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "Trying to update note text for message " + msg.MessageLogID + "."); } } } } else { //Log it.. _eventLog.WriteMessage("Message " + msg.MessageLogID + " on the server did not pass filters:" + Environment.NewLine + "Subject: " + msg.Headers.Subject + Environment.NewLine + "Reasons: " + Environment.NewLine + filterMsg, System.Diagnostics.EventLogEntryType.Warning); } } else { //Log it.. _eventLog.WriteMessage("Message " + msg.MessageLogID + " had no From address or was from the import account --" + Environment.NewLine + "Subject: " + msg.Headers.Subject + Environment.NewLine + "Msg UID: " + msg.Headers.MessageId + Environment.NewLine + "For reason: From Email address same as importing account, or was null.", System.Diagnostics.EventLogEntryType.Warning); } } catch (Exception ex) { _eventLog.WriteMessage(METHOD, ex, "While importing message " + msg.MessageLogID); } } //Save the seen message IDs first, in case there's a problem disconnecting. saveMessageIDsForAccount(account.AccountID.Value, seenUIDs); //Disconnect client and POP3.. try { clientPOP3.Disconnect(); clientAppl.Connection_Disconnect(); } catch (Exception ex) { _eventLog.WriteMessage("Trying to disconnect from server.", ex); } }