public RedmineController( ILogger <TestController> logger, IConfigurationService configuration, IServiceProvider services, RedmineXmlImporter importer ) { _logger = logger; _config = configuration; _services = services; _importer = importer; // Init _redmineUrl = _config.Get("Redmine:Url"); _redmineApiKey = _config.Get("Redmine:ApiKey"); var _reservedThreads = 2; _opts = new ExecutionDataflowBlockOptions(); if (Environment.ProcessorCount > _reservedThreads) { _opts.MaxDegreeOfParallelism = Environment.ProcessorCount - _reservedThreads; } // Checks if (string.IsNullOrWhiteSpace(_redmineUrl)) { throw new Exception("Не настроен адрес сайта Redmine"); } if (string.IsNullOrWhiteSpace(_redmineApiKey)) { throw new Exception("Не настроен Apikey для доступа к Redmine"); } }
/// <summary> /// Imports custom fields /// </summary> /// <returns></returns> private async Task <ImportStats> _importCustomFields() { _logger.LogDebug($"Custom fields import. Start."); using var client = RedmineXmlImporter.CreateWebClient(); var uri = new Uri($"{_redmineUrl}custom_fields.json?key={_redmineApiKey}"); var json = await client.DownloadStringTaskAsync(uri); var result = new ImportStats(); var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var list = JsonSerializer.Deserialize <Models.RedmineJson.CustomFieldResponse>(json, opts); using var scope = _services.CreateScope(); using var _db = scope.ServiceProvider.GetRequiredService <DB>(); var types = _db.CustomField.ToList(); result.total = list.FieldList.Count; foreach (var item in list.FieldList) { var dbItem = types.FirstOrDefault(d => d.Id == item.Id); if (dbItem == null) { dbItem = new Redmine.Models.CustomField { Id = item.Id, Created = DateTimeOffset.Now }; await _db.AddAsync(dbItem); result.added++; } dbItem.Name = item.Name; dbItem.Type = item.Type; dbItem.Format = item.Format; dbItem.IsRequired = item.IsRequired; dbItem.IsMultiple = item.IsMultiple; if (_db.Entry(dbItem).State != EntityState.Unchanged) { dbItem.Updated = DateTimeOffset.Now; result.updated++; } } await _db.SaveChangesAsync(); _logger.LogInformation($"Custom fields import. Finish. {list.FieldList.Count} processed."); return(result); }
/// <summary> /// Gets pages count and starts receiver-processor job for all pages /// </summary> /// <param name="uri">Base uri to fetch data from</param> /// <param name="processor">Processor function for fetched xml</param> /// <returns>Child elements in received xml</returns> private async Task <ImportStats> _doPagedJob(string uri, Func <XDocument, Task <ImportStats> > processor) { var pages = await RedmineXmlImporter.GetPageList(uri, _limit); var uriList = new List <string>(); foreach (var page in pages) { uriList.Add(uri + $"&limit={_limit}&offset={page * _limit}"); } return(await _doJob(uriList, processor)); }
/// <summary> /// Starts received-processor job for passed list of uris /// </summary> /// <param name="uriList">Uri list to fetch and process</param> /// <param name="processor">Processor function for fetched xml</param> /// <returns>Child elements in received xml</returns> private async Task <ImportStats> _doJob(List <string> uriList, Func <XDocument, Task <ImportStats> > processor) { var result = new ImportStats(); var receiveBlock = new TransformBlock <string, XDocument>( async(uri) => { XDocument xDoc = null; try { xDoc = await RedmineXmlImporter.ReceiveXmlAsync(uri, 0, 0); } catch (Exception e) { var we = e as WebException; if (we == null) { throw; } var resp = we.Response as HttpWebResponse; if (resp?.StatusCode != HttpStatusCode.Forbidden) { throw; } // we've got 403 responsse. It's a project with disabled something. } return(xDoc); }, _opts ); var processBlock = new ActionBlock <XDocument>( async xDoc => { if (xDoc == null) { return; } result.Add(await processor(xDoc)); } ); receiveBlock.LinkTo(processBlock, new DataflowLinkOptions { PropagateCompletion = true }); foreach (var uri in uriList) { receiveBlock.Post(uri); } receiveBlock.Complete(); await receiveBlock.Completion; _logger.LogDebug($"Received all. {processBlock.InputCount} pages waiting for process"); await processBlock.Completion; return(result); }
/// <summary> /// Imports issues priorities /// </summary> /// <returns></returns> private async Task <ImportStats> _importIssuePriorities() { _logger.LogDebug($"Priorities import. Start."); using var client = RedmineXmlImporter.CreateWebClient(); var uri = new Uri($"{_redmineUrl}enumerations/issue_priorities.json?key={_redmineApiKey}"); var json = await client.DownloadStringTaskAsync(uri); var result = new ImportStats(); var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var list = JsonSerializer.Deserialize <Models.RedmineJson.IssuePriorityResponse>(json, opts); // priorities are sorted from lowest to highest list.PriorityList.Reverse(); using var scope = _services.CreateScope(); using var _db = scope.ServiceProvider.GetRequiredService <DB>(); var prios = _db.IssuePriority.ToList(); _logger.LogDebug($"priorities"); result.total = list.PriorityList.Count; foreach (var item in list.PriorityList) { var dbItem = prios.FirstOrDefault(d => d.Id == item.Id); if (dbItem == null) { dbItem = new Redmine.Models.IssuePriority { Id = item.Id, Sort = list.PriorityList.IndexOf(item) + 1, // 1 .. 5 Created = DateTimeOffset.Now }; dbItem.Name = item.Name; var code = $"prio{dbItem.Sort}"; if (!prios.Any(d => d.Code == code)) { dbItem.Code = code; } await _db.AddAsync(dbItem); result.added++; } if (_db.Entry(dbItem).State != EntityState.Unchanged) { dbItem.Updated = DateTimeOffset.Now; result.updated++; } } await _db.SaveChangesAsync(); _logger.LogInformation($"Priorities import. Finish. {list.PriorityList.Count} processed."); return(result); }