private void ProcessRange(ProcessingInstruction processingInstruction) { var sqlParameters = new SqlParameter[2]; var pair = processingInstruction.Range.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries); sqlParameters[0] = new SqlParameter("@PIDRangeStart", Convert.ToInt32(pair[0])); sqlParameters[1] = new SqlParameter("@PIDRangeEnd ", Convert.ToInt32(pair[1])); var identifier = string.Format("{0}_{1}_{2}", processingInstruction.Catalog, sqlParameters[0].Value, sqlParameters[1].Value); try { var startDt = DateTime.Now; Log.DebugFormat("[{0}] start", identifier); WriteFeed(processingInstruction.Catalog, processingInstruction.Dbcmd, sqlParameters, identifier, processingInstruction.CatalogAttributesSection); var endDt = DateTime.Now; var execTime = endDt - startDt; Log.InfoFormat("[{0}] completed. Execution time in seconds: {1}", identifier, execTime.TotalSeconds); _executionLogLogger.AddFileGenerationUpdate(PlaRelatedFeedUtils.GetFeedFileName(identifier), true); } catch (Exception ex) { Log.InfoFormat("[Feed] {0}; error {1}", processingInstruction.Catalog + "-" + pair[0] + pair[1], ex); _executionLogLogger.AddFileGenerationUpdate(PlaRelatedFeedUtils.GetFeedFileName(identifier), false); if (HasTwoGenerators()) { _executionLogLoggerSecondary.AddFileGenerationUpdate(PlaRelatedFeedUtils.GetFeedFileName(identifier), false); } _hasError = true; } }
/// <summary> /// generates xml file(s) and archives it using gzip library /// </summary> /// <param name="catalog">catalog from which data is retrieved</param> /// <param name="reader">datareader</param> /// <param name="identifier">feed identifier used for file name</param> /// <param name="configSection">name of the configuration section containing catalog attributes</param> private void WriteFeedFiles(string catalog, IDataReader reader, string identifier, string configSection) { var dict = ConfigurationManager.GetSection(configSection) as StringDictionary; var feedFilePath = PlaRelatedFeedUtils.GetFeedFilePath(identifier, false, OutputFolderPath); Log.DebugFormat("[WriteFeedFile] {0} start", feedFilePath); //create gzip archive stream if (GzipFiles) { var gZipStream = new GZipStream(File.Create(feedFilePath + ".gz"), CompressionMode.Compress); var xmlWriter = XmlWriter.Create(gZipStream, new XmlWriterSettings { Indent = true }); FeedXmlElement(xmlWriter, reader, dict, catalog, identifier, feedFilePath); xmlWriter.Close(); gZipStream.Close(); } else { var xmlWriter = XmlWriter.Create(feedFilePath, new XmlWriterSettings { Indent = true }); FeedXmlElement(xmlWriter, reader, dict, catalog, identifier, feedFilePath); xmlWriter.Close(); } }
private XElement EntryAttribute(IDataReader reader , string gId , string sanitizedTitle, string sanitizedDescription, string googleProductCategory , string linkCatalog , string linkSku , decimal regularPrice , decimal?salePrice , string gAvailability //, string availability , string gtin , string gBrandName, bool hasBrandName, string defaultBreadcrumb, IEnumerable <string> allBreadcrumbs, string cpcValue, string customLabel0, string customLabel1, string customLabel2, string customLabel3, string customLabel4, GoogleRunFeedType feedType) { var googleNs = (feedType == GoogleRunFeedType.Google) ? FeedXmlsnsG : FeedXmlns; //entry var entry = new XElement(FeedXmlns + "entry"); //g:id var gaId = new XElement(googleNs + "id", gId); entry.Add(gaId); //title var aTitle = new XElement(FeedXmlns + "title", new XCData(sanitizedTitle)); entry.Add(aTitle); //description var aDescription = new XElement(FeedXmlns + "description", new XCData(sanitizedDescription)); entry.Add(aDescription); //<g:google_product_category> var gaGoogleProductCategory = new XElement(googleNs + "google_product_category", googleProductCategory); entry.Add(gaGoogleProductCategory); //write optional product type - NOT required by google foreach (var breadcrumb in allBreadcrumbs.Take(MaximumBreadcrumbsToSend)) { var aProductType = new XElement(googleNs + "product_type", breadcrumb); entry.Add(aProductType); } //link var url = PlaRelatedFeedUtils.GetFeedEntryLinkValue(reader, linkCatalog, linkSku); if (feedType == GoogleRunFeedType.Yahoo) { url += YahooItemPageUrlSuffix; } var aLink = PlaRelatedFeedUtils.FeedEntryLink(url); entry.Add(aLink); //g:image_link var aImgLink = PlaRelatedFeedUtils.EntryImageLink(reader, linkSku, googleNs, feedType); entry.Add(aImgLink); //g:condition var gaCondition = new XElement(googleNs + "condition", "new"); entry.Add(gaCondition); //google search availability var gaAvailability = new XElement(googleNs + "availability", gAvailability); entry.Add(gaAvailability); //g:price, g:sale_price, sale_price_effective_date if (DisplaySalePriceInfo) { var gaPrice = new XElement(googleNs + "price", regularPrice.ToString("F", CultureInfo.InvariantCulture) + " CAD"); entry.Add(gaPrice); if (salePrice.HasValue) { var gaSalePrice = new XElement(googleNs + "sale_price", salePrice.Value.ToString("F", CultureInfo.InvariantCulture) + " CAD"); entry.Add(gaSalePrice); if (DisplaySalePriceTimeSpanInfo) { var time = DateTime.Now.AddDays(SalePriceInfoTimeSpanBegin); const string timeToStringFormat = "yyyy-MM-ddTHH:mmK"; entry.Add(new XElement(googleNs + "sale_price_effective_date", string.Format("{0}/{1}", time.ToString(timeToStringFormat), time.AddDays(SalePriceInfoTimeSpanEnd).ToString(timeToStringFormat)))); } } } else { var gaPrice = new XElement(googleNs + "price", regularPrice.ToString("F", CultureInfo.InvariantCulture) + " CAD"); entry.Add(gaPrice); } // Items to be sent only to Google if (feedType == GoogleRunFeedType.Google) { if (DisplayDefaultShoppingInfo) { //g:shipping var gShipping = PlaRelatedFeedUtils.ShippingAttribute(null, null, null, googleNs); entry.Add(gShipping); } //write mandatory brand - required by google - only for items under Apparel & Accessories // (if brand name isn't available, set identifier_exists to false --> Unique Product Identifiers section) // https://support.google.com/merchants/answer/188494?hl=en&ref_topic=3404778 if (hasBrandName && !string.IsNullOrWhiteSpace(gBrandName)) { entry.Add(new XElement(googleNs + "brand", gBrandName.Trim())); } //google search unique product identifiers -> g:gtin var gaGtin = new XElement(googleNs + "gtin", gtin); entry.Add(gaGtin); // Add the adwords_redirect node if (!string.IsNullOrWhiteSpace(GoogleAdwordsRedirectUrlSuffix)) { entry.Add(new XElement(googleNs + "adwords_redirect", url + GoogleAdwordsRedirectUrlSuffix)); } else { entry.Add(new XElement(googleNs + "adwords_redirect", url)); } entry.Add(new XElement(googleNs + "adwords_grouping", defaultBreadcrumb)); if (!string.IsNullOrWhiteSpace(customLabel0)) { entry.Add(new XElement(googleNs + "custom_label_0", customLabel0)); } if (!string.IsNullOrWhiteSpace(customLabel1)) { entry.Add(new XElement(googleNs + "custom_label_1", customLabel1)); } if (!string.IsNullOrWhiteSpace(customLabel2)) { entry.Add(new XElement(googleNs + "custom_label_2", customLabel2)); } if (!string.IsNullOrWhiteSpace(customLabel3)) { entry.Add(new XElement(googleNs + "custom_label_3", customLabel3)); } if (!string.IsNullOrWhiteSpace(customLabel4)) { entry.Add(new XElement(googleNs + "custom_label_4", customLabel4)); } } if (feedType == GoogleRunFeedType.Yahoo) { entry.Add(new XElement(FeedXmlns + "cpc", cpcValue)); } return(entry); }
private void FeedXmlElement(XmlWriter xmlWriter, XmlWriter xmlWriterSecondary, IDataReader reader, StringDictionary dict, string catalog, string identifier, string feedFilePath, ref Tuple <int, int, int> counter) { if (counter == null) { throw new ArgumentNullException("counter"); } var countProcessed = 0; var countError = 0; var countSkipped = 0; var hasTwoGenerators = HasTwoGenerators(); var time = DateTime.Now; PlaRelatedFeedUtils.StartXmlDocument(xmlWriter, _runnerFeed.GetRunFeedType(), time); if (hasTwoGenerators) { PlaRelatedFeedUtils.StartXmlDocument(xmlWriterSecondary, _runnerFeedSecondary.GetRunFeedType(), time); } //<entry> while (reader.Read()) { Log.DebugFormat("{0}::Processing record [{1}]: {2}", identifier, (countProcessed + countError), reader["PID"]); var id = reader[dict["gId"]].ToString(); var title = reader[dict["title"]].ToString(); try { // First check if the product is a sensitive product, and if so, exclude if (dict.ContainsKey("isSensitiveProduct") && int.Parse(reader[dict["isSensitiveProduct"]].ToString()) > 0) { countSkipped++; continue; } var linkSku = reader[dict["linkSku"]].ToString(); var brandName = !dict.ContainsKey("gBrand") ? string.Empty : reader[dict["gBrand"]].ToString(); var cContributors = PlaRelatedFeedUtils.ContributorAttributes(dict, reader, id); var contributor = cContributors ?? null; var defaultCategory = FeedUtils.GetFeedGeneratorIndigoCategory(_feedGeneratorCategoryService, reader, dict, catalog, Log); var productData = FeedUtils.GetProductData(dict, reader, linkSku, catalog, brandName, contributor, defaultCategory); var formatString = dict.ContainsKey("bisacbindingtypeid") ? FeedUtils.GetFormat(reader[dict["bisacbindingtypeid"]].ToString(), id) : string.Empty; var sanitizedTitle = string.IsNullOrWhiteSpace(title) ? string.Empty : FeedUtils.SanitizeString(title); var gAvailability = !dict.ContainsKey("gAvailability") ? FeedUtils.GetGoogleAvailability(1) : FeedUtils.GetGoogleAvailability((int)reader[dict["gAvailability"]]); var availability = !dict.ContainsKey("gAvailability") ? 1 : (int)reader[dict["gAvailability"]]; var recordType = !dict.ContainsKey("recordType") ? string.Empty : reader[dict["recordType"]].ToString(); var hasImage = true; if (!SkipHasImageCheck) { hasImage = (!dict.ContainsKey("hasImage")) || int.Parse(reader[dict["hasImage"]].ToString()) > 0; } string message; var isExcludedDueToData = IndigoBreadcrumbRepositoryUtils.IsExcludedDueToData(_feedId, sanitizedTitle, hasImage, availability, recordType, false, out message); // First determine if the entries are to be excluded or not var isEntryExcluded = isExcludedDueToData || _runnerFeed.IsExcludedFromFeed(productData, false); var isEntrySecondaryExcluded = isExcludedDueToData; if (!isEntrySecondaryExcluded && hasTwoGenerators) { isEntrySecondaryExcluded = _runnerFeedSecondary.IsExcludedFromFeed(productData, false); } // If both entries are excluded, then there is no need to process anything further if ((isEntryExcluded && isEntrySecondaryExcluded) || (isEntryExcluded && !hasTwoGenerators)) { countSkipped++; continue; } var linkCatalog = dict["linkCatalog"]; if (MaxTitleLength > 0) { sanitizedTitle = FeedUtils.GetTruncatedTitle(sanitizedTitle, formatString, MaxTitleLength, ParameterUtils.GetParameter <bool>("GooglePlaFeedGenerator.TruncateTitle")); } var description = reader[dict["description"]].ToString(); description = string.IsNullOrWhiteSpace(description) ? sanitizedTitle : FeedUtils.SanitizeString(FeedUtils.RemoveHtmlTags(description)); var sanitizedDescription = string.IsNullOrWhiteSpace(description) ? sanitizedTitle : description; // Get the breadcrumb value var googleCategoryBreadcrumb = (string.IsNullOrWhiteSpace(defaultCategory.GoogleCategoryBreadcrumb)) ? dict["gGoogleProductCategory"] : defaultCategory.GoogleCategoryBreadcrumb; var allBreadcrumbs = (productData.BrowseCategoryIds.Any() && !productData.BrowseCategoryIds.Contains(-1)) ? new List <string>() : new List <string> { defaultCategory.Breadcrumb }; foreach (var browseCategoryId in productData.BrowseCategoryIds.Distinct()) { var categories = _feedGeneratorCategoryService.GetIndigoBreadcrumbCategories(browseCategoryId); if (categories != null) { foreach (var category in categories) { if (category.Crumbs[0].Equals(defaultCategory.Crumbs[0], StringComparison.OrdinalIgnoreCase)) { allBreadcrumbs.Add(category.Breadcrumb); } } } } decimal?salePrice = null; decimal parsedSalePrice; var regularPrice = decimal.Parse(reader[dict["price"]].ToString()); if (!string.IsNullOrEmpty(dict["adjustedPrice"]) && decimal.TryParse(reader[dict["adjustedPrice"]].ToString(), out parsedSalePrice)) { if (parsedSalePrice != regularPrice) { if (parsedSalePrice > regularPrice) { regularPrice = parsedSalePrice; } else { salePrice = parsedSalePrice; } } } var gtin = reader[dict["gGtin"]].ToString(); var isDefaultCpcValue = false; if (!isEntryExcluded) { var customLabel0 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_0); var customLabel1 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_1); var customLabel2 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_2); var customLabel3 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_3); var customLabel4 = PlaRelatedFeedUtils.GetCustomLabel4Value(_runnerFeed, productData, dict, reader); var cpcValue = PlaRelatedFeedUtils.GetCpcValue(_runnerFeed, productData, out isDefaultCpcValue); var entry = EntryAttribute(reader , id , sanitizedTitle, sanitizedDescription, googleCategoryBreadcrumb , linkCatalog, linkSku , regularPrice , salePrice , gAvailability//, availability , gtin , brandName, dict.ContainsKey("gBrand"), defaultCategory.Breadcrumb, allBreadcrumbs, cpcValue , customLabel0, customLabel1, customLabel2, customLabel3, customLabel4 , _runnerFeed.GetRunFeedType()); entry.WriteTo(xmlWriter); } if (hasTwoGenerators && !isEntrySecondaryExcluded) { var cpcValue = PlaRelatedFeedUtils.GetCpcValue(_runnerFeedSecondary, productData, out isDefaultCpcValue); var customLabel0 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_0); var customLabel1 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_1); var customLabel2 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_2); var customLabel3 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_3); var customLabel4 = PlaRelatedFeedUtils.GetCustomLabel4Value(_runnerFeed, productData, dict, reader); var entry = EntryAttribute(reader , id , sanitizedTitle, sanitizedDescription, googleCategoryBreadcrumb , linkCatalog, linkSku , regularPrice , salePrice , gAvailability , gtin , brandName, dict.ContainsKey("gBrand"), defaultCategory.Breadcrumb, allBreadcrumbs, cpcValue , customLabel0, customLabel1, customLabel2, customLabel3, customLabel4 , _runnerFeedSecondary.GetRunFeedType()); entry.WriteTo(xmlWriterSecondary); } if (isDefaultCpcValue) { _defaultCpcProductInfos.Add(new DefaultCpcProductInfo { Breadcrumb = defaultCategory.Breadcrumb, Sku = linkSku, Title = sanitizedTitle }); } countProcessed++; } catch (Exception e) { countError++; Log.ErrorFormat("Can't process the item. Id:{0};title:{1}", id, title); Log.DebugFormat("Error stack trace: {0}", e); _executionLogLogger.AddCustomMessage(string.Format("Can't process the item. Id: {0};title: {1}, file identifier: {2}", id, title, identifier)); if (HasTwoGenerators()) { _executionLogLoggerSecondary.AddCustomMessage(string.Format("Can't process the item. Id: {0};title: {1}, file identifier: {2}", id, title, identifier)); } if (!AllowItemErrorsInFiles) { _hasError = true; } } } PlaRelatedFeedUtils.EndXmlDocument(xmlWriter); if (hasTwoGenerators) { PlaRelatedFeedUtils.EndXmlDocument(xmlWriterSecondary); } counter = new Tuple <int, int, int>(countProcessed, countError, countSkipped); Log.InfoFormat("[WriteFeedFile] {0} completed Processed record count: {1}, Error record count: {2}, skipped record count: {3}", feedFilePath, countProcessed, countError, countSkipped); }
/// <summary> /// generates xml file(s) and archives it using gzip library /// </summary> /// <param name="catalog">catalog from which data is retrieved</param> /// <param name="reader">datareader</param> /// <param name="identifier">feed identifier used for file name</param> /// <param name="configSection">name of the configuration section containing catalog attributes</param> private void WriteFeedFiles(string catalog, IDataReader reader, string identifier, string configSection) { var dict = ConfigurationManager.GetSection(configSection) as StringDictionary; var feedFilePath = PlaRelatedFeedUtils.GetFeedFilePath(identifier, false, OutputFolderPath); var feedFilePathSecondary = (HasTwoGenerators()) ? PlaRelatedFeedUtils.GetFeedFilePath(identifier, true, AncillaryOutputFolderPath) : string.Empty; Log.DebugFormat("[WriteFeedFile] {0} start", feedFilePath); var countRec = new Tuple <int, int, int>(0, 0, 0); //create gzip archive stream if (GzipFiles) { var gZipStream = new GZipStream(File.Create(feedFilePath + ".gz"), CompressionMode.Compress); var xmlWriter = XmlWriter.Create(gZipStream, new XmlWriterSettings { Indent = true }); GZipStream gZipStreamSecondary = null; XmlWriter xmlWriterSecondary = null; if (HasTwoGenerators()) { gZipStreamSecondary = new GZipStream(File.Create(feedFilePathSecondary + ".gz"), CompressionMode.Compress); xmlWriterSecondary = XmlWriter.Create(gZipStreamSecondary, new XmlWriterSettings { Indent = true }); } FeedXmlElement(xmlWriter, xmlWriterSecondary, reader, dict, catalog, identifier, feedFilePath, ref countRec); xmlWriter.Close(); if (xmlWriterSecondary != null) { xmlWriterSecondary.Close(); } gZipStream.Close(); if (gZipStreamSecondary != null) { gZipStreamSecondary.Close(); } } else { var xmlWriter = XmlWriter.Create(feedFilePath, new XmlWriterSettings { Indent = true }); XmlWriter xmlWriterSecondary = null; if (HasTwoGenerators()) { xmlWriterSecondary = XmlWriter.Create(feedFilePathSecondary, new XmlWriterSettings { Indent = true }); } FeedXmlElement(xmlWriter, xmlWriterSecondary, reader, dict, catalog, identifier, feedFilePath, ref countRec); xmlWriter.Close(); if (xmlWriterSecondary != null) { xmlWriterSecondary.Close(); } } Log.DebugFormat("[WriteFeedFile] {0} complete. Total written records: {1}; Error records: {2}, skipped records: {3}.", feedFilePath, countRec.Item1, countRec.Item2, countRec.Item3); }
private void FeedXmlElement(XmlWriter xmlWriter, IDataReader reader, StringDictionary dict, string catalog, string identifier, string feedFilePath, ref Tuple <int, int, int> counter) { if (counter == null) { throw new ArgumentNullException("counter"); } var countProcessed = 0; var countError = 0; var countSkipped = 0; var dynamicMerchLabelProductCount = 0; XmlDataUtils.StartXmlDocument(xmlWriter); //<entry> while (reader.Read()) { Log.DebugFormat("{0}::Processing record [{1}]: {2}", identifier, (countProcessed + countError), reader["PID"]); var id = reader[dict["gId"]].ToString(); var title = reader[dict["title"]].ToString(); try { var sku = reader[dict["sku"]].ToString(); // Interesting business choice: for books, first author. For GM, real brand. var marinBrand = reader[dict["gMarinBrand"]].ToString(); var brandName = !dict.ContainsKey("gBrand") ? string.Empty : reader[dict["gBrand"]].ToString(); var cContributors = PlaRelatedFeedUtils.ContributorAttributes(dict, reader, id); var contributor = cContributors ?? null; var defaultCategory = FeedUtils.GetFeedGeneratorIndigoCategory(_feedGeneratorCategoryService, reader, dict, catalog, Log); var productData = FeedUtils.GetProductData(dict, reader, sku, catalog, brandName, contributor, defaultCategory); marinBrand = XmlDataUtils.GetBrand(marinBrand, catalog); var formatString = dict.ContainsKey("Format") ? FeedUtils.GetFormat(reader[dict["Format"]].ToString(), id) : string.Empty; var sanitizedTitle = (string.IsNullOrWhiteSpace(title)) ? string.Empty : FeedUtils.SanitizeString(string.IsNullOrWhiteSpace(formatString) ? title : $"{title}-{formatString}"); var quantity = reader[dict["quantity"]].ToString(); var gAvailability = !dict.ContainsKey("gAvailability") ? FeedUtils.GetGoogleAvailability(1) : FeedUtils.GetGoogleAvailability((int)reader[dict["gAvailability"]]); var availability = !dict.ContainsKey("gAvailability") ? 1 : (int)reader[dict["gAvailability"]]; var recordType = !dict.ContainsKey("recordType") ? string.Empty : reader[dict["recordType"]].ToString(); var hasImage = true; if (!SkipHasImageCheck) { hasImage = (!dict.ContainsKey("hasImage")) || int.Parse(reader[dict["hasImage"]].ToString()) > 0; } var size = !dict.ContainsKey("size") ? string.Empty : reader[dict["size"]].ToString(); var colour = !dict.ContainsKey("colour") ? string.Empty : reader[dict["colour"]].ToString(); var style = !dict.ContainsKey("style") ? string.Empty : reader[dict["style"]].ToString(); var scent = !dict.ContainsKey("scent") ? string.Empty : reader[dict["scent"]].ToString(); var flavour = !dict.ContainsKey("flavour") ? string.Empty : reader[dict["flavour"]].ToString(); var bindingType = !dict.ContainsKey("Format") ? string.Empty : FeedUtils.GetFormat(reader[dict["Format"]].ToString(), ""); var familyId = !dict.ContainsKey("FamilyId") ? string.Empty : reader[dict["FamilyId"]].ToString(); var dynamicMerchLabel = string.Empty; var hasLoadedDynamicMerchLabelData = false; string message; var isEntryExcluded = IndigoBreadcrumbRepositoryUtils.IsExcludedDueToData(GooglePlaFeedId, sanitizedTitle, hasImage, availability, recordType, false, out message); // If entry is excluded, then there is no need to process anything further if (isEntryExcluded) { // Business requested that any product that has a dynamic merch label value will be included in the feed, regardless of its data dynamicMerchLabel = PlaRelatedFeedUtils.GetDynamicMerchLabelValue(_runnerFeed, productData); hasLoadedDynamicMerchLabelData = true; if (string.IsNullOrWhiteSpace(dynamicMerchLabel)) { countSkipped++; continue; } } // At this point, check if the product is excluded from the feed due to an exclusion rule, if so, skip if (_runnerFeed.IsExcludedFromFeed(productData, false)) { countSkipped++; continue; } if (!hasLoadedDynamicMerchLabelData) { dynamicMerchLabel = PlaRelatedFeedUtils.GetDynamicMerchLabelValue(_runnerFeed, productData); } if (!string.IsNullOrWhiteSpace(dynamicMerchLabel)) { dynamicMerchLabelProductCount++; } var googleCategoryBreadcrumb = (string.IsNullOrWhiteSpace(defaultCategory.GoogleCategoryBreadcrumb)) ? dict["gGoogleProductCategory"] : defaultCategory.GoogleCategoryBreadcrumb; var customLabel0 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_0); var customLabel1 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_1); var customLabel2 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_2); var customLabel3 = PlaRelatedFeedUtils.GetCustomLabelValue(_runnerFeed, productData, FeedRuleType.Custom_Label_3); var customLabel4 = PlaRelatedFeedUtils.GetCustomLabel4Value(_runnerFeed, productData, dict, reader); var regularPrice = (decimal)reader[dict["price"]]; var effectivePrice = regularPrice; decimal?gSavingDollars = null; decimal?gSavingsPercentage = null; var adjustedPrice = string.IsNullOrEmpty(dict["adjustedPrice"]) ? "" : reader[dict["adjustedPrice"]].ToString(); var isOnSale = false; if (!string.IsNullOrWhiteSpace(adjustedPrice)) { decimal salePrice = Decimal.Parse(adjustedPrice); if (salePrice != regularPrice) { if (salePrice > regularPrice) { regularPrice = salePrice; } else { isOnSale = true; } effectivePrice = salePrice; } } // finally calculate the sale-related values if (isOnSale) { gSavingDollars = regularPrice - effectivePrice; gSavingsPercentage = 100 - effectivePrice * 100 / regularPrice; } var linkCatalog = dict["linkCatalog"]; var entry = EntryAttribute(reader , id , sanitizedTitle , sku , effectivePrice , gSavingDollars , gSavingsPercentage , gAvailability , marinBrand , quantity , linkCatalog, googleCategoryBreadcrumb, defaultCategory.Breadcrumb, customLabel0, customLabel1, customLabel2, customLabel3, customLabel4, dynamicMerchLabel , size, colour, style, scent, flavour, bindingType, familyId ); entry.WriteTo(xmlWriter); countProcessed++; } catch (Exception e) { countError++; var errorMessage = string.Format("Can't process the item. Id:{0};title:{1},catalog:{2},Message:{3}", id, title, catalog, e.Message); Log.Error(errorMessage); Log.DebugFormat("Error stack trace: {0}", e); _executionLogLogger.AddCustomMessage(string.Format("Can't process the item. Id: {0};title: {1}, file identifier: {2}", id, title, identifier)); if (!AllowItemErrorsInFiles) { _hasError = true; } } } XmlDataUtils.EndXmlDocument(xmlWriter); counter = new Tuple <int, int, int>(countProcessed, countError, countSkipped); AddToDynamicMerchProductCount(dynamicMerchLabelProductCount); Log.InfoFormat("[WriteFeedFile] {0} completed Processed record count: {1}, Error record count: {2}, skipped record count: {3}", feedFilePath, countProcessed, countError, countSkipped); }
private void FeedXmlElement(XmlWriter xmlWriter, IDataReader reader, StringDictionary dict, string catalog, string identifier, string feedFilePath) { var counter = new ProcessingCounters(); var time = _effectiveFromTime.HasValue ? _effectiveFromTime.Value : DateTime.Now; PlaRelatedFeedUtils.StartXmlDocument(xmlWriter, GoogleRunFeedType.Google, time); //<entry> while (reader.Read()) { Log.DebugFormat("{0}::Processing record [{1}]: {2}", identifier, (counter.GetTotalProcessed()), reader["PID"]); var id = reader[dict["gId"]].ToString(); var title = reader[dict["title"]].ToString(); try { var haveExclusionRulesChanged = _runnerFeedRulesHelper.HaveExclusionRulesChanged(); var sku = reader[dict["sku"]].ToString(); var brandName = !dict.ContainsKey("gBrand") ? string.Empty : reader[dict["gBrand"]].ToString(); var cContributors = PlaRelatedFeedUtils.ContributorAttributes(dict, reader, id); var contributor = cContributors ?? null; var defaultCategory = FeedUtils.GetFeedGeneratorIndigoCategory(_feedGeneratorCategoryService, reader, dict, catalog, Log); var productData = FeedUtils.GetProductData(dict, reader, sku, catalog, brandName, contributor, defaultCategory); var sanitizedTitle = (string.IsNullOrWhiteSpace(title)) ? string.Empty : FeedUtils.SanitizeString(title); var gAvailability = !dict.ContainsKey("gAvailability") ? FeedUtils.GetGoogleAvailability(1) : FeedUtils.GetGoogleAvailability(int.Parse(reader[dict["gAvailability"]].ToString())); var availability = !dict.ContainsKey("gAvailability") ? 1 : int.Parse(reader[dict["gAvailability"]].ToString()); var recordType = !dict.ContainsKey("recordType") ? string.Empty : reader[dict["recordType"]].ToString(); var hasImage = true; if (!SkipHasImageCheck) { hasImage = (!dict.ContainsKey("hasImage")) || int.Parse(reader[dict["hasImage"]].ToString()) > 0; } string message; var isExcluded = false; if (_isIncrementalRun) { var statusId = int.Parse(reader["StatusId"].ToString()); switch (statusId) { // New product case 1: counter.NumberOfNew++; continue; // Unchanged product case 3: if (!haveExclusionRulesChanged) { Log.DebugFormat("Product with id {0} is skipped in incremental mode as it wasn't modified and the rules haven't changed.", id); counter.NumberOfUnchanged++; continue; } var oldExclusionResult = _runnerFeedRulesHelper.IsExcludedFromFeed(productData, true); var currentExclusionResult = _runnerFeedRulesHelper.IsExcludedFromFeed(productData, false); if (oldExclusionResult == currentExclusionResult) { Log.DebugFormat("Product with id {0} is skipped in incremental mode as it wasn't modified, rules had changed but exclusion rule evaluation's result remained the same.", id); counter.NumberOfUnchanged++; continue; } // If the product is excluded at the moment, then perform the "exclusion logic" per business requirements, // otherwise (i.e. product is included at the moment, treat it as "new") if (!currentExclusionResult) { Log.DebugFormat("Product with id {0} is marked as new and skipped in incremental mode as it wasn't modified, rules had changed but exclusion rule evaluation's result changed and currently product isn't excluded.", id); counter.NumberOfNew++; continue; } Log.DebugFormat("Product with id {0} is marked as excluded in incremental mode as it wasn't modified, rules had changed but exclusion rule evaluation's result changed and currently product is being excluded.", id); isExcluded = true; break; // Modified product case 2: var isEntryExcluded = IndigoBreadcrumbRepositoryUtils.IsExcludedDueToData(GooglePlaFeedId, sanitizedTitle, hasImage, availability, recordType, false, out message); if (isEntryExcluded) { Log.DebugFormat("Product with id {0} is marked as excluded in incremental mode as it was modified, and it failed the data requirements for inclusion.", id); isExcluded = true; break; } // If product was excluded from the feed due to rules, then mark it as excluded if (_runnerFeedRulesHelper.IsExcludedFromFeed(productData, false)) { Log.DebugFormat("Product with id {0} is marked as excluded in incremental mode as it was modified, and it's matching one of the exclusion rules.", id); isExcluded = true; } break; default: throw new ApplicationException("Invalid StatusId during an incremental run."); } } else { isExcluded = IndigoBreadcrumbRepositoryUtils.IsExcludedDueToData(GooglePlaFeedId, sanitizedTitle, hasImage, availability, recordType, false, out message); if (isExcluded) { Log.DebugFormat("Product with id {0} is marked as excluded in full mode as it failed the data requirements for inclusion.", id); } if (!isExcluded) { isExcluded = _runnerFeedRulesHelper.IsExcludedFromFeed(productData, false); } if (isExcluded) { Log.DebugFormat("Product with id {0} is marked as excluded in full mode as it's matching one of the exclusion rules.", id); } } // At this point, we know if the product is excluded or not, regardless of which type of run is being executed. // If we aren't supposed to be sending excluded product data, then update the skipped counter and exit if (isExcluded) { counter.NumberOfExcluded++; if (!SendExcludedProductData) { Log.Debug("Skipped the product because it was excluded."); continue; } gAvailability = ExcludedProductGoogleAvailabilityText; } var regularPrice = (decimal)reader[dict["price"]]; var adjustedPrice = string.IsNullOrEmpty(dict["adjustedPrice"]) ? "" : reader[dict["adjustedPrice"]].ToString(); decimal?salePrice = null; if (!string.IsNullOrWhiteSpace(adjustedPrice)) { var salePriceFromDatabase = Decimal.Parse(adjustedPrice); if (salePriceFromDatabase != regularPrice) { if (salePriceFromDatabase > regularPrice) { regularPrice = salePriceFromDatabase; salePrice = null; } else { salePrice = salePriceFromDatabase; } } } var entry = EntryAttribute(id, regularPrice, salePrice, gAvailability); entry.WriteTo(xmlWriter); counter.NumberOfProcessed++; } catch (Exception e) { counter.NumberOfErrored++; var errorMessage = string.Format("Can't process the item. Id:{0};title:{1},catalog:{2},Message:{3}", id, title, catalog, e.Message); Log.Error(errorMessage); Log.DebugFormat("Error stack trace: {0}", e); _executionLogLogger.AddCustomMessage(string.Format("Can't process the item. Id: {0};title: {1}, file identifier: {2}", id, title, identifier)); if (_isIncrementalRun && !AllowItemErrorsInFiles) { _hasError = true; } } } // If the setting for sending deleted products is set to true in an incremental run, then get the deleted products since the last run // and send them as the "special" deleted products, i.e. pid + availability of "out of stock" if (_isIncrementalRun && SendExcludedProductData) { AddDeletedProducts(xmlWriter, identifier, ref counter); } PlaRelatedFeedUtils.EndXmlDocument(xmlWriter); var infoLogMessage = string.Format("[WriteFeedFile] {0} completed processed record count: {1}, error record count: {2}, unchanged record count: {3}, " + "new record count: {4}, excluded record count: {5}, deleted record count: {6}. ", feedFilePath, counter.NumberOfProcessed, counter.NumberOfErrored, counter.NumberOfUnchanged, counter.NumberOfNew, counter.NumberOfExcluded, counter.NumberOfDeleted); if (SendExcludedProductData) { infoLogMessage += "Excluded produces were included in processed count."; } Log.Info(infoLogMessage); }