/// Generates a XML entry for PUT STOCK for a given article. /// <param name="card">The card. Must have idArticle, price, idLanguage, count and condition set.</param> /// <param name="sNewPrice">The new price to set.</param> /// <returns>The body of the API request.</returns> public static string ChangeStockArticleBody(MKMMetaCard card, string sNewPrice) { var XMLContent = "<article>" + "<idArticle>" + card.GetAttribute(MCAttribute.ArticleID) + "</idArticle>" + "<price>" + sNewPrice + "</price>" + "<idLanguage>" + card.GetAttribute(MCAttribute.LanguageID) + "</idLanguage>" + new XElement("comments", card.GetAttribute(MCAttribute.Comments)) + // to escape special characters (&, <, > etc.) "<count>" + card.GetAttribute(MCAttribute.Count) + "</count>" + "<condition>" + card.GetAttribute(MCAttribute.Condition) + "</condition>" + "</article>"; return(XMLContent); }
/// <summary> /// Determines whether the specified other card has the minimum required condition by this card (attribute MinCondition). /// </summary> /// <param name="otherCard">The other card.</param> /// <returns> /// <c>True</c> if the specified other card has Condition at least == this.MinCondition, /// <c>False</c> if it does not and <c>Any</c> if either Condition is not assigned for the other card or MinCondition is not assigned for this card. /// </returns> public Bool3 IsOfMinCondition(MKMMetaCard otherCard) { string minC = GetAttribute(MCAttribute.MinCondition); if (minC != "") { string otherC = otherCard.GetAttribute(MCAttribute.Condition); if (otherC != "") { bool res = IsBetterOrSameCondition(otherC, minC); return(res ? Bool3.True : Bool3.False); } } return(Bool3.Any); }
/// Generates a XML entry for POST STOCK for a given article, i.e. uploading a new article to stock. /// <param name="card">The card. Must have idProduct, MKMPrice, LanguageID, Count and Condition set.</param> /// <param name="sNewPrice">The new price to set.</param> /// <returns>The body of the API request.</returns> public static string PostStockArticleBody(MKMMetaCard card) { string isFoil = card.GetAttribute(MCAttribute.Foil); string isSigned = card.GetAttribute(MCAttribute.Signed); string isPlayset = card.GetAttribute(MCAttribute.Playset); string isAltered = card.GetAttribute(MCAttribute.Altered); var XMLContent = "<article>" + "<idProduct>" + card.GetAttribute(MCAttribute.ProductID) + "</idProduct>" + "<idLanguage>" + card.GetAttribute(MCAttribute.LanguageID) + "</idLanguage>" + new XElement("comments", card.GetAttribute(MCAttribute.Comments)) + // to escape special characters (&, <, > etc.) "<count>" + card.GetAttribute(MCAttribute.Count) + "</count>" + "<price>" + card.GetAttribute(MCAttribute.MKMPrice) + "</price>" + "<condition>" + card.GetAttribute(MCAttribute.Condition) + "</condition>" + (isFoil == "" ? "" : ("<isFoil>" + isFoil + "</isFoil>")) + (isSigned == "" ? "" : ("<isSigned>" + isSigned + "</isSigned>")) + (isPlayset == "" ? "" : ("<isPlayset>" + isPlayset + "</isPlayset>")) + (isAltered == "" ? "" : ("<isAltered>" + isAltered + "</isAltered>")) + "</article>"; return(XMLContent); }
// reload data each time the form is made visible in case the user's stock has changed so they can reload the stockview this way private void StockView_VisibleChanged(object sender, EventArgs e) { if (Visible) { int start = 1; var articles = new DataTable(); try { while (true) { var doc = MKMInteract.RequestHelper.readStock(start); if (doc.HasChildNodes) { var result = doc.GetElementsByTagName("article"); int elementCount = 0; foreach (XmlNode article in result) { if (article["condition"] != null) // is null for articles that are not cards (boosters etc.) - don't process those { MKMMetaCard m = new MKMMetaCard(article); m.WriteItselfIntoTable(articles, true, MCFormat.MKM); elementCount++; } } if (elementCount != 100) { break; } start += elementCount; } else { break; // document is empty -> end*/ } } // Remove columns we don't want showing // TODO - what is and isn't shown should probably be customizable and left to the user to choose in some way articles.Columns.Remove(MCAttribute.ArticleID); articles.Columns.Remove(MCAttribute.ProductID); articles.Columns.Remove(MCAttribute.LanguageID); articles.Columns.Remove(MCAttribute.CardNumber); var dj = MKMDbManager.JoinDataTables(articles, MKMDbManager.Instance.Expansions, (row1, row2) => row1.Field <string>(MKMDbManager.InventoryFields.ExpansionID) == row2.Field <string>(MKMDbManager.ExpansionsFields.ExpansionID)); dj.Columns.Remove(MCAttribute.ExpansionID); // duplicated dj.Columns.Remove(MKMDbManager.ExpansionsFields.ExpansionID); // ...and we don't want it anyway dj.Columns.Remove(MKMDbManager.ExpansionsFields.Name); // duplicated dj.Columns[dj.Columns.IndexOf(MCAttribute.Name)].SetOrdinal(0); dj.Columns[dj.Columns.IndexOf(MCAttribute.Expansion)].SetOrdinal(1); dj.Columns[dj.Columns.IndexOf(MCAttribute.Language)].SetOrdinal(2); stockGridView.DataSource = dj; buttonExport.Enabled = true; } catch (Exception eError) { MKMHelpers.LogError("listing stock in Stock View", eError.Message, true); } } }
/// <summary> /// Imports and processes the input CSV file, i.e. fills the internal MetaCard hash tables and fetches product info from MKM if necessary. /// Intended to run in a separate thread. /// </summary> /// <param name="filePath">The file path.</param> private void importRun(string filePath, string defaultFoil, string defaultPlayset, string defaultSigned, string defaultAltered, string defaultCondition, string defaultExpansion, string defaultLanguageID) { DataTable dt; try { dt = MKMCsvUtils.ConvertCSVtoDataTable(filePath); } catch (Exception eError) { LogError("importing file " + filePath, eError.Message, true); return; } importedColumns = dt.Columns; MainView.Instance.LogMainWindow("Loaded file with " + dt.Rows.Count + " articles, processing..."); importedAll.Clear(); importedValidOnly.Clear(); int counter = 0, failed = 0, hasPriceGuide = 0; // if we search for products based on their locName, we have to make a product query for each of them - store the result to reuse in case there are more of those cards later in the list Dictionary <string, string> locNameProducts = new Dictionary <string, string>(); foreach (DataRow row in dt.Rows) { MKMMetaCard mc = new MKMMetaCard(row); importedAll.Add(mc); // add it no matter if it can be correctly processed or not so that we can export it counter++; string productID = mc.GetAttribute(MCAttribute.ProductID); string name = mc.GetAttribute(MCAttribute.Name); string languageID = mc.GetAttribute(MCAttribute.LanguageID); if (languageID == "" && defaultLanguageID != "") { languageID = defaultLanguageID; mc.SetLanguageID(languageID); } if (name == "" && productID == "") // we have neither name or productID - we have to hope we have locName and language { string locName = mc.GetAttribute(MCAttribute.LocName).ToLower(); if (locName == "" || languageID == "") { LogError("importing line #" + (counter + 1) + ", article will be ignored", "Neither product ID, English name, or localized name + language was found, cannot identify the card.", false); failed++; continue; } // have to use search on MKM to get the English name string hash = "" + locName + languageID; // technically it is unlikely that two different languages would have the same name, but could happen if (!locNameProducts.TryGetValue(hash, out name)) // we haven't had a product like this in the list yet, use MKM API to find it { int start = 0; List <XmlNode> found = new List <XmlNode>(); try { XmlNodeList products; do { XmlDocument doc = MKMInteract.RequestHelper.findProducts(locName, languageID, start); products = doc.GetElementsByTagName("product"); // we still want to insert empty string in the hash table - this way we know in the future that this name is invalid locNameProducts[hash] = ""; foreach (XmlNode product in products) { // only take exact matches, otherwise we get all kinds of garbage like sleeves etc. that use the name of the card if (product["locName"].InnerText.ToLower() == locName) { found.Add(product); } } start += products.Count; } while (products.Count == 100); } catch (Exception eError) { LogError("importing line #" + (counter + 1) + ", trying to find product by its localized name " + locName + ", article will be ignored", eError.Message, false); failed++; continue; } if (found.Count < 1) { LogError("importing line #" + (counter + 1) + ", trying to find product by its localized name " + locName + ", article will be ignored", "No article called " + locName + " in " + mc.GetAttribute(MCAttribute.Language) + " language found on MKM.", false); failed++; continue; } locNameProducts[hash] = name = found[0]["enName"].InnerText; mc.SetAttribute(MCAttribute.Name, name); } else if (name != "") { mc.SetAttribute(MCAttribute.Name, name); } else { LogError("importing line #" + (counter + 1) + ", trying to find product by its localized name " + locName + ", article will be ignored", "" + locName + " is not a valid name", false); failed++; continue; } } // process foil and condition now as it can be useful in determining expansion string temp = mc.GetAttribute(MCAttribute.Foil); Bool3 isFoil; if (temp == "") { mc.SetBoolAttribute(MCAttribute.Foil, defaultFoil); isFoil = ParseBool3(mc.GetAttribute(MCAttribute.Foil)); } else { isFoil = ParseBool3(temp); } string condition = mc.GetAttribute(MCAttribute.Condition); if (condition == "") { condition = defaultCondition; mc.SetCondition(condition); } if (productID == "") // we now know we have the name, but we have to find out which expansion it is from to get the productID { string expID = mc.GetAttribute(MCAttribute.ExpansionID); // if the Expansion would be set, ExpansionID would be set as well in constructor of MKMMetaCard if (expID == "") // we have to determine the expansion { var all = MKMDbManager.Instance.GetCardByName(name); // options are: Latest, Oldest, Cheapest, Median Price, Most Expensive if (all.GetEnumerator().MoveNext()) { // used for prices based on price guide (cheapest, median, most expensive): // for non-foil, compare the LOWEX+ for EX+ items, LOW for worse conditions, for foil compare the LOWFOIL, for "any foil" compare SELL string priceGuidePrice; if (isFoil == Bool3.True) { priceGuidePrice = "LOWFOIL"; } else if (isFoil == Bool3.Any) { priceGuidePrice = "SELL"; } else { if (IsBetterOrSameCondition(condition, "EX")) { priceGuidePrice = "LOWEX+"; } else { priceGuidePrice = "LOW"; } } switch (defaultExpansion) { // for latest and oldest, we can just check local database case "Latest": DateTime latestTime = new DateTime(0); foreach (DataRow dr in all) { string tempExpID = dr[MKMDbManager.InventoryFields.ExpansionID].ToString(); string releaseDate = MKMDbManager.Instance.GetExpansionByID( tempExpID)[MKMDbManager.ExpansionsFields.ReleaseDate].ToString(); DateTime rel = DateTime.Parse(releaseDate, CultureInfo.InvariantCulture); if (latestTime < rel) { latestTime = rel; expID = tempExpID; } } mc.SetAttribute(MCAttribute.ExpansionID, expID); mc.SetAttribute(MCAttribute.Expansion, MKMDbManager.Instance.GetExpansionByID(expID)[MKMDbManager.ExpansionsFields.Name].ToString()); break; case "Oldest": DateTime oldestTime = DateTime.Now; foreach (DataRow dr in all) { string tempExpID = dr[MKMDbManager.InventoryFields.ExpansionID].ToString(); string releaseDate = MKMDbManager.Instance.GetExpansionByID( tempExpID)[MKMDbManager.ExpansionsFields.ReleaseDate].ToString(); DateTime rel = DateTime.Parse(releaseDate, CultureInfo.InvariantCulture); if (oldestTime > rel) { latestTime = rel; expID = tempExpID; } } mc.SetAttribute(MCAttribute.ExpansionID, expID); mc.SetAttribute(MCAttribute.Expansion, MKMDbManager.Instance.GetExpansionByID(expID)[MKMDbManager.ExpansionsFields.Name].ToString()); break; // for the others we have to do product queries for each possibility case "Cheapest": XmlNode cheapestProduct = null; // we know all has at least one item so this will either get assigned or an exception is thrown and caught inside the foreach cycle double cheapestPrice = double.MaxValue; foreach (DataRow dr in all) { try { // there should always be exactly one product in the list XmlNode product = MKMInteract.RequestHelper.getProduct( dr[MKMDbManager.InventoryFields.ProductID].ToString()).GetElementsByTagName("product")[0]; double price = double.Parse(product["priceGuide"][priceGuidePrice].InnerText); if (price < cheapestPrice) { cheapestPrice = price; cheapestProduct = product; } } catch (Exception eError) { LogError("importing line #" + (counter + 1) + ", could not identify cheapest expansion for " + name + ", article will be ignored", eError.Message, false); failed++; continue; } } mc.FillProductInfo(cheapestProduct); hasPriceGuide++; break; case "Median Price": SortedList <double, XmlNode> prices = new SortedList <double, XmlNode>(); foreach (DataRow dr in all) { try { // there should always be exactly one product in the list XmlNode product = MKMInteract.RequestHelper.getProduct( dr[MKMDbManager.InventoryFields.ProductID].ToString()).GetElementsByTagName("product")[0]; double price = double.Parse(product["priceGuide"][priceGuidePrice].InnerText); prices.Add(price, product); } catch (Exception eError) { LogError("importing line #" + (counter + 1) + ", could not identify median price expansion for " + name + ", article will be ignored", eError.Message, false); failed++; continue; } } mc.FillProductInfo(prices.Values[prices.Count / 2]); hasPriceGuide++; break; case "Most Expensive": XmlNode mostExpProduct = null; // we know all has at least one item so this will either get assigned or an exception is thrown and caught inside the foreach cycle double highestPrice = double.MinValue; foreach (DataRow dr in all) { try { // there should always be exactly one product in the list XmlNode product = MKMInteract.RequestHelper.getProduct( dr[MKMDbManager.InventoryFields.ProductID].ToString()).GetElementsByTagName("product")[0]; double price = double.Parse(product["priceGuide"][priceGuidePrice].InnerText); if (price > highestPrice) { highestPrice = price; mostExpProduct = product; } } catch (Exception eError) { LogError("importing line #" + (counter + 1) + ", could not identify cheapest expansion for " + name + ", article will be ignored", eError.Message, false); failed++; continue; } } mc.FillProductInfo(mostExpProduct); hasPriceGuide++; break; } } else { LogError("importing line #" + (counter + 1) + ", identifying expansion for " + name + ", article will be ignored", "No card with this name found.", false); failed++; continue; } // TODO - determine whether the expansion is foil only / cannot be foil and based on the isFoil flag of the current article choose the correct set } // now we have expID and English name -> we can determine the product ID string[] ids = MKMDbManager.Instance.GetCardProductID(name, mc.GetAttribute(MCAttribute.ExpansionID)); if (ids.Length == 0) { LogError("importing line #" + (counter + 1) + ", article will be ignored", "The specified " + name + " and expansion ID " + expID + " do not match - product cannot be identified.", false); failed++; continue; } else if (ids.Length > 1) { string cardNumber = mc.GetAttribute(MCAttribute.CardNumber); if (cardNumber == "") { LogError("importing line #" + (counter + 1) + ", article will be ignored", "The specified " + name + " and expansion ID " + expID + " match multiple products - please provide Card Number to identify which one it is.", false); failed++; continue; } // search for the matching item int start = 0; try { XmlNodeList products; do { XmlDocument doc = MKMInteract.RequestHelper.findProducts(name, "1", start); products = doc.GetElementsByTagName("product"); string expansion = mc.GetAttribute(MCAttribute.Expansion); foreach (XmlNode product in products) { if (product["number"].InnerText == cardNumber && product["expansionName"].InnerText == expansion) { productID = product["idProduct"].InnerText; // since we already have it, why not fill the product info in the MetaCard mc.FillProductInfo(product); break; } } start += products.Count; } while (products.Count == 100 && productID == ""); } catch (Exception eError) { LogError("importing line #" + (counter + 1) + ", trying to find product ID for " + name + " based on its card number and expansion, article will be ignored", eError.Message, false); failed++; continue; } if (productID == "") { LogError("importing line #" + (counter + 1) + ", article will be ignored", "The specified " + name + " and expansion ID " + expID + " match multiple products, Card Number was used to find the correct article, but no match was found, verify the data is correct.", false); failed++; continue; } } else { productID = ids[0]; } mc.SetAttribute(MCAttribute.ProductID, productID); } // if the defaults are "Any", there is not point in checking whether that attribute has been set or not if (defaultPlayset != "") { temp = mc.GetAttribute(MCAttribute.Playset); if (temp == "") { mc.SetBoolAttribute(MCAttribute.Playset, defaultPlayset); } } if (defaultSigned != "") { temp = mc.GetAttribute(MCAttribute.Signed); if (temp == "") { mc.SetBoolAttribute(MCAttribute.Signed, defaultSigned); } } if (defaultAltered != "") { temp = mc.GetAttribute(MCAttribute.Altered); if (temp == "") { mc.SetBoolAttribute(MCAttribute.Altered, defaultAltered); } } // rarity might not be present in some cases, check it and get it from database, or worst case from MKM var rarity = mc.GetAttribute(MCAttribute.Rarity); if (rarity == "") { var dataRow = MKMDbManager.Instance.GetSingleCard(productID); rarity = dataRow[MKMDbManager.InventoryFields.Rarity].ToString(); if (rarity == "") { try { var productDoc = MKMInteract.RequestHelper.getProduct(productID); rarity = productDoc["response"]["product"]["rarity"].InnerText; dataRow[MKMDbManager.InventoryFields.Rarity] = rarity; } catch (Exception eError) { LogError("getting rarity for product " + productID, eError.Message, false); } } mc.SetAttribute(MCAttribute.Rarity, rarity); } importedValidOnly.Add(mc); if (checkBoxImportLog.Checked) { MainView.Instance.LogMainWindow("Imported line #" + (counter + 1) + ", " + name); } } MainView.Instance.LogMainWindow("Card list " + filePath + " imported. Successfully imported " + importedValidOnly.Count + " articles, failed to import: " + failed + ", articles that include MKM Price Guide: " + hasPriceGuide); if (hasPriceGuide > 0) { priceGuidesGenerated = true; } }
/// Returns all single cards in our stock as meta cards. This is just a convenience wrapper on getStockFile/readStock. /// <param name="useFile">This is for legacy support. If set to false, it will use the old way of getting stock /// by the readStock method. New way is to use getStockFile as it takes only a single API request.</param> /// <returns>List of all single cards in our stock</returns> public static List <MKMMetaCard> GetAllStockSingles(bool useFile) { MainView.Instance.LogMainWindow("Fetching stock..."); List <MKMMetaCard> cards = new List <MKMMetaCard>(); if (useFile) { foreach (var game in MainView.Instance.Config.Games) { byte[] stock = getStockFile(game.GameID); if (stock != null && stock.Length > 0) { var articleTable = MKMCsvUtils.ConvertCSVtoDataTable(stock); // the GET STOCK FILE has language ID named Language, fix that articleTable.Columns["Language"].ColumnName = MCAttribute.LanguageID; articleTable.Columns.Remove("Local Name"); // this is in the language of the request...which is always English foreach (DataRow row in articleTable.Rows) { MKMMetaCard mc = new MKMMetaCard(row); // according to the API documentation, "The 'condition' key is only returned for single cards. " // -> check if condition exists to see if this is a single card or something else if (mc.GetAttribute(MCAttribute.Condition) != "" && mc.GetAttribute(MCAttribute.ArticleID) != "") { // sanitize the false booleans - the empty ones mean no, while in MKMMEtaCard empty means "any" if (articleTable.Columns.Contains("Foil?") && mc.GetAttribute(MCAttribute.Foil) == "") { mc.SetBoolAttribute(MCAttribute.Foil, "false"); } if (articleTable.Columns.Contains("Altered?") && mc.GetAttribute(MCAttribute.Altered) == "") { mc.SetBoolAttribute(MCAttribute.Altered, "false"); } if (articleTable.Columns.Contains("Signed?") && mc.GetAttribute(MCAttribute.Signed) == "") { mc.SetBoolAttribute(MCAttribute.Signed, "false"); } if (articleTable.Columns.Contains("Playset?") && mc.GetAttribute(MCAttribute.Playset) == "") { mc.SetBoolAttribute(MCAttribute.Playset, "false"); } // this is just a guess, not sure how isFirstEd is written there, or if at all if (articleTable.Columns.Contains("FirstEd?") && mc.GetAttribute(MCAttribute.FirstEd) == "") { mc.SetBoolAttribute(MCAttribute.FirstEd, "false"); } cards.Add(mc); } } } } } else { var start = 1; XmlNodeList result; var count = 0; do { var doc = ReadStock(start); if (doc.HasChildNodes) { result = doc.GetElementsByTagName("article"); foreach (XmlNode article in result) { // according to the API documentation, "The 'condition' key is only returned for single cards. " // -> check if condition exists to see if this is a single card or something else if (article["condition"] != null && article["idArticle"].InnerText != null) { var card = new MKMMetaCard(article); cards.Add(card); } } count = result.Count; start += count; } } while (count == 100); } MainView.Instance.LogMainWindow("Finished fetching stock."); return(cards); }