public void initCardView() { cardView.Items.Clear(); cardView.View = View.Details; cardView.GridLines = true; cardView.FullRowSelect = true; cardView.BackColor = Color.WhiteSmoke; cardView.Columns.Add("ProductID", 60); cardView.Columns.Add("Card Name", 220); cardView.Columns.Add("Edition", 120); dj = new DataTable(); // note - we have expansions only for MTG cards, so non-mtg will not have the expansion name var inventorySinglesOnly = MKMDbManager.Instance.InventorySinglesOnly.CopyToDataTable(); dj = MKMDbManager.JoinDataTables(inventorySinglesOnly, MKMDbManager.Instance.Expansions, (row1, row2) => row1.Field <string>(MKMDbManager.InventoryFields.ExpansionID) == row2.Field <string>(MKMDbManager.ExpansionsFields.ExpansionID)); foreach (DataRow row in dj.Rows) { var item = new ListViewItem(row[MKMDbManager.InventoryFields.ProductID].ToString()); item.SubItems.Add(row[MKMDbManager.InventoryFields.Name].ToString()); item.SubItems.Add(row[MKMDbManager.ExpansionsFields.Name].ToString()); cardView.Items.Add(item); } }
private void buttonExport_Click(object sender, EventArgs e) { SaveFileDialog sf = new SaveFileDialog(); sf.Filter = "csv files (*.csv)|*.csv|All files (*.*)|*.*"; if (sf.ShowDialog() == DialogResult.OK) { MainView.Instance.LogMainWindow("Exporting external list..."); // start with the same columns as there were in import DataTable export = new DataTable(); foreach (DataColumn dc in importedColumns) { export.Columns.Add(dc.ColumnName); } // now add additional columns based on what checkboxes are checked if (!checkBoxExportAll.Checked) { if (checkBoxExportPriceGuide.Checked) { export.Columns.Add(MCAttribute.PriceGuideAVG); export.Columns.Add(MCAttribute.PriceGuideLOW); export.Columns.Add(MCAttribute.PriceGuideLOWEX); export.Columns.Add(MCAttribute.PriceGuideLOWFOIL); export.Columns.Add(MCAttribute.PriceGuideSELL); export.Columns.Add(MCAttribute.PriceGuideTREND); export.Columns.Add(MCAttribute.PriceGuideTRENDFOIL); } if (checkBoxExportToolPrices.Checked) { export.Columns.Add(MCAttribute.MKMToolPrice); export.Columns.Add(MCAttribute.PriceCheapestSimilar); } } foreach (MKMMetaCard mc in importedAll) { if (checkBoxExportOnlyAppraised.Checked) { // price guides are not generated or the card does not have them or the user does not want them bool priceGuidesSkip = (!checkBoxExportPriceGuide.Checked || !priceGuidesGenerated || !mc.HasPriceGuides); if (toolPriceGenerated) { if ((mc.GetAttribute(MCAttribute.MKMToolPrice) == "" && mc.GetAttribute(MCAttribute.PriceCheapestSimilar) == "") && // toolPrices not present for this card priceGuidesSkip) { continue; } } else if (priceGuidesSkip) { continue; } } mc.WriteItselfIntoTable(export, checkBoxExportAll.Checked, checkBoxExportFormatDeckbox.Checked ? MCFormat.Deckbox : MCFormat.MKM); } MKMDbManager.WriteTableAsCSV(sf.FileName, export); MainView.Instance.LogMainWindow("Exporting finished."); } }
private void buttonExport_Click(object sender, EventArgs e) { SaveFileDialog sf = new SaveFileDialog(); sf.Filter = "csv files (*.csv)|*.csv|All files (*.*)|*.*"; if (sf.ShowDialog() == DialogResult.OK) { MainView.Instance.LogMainWindow("Exporting inventory..."); MKMDbManager.WriteTableAsCSV(sf.FileName, (DataTable)stockGridView.DataSource); MainView.Instance.LogMainWindow("Inventory exported."); } }
// 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); } } }
// 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) { var articles = new DataTable(); try { // getAllStockSingles creates a DataTable and converts it to list of cards, so theoretically we are wasting some work // but it also filters out non-singles and converting to MKMcard will make sure we use the primary column names rather than synonyms var cards = MKMInteract.RequestHelper.GetAllStockSingles(MainView.Instance.Config.UseStockGetFile); if (cards.Count == 0) { MainView.Instance.LogMainWindow("Stock is empty. Did you select correct idGame in config.xml?"); return; } foreach (var card in cards) { card.WriteItselfIntoTable(articles, true, MCFormat.MKM, true); } // 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 if (articles.Columns.Contains(MCAttribute.ArticleID)) { articles.Columns.Remove(MCAttribute.ArticleID); } if (articles.Columns.Contains(MCAttribute.LanguageID)) { articles.Columns.Remove(MCAttribute.LanguageID); } if (articles.Columns.Contains(MCAttribute.MetaproductID)) { articles.Columns.Remove(MCAttribute.MetaproductID); } if (articles.Columns.Contains("onSale")) { articles.Columns.Remove("onSale"); // don't even know what this one is supposed to be, it's not even in the API documentation } if (articles.Columns.Contains(MCAttribute.MKMCurrencyCode)) { articles.Columns.Remove(MCAttribute.MKMCurrencyCode); } if (articles.Columns.Contains(MCAttribute.MKMCurrencyId)) { articles.Columns.Remove(MCAttribute.MKMCurrencyId); } var dj = MKMDbManager.JoinDataTables(articles, MKMDbManager.Instance.Expansions, (row1, row2) => row1.Field <string>(MCAttribute.ExpansionID) == row2.Field <string>(MKMDbManager.ExpansionsFields.ExpansionID)); if (dj.Columns.Contains(MCAttribute.ExpansionID)) { dj.Columns.Remove(MCAttribute.ExpansionID); // duplicated } if (dj.Columns.Contains(MKMDbManager.ExpansionsFields.ExpansionID)) { dj.Columns.Remove(MKMDbManager.ExpansionsFields.ExpansionID); // ...and we don't want it anyway } if (dj.Columns.Contains(MKMDbManager.ExpansionsFields.Name)) { dj.Columns.Remove(MKMDbManager.ExpansionsFields.Name); // duplicated } // use the same order with or without UseStockGetFile System.Collections.Generic.List <string> attsOrdered = new System.Collections.Generic.List <string> { MCAttribute.Name, MCAttribute.Expansion, MCAttribute.Language, MCAttribute.ProductID, MCAttribute.MKMPrice, MCAttribute.Condition, MCAttribute.Comments, MCAttribute.Foil, MCAttribute.Altered, MCAttribute.Signed, MCAttribute.Playset, MCAttribute.Count, MCAttribute.Rarity, MCAttribute.ExpansionAbb }; int ordinal = 0; foreach (string att in attsOrdered) { if (dj.Columns.Contains(att)) { dj.Columns[dj.Columns.IndexOf(att)].SetOrdinal(ordinal++); } } // convert columns with numerical data from string so that sorting works correctly if (dj.Columns.Contains(MCAttribute.ProductID)) { convertNumberColumn(dj, MCAttribute.ProductID, false); } if (dj.Columns.Contains(MCAttribute.Count)) { convertNumberColumn(dj, MCAttribute.Count, false); } if (dj.Columns.Contains(MCAttribute.MKMPrice)) { convertNumberColumn(dj, MCAttribute.MKMPrice, true); } 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 = MKMDbManager.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 { DataRow[] all = MKMDbManager.Instance.GetCardByName(name); // options are: Latest, Oldest, Cheapest, Median Price, Most Expensive if (all.Length > 0) { // 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 expansion 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.GetProductID(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); } } 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; } }