private void UpdateGraphWithPricePoints() { MTGCard curCard = DM.GetCurrentCard(); List <PricePoint> curPP = DM.GetCurrentPricePoints(); if (curCard == null || curPP == null) { // This can be called at the start to update sources when card/PP are null. return; } foreach (Series cs in mtgPriceChart.Series) { cs.Points.Clear(); } UpdateStatusLabel("Status: Applying data filters."); // Apply any filters that are selected FilterTypes tempDataFilters = new FilterTypes(); this.PopulateFilterTypes(ref tempDataFilters); curPP = DM.ApplyFilters(curPP, tempDataFilters); UpdateStatusLabel("Status: Complete"); foreach (PricePoint pp in curPP) { // Only add points for selected retailers if (mtgPriceChart.Series.IsUniqueName(pp.Retailer) == false) { mtgPriceChart.Series[pp.Retailer].Points.AddXY(pp.Date, (double)pp.Price / 100); } } UpdateCardInfoWindow(curCard, curPP); }
/* For saving the updated card list for a given set */ public void UpdatePricePoints(List <PricePoint> PPsIn, MTGCard CardIn) { int sum = 0; try { SQLiteTransaction trn = MTGDB.BeginTransaction(); foreach (PricePoint pp in PPsIn) { using (SQLiteCommand cmd = new SQLiteCommand(MTGDB)) { cmd.Parameters.Clear(); cmd.CommandText = "INSERT OR REPLACE INTO mtgPP (cardName, setName, price, retailer, priceDate) " + "VALUES (@CNAME, @SNAME, @PRICE, @RET, @PDATE)"; cmd.Parameters.AddWithValue("@CNAME", CardIn.CardName); cmd.Parameters.AddWithValue("@SNAME", CardIn.SetName); cmd.Parameters.AddWithValue("@PRICE", pp.Price); cmd.Parameters.AddWithValue("@RET", pp.Retailer); cmd.Parameters.AddWithValue("@PDATE", pp.Date); sum += cmd.ExecuteNonQuery(); } } trn.Commit(); } catch (Exception err) { log.Error("Insert/Update Error: ", err); } }
/* Returns a List of the MTGCards from the DB for a given set */ public List <MTGCard> GetCardList(string SetName) { List <MTGCard> retCards = new List <MTGCard>(); try { using (SQLiteCommand cmd = new SQLiteCommand(MTGDB)) { cmd.CommandText = "SELECT * FROM mtgCards WHERE setName=@SNAME"; cmd.Parameters.AddWithValue("@SNAME", SetName); SQLiteDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) { UInt64 price = Convert.ToUInt64(rdr["price"]); MTGCard card = new MTGCard(rdr["cardName"].ToString(), rdr["setName"].ToString(), price); card.LastPricePointUpdate = (DateTime)rdr["lastUpdate"]; card.URL = rdr["url"].ToString(); card.FoilURL = rdr["foilURL"].ToString(); card.CardImageURL = rdr["imageURL"].ToString(); retCards.Add(card); } } } catch (Exception err) { log.Warn("GetCardList() for set " + SetName + " Err:", err); } return(retCards); }
/* Price Point fetching/parsing for a particular card/set and list of retailers*/ public List <PricePoint> GetPricePointsForCard(MTGCard CardIn, List <string> RetailerList) { if (CardIn == null) { log.Error("UpdatePricePoints supplied null MTGCard"); return(null); } CurrentCard = CardIn; // Need to Update PricePoints if (CardIn.LastPricePointUpdate.CompareTo(DateTime.Today) < 0) { List <PricePoint> parsePP = new List <PricePoint>(); URLFetcher Fetcher = new URLFetcher(startURL + CardIn.URL); string ret = Fetcher.Fetch(); parsePP = _MTGPriceParser.ParsePricePoints(ret, CardIn); CardIn.LastPricePointUpdate = DateTime.Today; _SQLWrapper.UpdatePricePoints(parsePP, CardIn); _SQLWrapper.UpdateCardLastUpdate(CardIn, CardIn.LastPricePointUpdate); } // Select from SQL for given retailers. List <PricePoint> retPP = _SQLWrapper.GetPricePoints(CardIn, RetailerList); CurrentPricePoints = retPP; return(retPP); }
/* Returns a List of the PricePoints from the DB for a given Card for certain retailers retailers.*/ public List <PricePoint> GetPricePoints(MTGCard CardIn, List <string> RetailerList) { if (CardIn == null) { log.Error("SQLWrapper::GetPricePoints was given a null CardIn."); return(null); } List <PricePoint> retPP = new List <PricePoint>(); try { using (SQLiteCommand cmd = new SQLiteCommand(MTGDB)) { cmd.CommandText = "SELECT * FROM mtgPP WHERE cardName=@CNAME AND setName=@SNAME"; cmd.Parameters.AddWithValue("@SNAME", CardIn.SetName); cmd.Parameters.AddWithValue("@CNAME", CardIn.CardName); // If no retailers it will just select all, otherwise specify retailers. if (RetailerList.Count > 0) { cmd.CommandText += " AND ("; for (int i = 0; i < RetailerList.Count; i++) { cmd.CommandText += "retailer = @RNAME" + i.ToString(); if (i < RetailerList.Count() - 1) { cmd.CommandText += " OR "; } // Add OR for all statements except last one. cmd.Parameters.AddWithValue("@RNAME" + i.ToString(), RetailerList[i]); } cmd.CommandText += ")"; } cmd.CommandText += " ORDER BY priceDate DESC"; SQLiteDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) { PricePoint PP = new PricePoint(); PP.Date = (DateTime)rdr["priceDate"]; PP.Price = Convert.ToUInt64(rdr["price"]); PP.Retailer = rdr["retailer"].ToString(); retPP.Add(PP); } } } catch (Exception err) { log.Warn("GetPricePoints() for card " + CardIn.CardName + " in set " + CardIn.SetName + " Err:", err); } return(retPP); }
/* * Called to save the URL of the card image. * Format of: * <meta property = "og:image" content="http://s.mtgprice.com/sets/Magic_Origins/img/Jace, Vryn's Prodigy.full.jpg" /> */ private void ParseCardImage(string HTMLIn, MTGCard CardIn) { HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument(); htmlDoc.LoadHtml(HTMLIn.ToString()); HtmlAgilityPack.HtmlNode imageNode = htmlDoc.DocumentNode.SelectSingleNode("//meta[@property = 'og:image']"); foreach (HtmlAgilityPack.HtmlAttribute att in imageNode.Attributes) { if (att.Name == "content") { CardIn.CardImageURL = att.Value; } } }
public DataManager() { _SQLWrapper = new SQLWrapper(); _MTGPriceParser = new MTGPriceParser(); _FormatParser = new FormatParser(); _DataFilter = new DataFilter(); _ApplicationState = new MTGUtils.AppState(); log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); Sets = _SQLWrapper.GetSetList(); CurrentCard = null; CurrentPricePoints = null; CurrentFormat = null; }
private void UpdateCardInfoWindow(MTGCard CardIn, List <PricePoint> PPsIn) { UInt64 min = 0, max = 0; DF.GetMinMax(PPsIn, ref min, ref max); lblCurrentPrice.Text = DF.GetPriceFromUInt64(CardIn.Price); lblLowPrice.Text = DF.GetPriceFromUInt64(min); lblHighPrice.Text = DF.GetPriceFromUInt64(max); UInt64 Avg = 0, Avg3Day = 0, Avg7Day = 0, Avg30Day = 0; DF.CalculateAverages(PPsIn, ref Avg, ref Avg3Day, ref Avg7Day, ref Avg30Day); lblAveragePrice.Text = DF.GetPriceFromUInt64(Avg); lbl3DayAveragePrice.Text = DF.GetPriceFromUInt64(Avg3Day); lbl7DayAveragePrice.Text = DF.GetPriceFromUInt64(Avg7Day); lbl30DayAveragePrice.Text = DF.GetPriceFromUInt64(Avg30Day); }
public void UpdateCardLastUpdate(MTGCard CardIn, DateTime LastPPUpdate) { try { SQLiteTransaction trn = MTGDB.BeginTransaction(); using (SQLiteCommand cmd = new SQLiteCommand(MTGDB)) { cmd.CommandText = "UPDATE mtgCards SET lastUpdate=@DATE, imageURL=@IURL WHERE setname=@SNAME AND cardname=@CNAME"; cmd.Parameters.AddWithValue("@SNAME", CardIn.SetName); cmd.Parameters.AddWithValue("@CNAME", CardIn.CardName); cmd.Parameters.AddWithValue("@DATE", LastPPUpdate); cmd.Parameters.AddWithValue("@IURL", CardIn.CardImageURL); cmd.ExecuteNonQuery(); } trn.Commit(); } catch (Exception err) { log.Error("UpdateCardLastUpdate error: ", err); } }
/* Information stored in <script> and specific line has "$scope.setList" */ public List <MTGCard> ParseCardURLs(string HTMLIn, string SetName) { List <MTGCard> retCards = new List <MTGCard>(); HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument(); htmlDoc.LoadHtml(HTMLIn.ToString()); string result = htmlDoc.DocumentNode.OuterHtml; /* As they use a script to display card names/urls/etc no easy way to get the specified line. So big giant string with entire HTML doc inside. */ int listLocation = result.IndexOf("$scope.setList"); int startOfList = result.IndexOf("[{", listLocation); int endOfList = result.IndexOf("}];", startOfList); // Off by 2 to remove the '[{' string listOfCards = result.Substring(startOfList + 2, (endOfList - startOfList - 2)); /* Now we have listOfCards which is of the format: * * {"cardId":"AEtherlingDragons_MazefalseNM-M","name":"AEtherling","quantity":0,"countForTrade":0,"isFoil":false,"url":"/sets/Dragons_Maze/AEtherling", * "setUrl":"/spoiler_lists/Dragons_Maze","fair_price":0.39,"setName":"Dragons Maze","absoluteChangeSinceYesterday":0.0,"absoluteChangeSinceOneWeekAgo":0.0, * "percentageChangeSinceYesterday":0.0,"percentageChangeSinceOneWeekAgo":0.0,"color":"U","rarity":"R","manna":"4UU","bestVendorBuylist":"UNDEFINED", * "bestVendorBuylistPrice":"0","lowestPrice":"0.33","lowestPriceVendor":"HotSauce Games", * "fullImageUrl":"http://s.mtgprice.com/sets/Dragons_Maze/img/AEtherling.full.jpg"},{},{} */ string[] stringSeperator = new string[] { "},{" }; listOfCards = listOfCards.Replace("\\u0027", "'"); string[] cardStrings = listOfCards.Split(stringSeperator, StringSplitOptions.None); // Now we have each card {INFO} in a string foreach (string cardString in cardStrings) { // Now to break it up into "Tag":"Value" pairs. The value can be numerical as well. string[] infoStringSeperator = new string[] { "\",\"", ",\"" }; string[] infoStrings = cardString.Split(infoStringSeperator, StringSplitOptions.None); string cardName = null, setURL = null, price = null; foreach (string infoString in infoStrings) { // Now in the format of "TAG":"VALUE" string[] cleanStringSeperator = new string[] { "\":" }; string[] tagStrings = infoString.Split(cleanStringSeperator, StringSplitOptions.None); if (tagStrings.Count() != 2) { continue; } if (tagStrings[0] == "name") { cardName = tagStrings[1].Trim('"'); } else if (tagStrings[0] == "url") { setURL = tagStrings[1].Trim('"'); } else if (tagStrings[0] == "fair_price") { price = tagStrings[1].Trim('"'); if (price.Contains('.')) { if (price.Length - price.LastIndexOf('.') == 2) // Adjust "1.X" to "1.X0" { price += "0"; } else if (price.Length - price.LastIndexOf('.') > 3) // Adjust "1.XXxxxxxx" to "1.XX" { price = price.Substring(0, price.LastIndexOf('.') + 3); } price = price.Replace(".", string.Empty); } else { price = price + "00"; // Adjust "X" to "X00" } } } MTGCard tempCard = new MTGCard(cardName, SetName, Convert.ToUInt64(price)); tempCard.URL = setURL; retCards.Add(tempCard); } return(retCards); }
/* * JavaScript is used to populate the tbody, and the formatting on the data is odd, * so pretty much just have to search for data and parse manually. * Ignore all zero priced data as it's useless. */ public List <PricePoint> ParsePricePoints(string HTMLIn, MTGCard CardIn) { List <PricePoint> retPP = new List <PricePoint>(); // Double check formatting if (HTMLIn.Contains("var results = [") == false || HTMLIn.Contains("var sellPriceData = [") == false) { log.Error("MTGPrice.com PricePoints formatting has been changed. Need to update the application."); return(null); } if (CardIn.CardImageURL == null || CardIn.CardImageURL == "") { ParseCardImage(HTMLIn, CardIn); } int start = HTMLIn.IndexOf("var results = ["); int end = HTMLIn.IndexOf("var sellPriceData = ["); if (start == -1 || end == -1) { log.Error("MTGPrice.com PricePoints formatting has been changed. Need to update the application. (start = " + start + " end = " + end + ")"); return(null); } /* * Formatting Example: * { * "color": "rgb(140,172,198)", * "label": "HotSauce Games - $19.99", * "lines": { "show": true, "fill": true }, * "data": [[1379804931252,29.99],[1379964574414,29.99]] * }, * * The above group repeats per Retailer */ string relevantData = HTMLIn.Substring(start, (end - start)); // Keep going until a '"label":' is not found. string labelString = "\"label\": "; string dataString = "\"data\": "; while (true) { int labelIndex = relevantData.IndexOf(labelString); if (labelIndex < 0) { // No more retailers, done! break; } int commaIndex = relevantData.IndexOf(',', labelIndex); string retailerName = relevantData.Substring((labelIndex + labelString.Length), commaIndex - labelIndex - labelString.Length); // Format is now '"<NAME>" - $<PRICE>', remove quotes, and everything after name. retailerName = retailerName.Substring(1, retailerName.IndexOf(" -") - 1); int dataIndex = relevantData.IndexOf(dataString); // Start at Index 1, as the 2nd-Xth strings will be "}DATA" int endIndex = relevantData.IndexOf('}', dataIndex); // Put just the price points into [DATE,PRICE],[DATE,PRICE] format and // Trim the first/last '[' and ']' string pureData = relevantData.Substring(dataIndex + dataString.Length, endIndex - dataIndex - dataString.Length); pureData = pureData.Replace(" ", string.Empty); pureData = pureData.Replace("\r", string.Empty); pureData = pureData.Replace("\n", string.Empty); if (pureData != null && pureData != "[]" && retailerName != "Best BUYLIST Price") { // Split based on delimiter "],[" and use "[[" and "]]" to remove first/last double brackets and then remove emptry entries // The "[]" case takes care of empty brackets string[] delim = new string[] { "],[", "[[", "]]", "[]" }; string[] splitData = pureData.Split(delim, StringSplitOptions.RemoveEmptyEntries); // Data is now formatted as '[DATE,PRICE]' foreach (string point in splitData) { string[] splitPoint = point.Split(','); string stringPrice = splitPoint[1]; if (stringPrice == "0.0" || stringPrice == "0") // Ignore zero prices. { continue; } if (stringPrice.Contains('.')) { if (stringPrice.Length - stringPrice.LastIndexOf('.') == 2) // Adjust "1.X" to "1.X0" { stringPrice += "0"; } else if (stringPrice.Length - stringPrice.LastIndexOf('.') > 3) // Adjust "1.XXxxxxxx" to "1.XX" { stringPrice = stringPrice.Substring(0, stringPrice.LastIndexOf('.') + 3); } stringPrice = stringPrice.Replace(".", string.Empty); } else { stringPrice = stringPrice + "00"; // Adjust "X" to "X00" } UInt64 Price = Convert.ToUInt64(stringPrice); if (Price == 0) { log.Error(Price + " from '" + stringPrice + "'"); } PricePoint tempPP = new PricePoint(); tempPP.Retailer = retailerName; // Convert from Milliseconds since Unix Epoch to DateTime tempPP.Date = new DateTime(1970, 1, 1, 0, 0, 0, 0); tempPP.Date = tempPP.Date.AddSeconds(Convert.ToDouble(splitPoint[0]) / 1000).ToLocalTime(); tempPP.Price = Price; retPP.Add(tempPP); } } relevantData = relevantData.Substring(endIndex); } return(retPP); }
/* Fetch PricePoints if needed, update chart variables */ private void ApplyPricePointsToChart() { if (mtgCardsGraphListBox.SelectedItem == null) { return; } MTGCard curCard = (MTGCard)mtgCardsGraphListBox.SelectedItem; if (curCard == null) { return; } // Populate the List of Currently selected retailers. List <string> RetailerList = new List <string>(); foreach (string retailer in mtgPriceSourceCheckListBox.CheckedItems) { RetailerList.Add(retailer); } // Update the Title mtgPriceChart.Titles.Clear(); Title title = mtgPriceChart.Titles.Add(curCard.ToString()); title.Font = new Font("Arial", 32); // Fetch the Price Points if required UpdateStatusLabel("Status: Fetching info for " + curCard.ToString()); List <PricePoint> PricePoints = DM.GetPricePointsForCard(curCard, RetailerList); UpdateStatusLabel("Status: Complete"); if (PricePoints != null) { UpdateGraphWithPricePoints(); } else { UpdateStatusLabel("Status: Error retrieving price points for card " + curCard.ToString()); } try { pictureBoxCard.Load(curCard.CardImageURL); } catch (System.Net.WebException ex) { if (ex.Response is System.Net.HttpWebResponse) { System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)ex.Response; log.Error("Failed to load image " + curCard.CardImageURL + " with status code " + response.StatusCode + " (" + response.StatusDescription + ")"); } pictureBoxCard.Image = global::MTGUtils.Properties.Resources.MTG_Card_Back; } /* TODO: Add a toggle for this as it can be cluttered with older sets. * // Add the set release date markets to the chart * mtgPriceChart.ChartAreas["StripLines"].AxisX.StripLines.Clear(); * foreach(MTGSet set in DM.GetSets()) * { * if(set.SetDate.CompareTo(PricePoints.First().Date) < 0 && set.SetDate.CompareTo(PricePoints.Last().Date) > 0) * { * StripLine stripLine = new StripLine(); * // stripLine.Text = set.SetName; * stripLine.BackColor = Color.Magenta; * stripLine.StripWidth = 0.1; * stripLine.StripWidthType = DateTimeIntervalType.Days; * * TimeSpan ts = set.SetDate - PricePoints.Last().Date; * * stripLine.Interval = 100000; // Tried setting to 0 to be shown once, but then I see nothing. * stripLine.IntervalOffset = ts.Days; * stripLine.IntervalOffsetType = DateTimeIntervalType.Days; * * mtgPriceChart.ChartAreas["StripLines"].AxisX.StripLines.Add(stripLine); * } * } */ }