public void LogSendsAggregateExceptionDetailsToSentryTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = new AggregateException(Guid.NewGuid().ToString()); exception.AddContextData(state.Address); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received(1).Capture(Arg.Any <SentryEvent>()); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Level == ErrorLevel.Fatal)); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Exception == exception)); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Message == exception.Message)); client.Logger.Should().Be(name); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["ContextData"].As <string>() == state.Address)); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["CleanedException"].As <string>() == exception.Demystify().ToString())); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["StorageException.RequestInformation"] == null)); }
private static string GetSteamFolder() { try { using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Valve\Steam")) { if (key != null) { object obj = key.GetValue("InstallPath"); if (obj != null) { if (obj is string installPath) { return(installPath); } } } } } catch (Exception e) { SentryLogger.Log(e); } return(string.Empty); }
public void LogSendsIncludesComplexPropertyWhenExtractingAdditionalDataTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var company = Model.Create <Company>(); var exception = new EmptyException { Company = company }; var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received(1).Capture(Arg.Any <SentryEvent>()); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data.Contains("EmptyException.Company"))); }
public void LogIgnoresFailureToReadPropertiesForExceptionDataTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = Model.Ignoring <ReadFailureException>(x => x.Data) .Ignoring <ReadFailureException>(x => x.Failure).Ignoring <ReadFailureException>(x => x.State) .Create <ReadFailureException>(); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ReadFailureException.Before"].As <string>() == exception.Before)); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data.Contains("ReadFailureException.Failure") == false)); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ReadFailureException.Other"].As <string>() == exception.Other)); }
public void LogSendsNestedExceptionDetailsToSentryTest() { var value = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var name = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var innerException = Model.Ignoring <ValueTypeException>(x => x.Data).Create <ValueTypeException>() .Set(x => x.Id = value); var exception = new ArgumentNullException(Guid.NewGuid().ToString(), innerException); exception.AddContextData(state.Address); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["ValueTypeException.Id"].As <string>() == value)); }
public void LogSendsValueTypeExceptionPropertiesAsDataTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = Model.Ignoring <ValueTypeException>(x => x.Data).Create <ValueTypeException>(); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["ValueTypeException.Day"].As <DayOfWeek>() == exception.Day)); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["ValueTypeException.Number"].As <int>() == exception.Number)); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["ValueTypeException.Id"].As <string>() == exception.Id)); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ValueTypeException.When"].As <DateTimeOffset>() == exception.When)); }
public void LogStoresSentryIdInExceptionDataTest() { var expected = Guid.NewGuid().ToString(); var name = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = new TimeoutException(Guid.NewGuid().ToString()); exception.AddContextData(state.Address); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(expected); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); var actual = exception.Data["Sentry_Id"] as string; actual.Should().Be(expected); }
public void LogSendsExceptionWithContextDataToSentryTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = new TimeoutException(Guid.NewGuid().ToString()); exception.AddContextData(state.Address); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received(1).Capture(Arg.Any <SentryEvent>()); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data["ContextData"].As <string>().Contains(state.Address))); }
/// <summary> /// Shows the form and ends the program after an exception makes it to the top level. /// </summary> /// <param name="e">The unhandled exception.</param> private static void HandleUnhandledException(Exception e) { Logger.Exception("Fatal Error: ", e); SentryLogger.Log(e); using (FatalErrorDialog dialog = new FatalErrorDialog(e)) { dialog.ShowDialog(); } Application.Exit(); }
public void LogSendsTypeReflectionLoadExceptionWithAdditionalContentTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString("N") }; var first = Model.Ignoring <ValueTypeException>(x => x.Data).Create <ValueTypeException>(); var second = Model.Ignoring <WithNestedClassException>(x => x.Data).Create <WithNestedClassException>(); var innerException = new ReflectionTypeLoadException( new[] { typeof(string), typeof(int) }, new Exception[] { first, second }); var exception = new AggregateException(Guid.NewGuid().ToString(), innerException); exception.AddContextData(state.Address); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ReflectionTypeLoadException.Types"].As <string>() .Contains(innerException.Types[0].AssemblyQualifiedName))); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ReflectionTypeLoadException.Types"].As <string>() .Contains(innerException.Types[1].AssemblyQualifiedName))); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ReflectionTypeLoadException.LoaderExceptions"].As <string>() .Contains(innerException.LoaderExceptions[0].GetType().Name))); client.Received().Capture( Arg.Is <SentryEvent>( x => x.Exception.Data["ReflectionTypeLoadException.LoaderExceptions"].As <string>() .Contains(innerException.LoaderExceptions[1].GetType().Name))); }
public static Dictionary <int, AppInfo> LoadApps(string path) { Dictionary <int, AppInfo> appInfos = new Dictionary <int, AppInfo>(); Dictionary <uint, AppInfoNode> appInfoNodes; try { /* * string currentHash = Utility.CalculateMD5(path); * * if (!File.Exists(Location.File.AppInfoCache) || !File.Exists(Location.File.AppInfoHash) || (File.ReadAllText(Location.File.AppInfoHash) != currentHash)) * { */ appInfoNodes = new AppInfoReader(path).Items; /* * File.WriteAllText(Location.File.AppInfoCache, JsonConvert.SerializeObject(appInfoNodes)); * File.WriteAllText(Location.File.AppInfoHash, currentHash); * } * else * { * appInfoNodes = JsonConvert.DeserializeObject<Dictionary<uint, AppInfoNode>>(File.ReadAllText(Location.File.AppInfoCache)); * } */ } catch (Exception e) { SentryLogger.Log(e); throw; } try { foreach (AppInfoNode appInfoNode in appInfoNodes.Values) { AppInfo appInfo = FromNode(appInfoNode); if (appInfo != null) { appInfos.Add(appInfo.AppId, appInfo); } } } catch (Exception e) { SentryLogger.Log(e); throw; } return(appInfos); }
public void LogDoesNotSendEntryToSentryWhenExceptionIsNullTest() { var name = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var client = Substitute.For <IRavenClient>(); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, null, (logState, ex) => ex.ToString()); client.DidNotReceive().Capture(Arg.Any <SentryEvent>()); }
private async Task <bool> upload(List <Model.HttpDataModel.ClippingItemRequest> clippingItemRequests) { if (clippingItemRequests.Count == 0) { return(true); } var clippingsAPI = new Repository.Clippings(); try { clippingsAPI.UplodClippings(clippingItemRequests); return(true); } catch (Exception err) { SentryLogger.Log(err); return(false); } }
public void LogSendsExceptionToSentryTest(LogLevel logLevel) { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = new TimeoutException(Guid.NewGuid().ToString()); var expectedLevel = ErrorLevel.Debug; if (logLevel == LogLevel.Critical) { expectedLevel = ErrorLevel.Fatal; } else if (logLevel == LogLevel.Information) { expectedLevel = ErrorLevel.Info; } else if (Enum.IsDefined(typeof(ErrorLevel), logLevel.ToString())) { expectedLevel = (ErrorLevel)Enum.Parse(typeof(ErrorLevel), logLevel.ToString()); } var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(logLevel, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received(1).Capture(Arg.Any <SentryEvent>()); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Level == expectedLevel)); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Exception == exception)); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Message == exception.Message)); client.Logger.Should().Be(name); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Exception.Data["ContextData"] == null)); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Exception.Data["AsyncException"] == null)); client.Received().Capture(Arg.Is <SentryEvent>(x => x.Exception.Data["StorageException"] == null)); }
public XmlDocument FetchAppListFromWeb() { XmlDocument document = new XmlDocument(); lock (Games) { Logger.Instance.Info("Downloading Steam app list"); Stream responseStream = null; try { WebRequest req = WebRequest.Create(@"http://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=xml"); using (WebResponse resp = req.GetResponse()) { responseStream = resp.GetResponseStream(); if (responseStream == null) { return(document); } document.Load(responseStream); } } catch (Exception e) { SentryLogger.Log(e); throw; } finally { if (responseStream != null) { responseStream.Dispose(); } } Logger.Instance.Info("XML App list downloaded"); } return(document); }
public void LogDoesNotIncludeNullNestedTypeInExceptionDataTest() { var name = Guid.NewGuid().ToString(); var sentryId = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = Model.Ignoring <WithNestedClassException>(x => x.Data).Create <WithNestedClassException>() .Set(x => x.State = null); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data.Contains("WithNestedClassException.State") == false)); }
public void LogDoesNotIncludeCustomStringDataWithoutValueTest(string value) { var sentryId = Guid.NewGuid().ToString(); var name = Guid.NewGuid().ToString(); var eventId = new EventId(Environment.TickCount); var state = new AddressState { Address = Guid.NewGuid().ToString() }; var exception = Model.Ignoring <ValueTypeException>(x => x.Data).Create <ValueTypeException>() .Set(x => x.Id = value); var client = Substitute.For <IRavenClient>(); client.Capture(Arg.Any <SentryEvent>()).Returns(sentryId); var sut = new SentryLogger(name, client); sut.Log(LogLevel.Critical, eventId, state, exception, (logState, ex) => ex.ToString()); client.Received().Capture( Arg.Is <SentryEvent>(x => x.Exception.Data.Contains("ValueTypeException.Id") == false)); }
private bool Save(string path) { lock (Games) { Logger.Instance.Info("Database: Saving current instance to '{0}'", path); XmlWriter writer = null; Stream stream = null; try { stream = new FileStream(path, FileMode.Create); stream = new GZipStream(stream, CompressionMode.Compress); XmlWriterSettings settings = new XmlWriterSettings { Indent = true, CloseOutput = true }; writer = XmlWriter.Create(stream, settings); writer.WriteStartDocument(); writer.WriteStartElement(XmlName_RootNode); writer.WriteElementString(XmlName_LastHltbUpdate, LastHLTBUpdate.ToString(CultureInfo.InvariantCulture)); writer.WriteElementString(XmlName_dbLanguage, Enum.GetName(typeof(StoreLanguage), Language)); writer.WriteStartElement(XmlName_Games); XmlSerializer xmlSerializer = new XmlSerializer(typeof(DatabaseEntry)); XmlSerializerNamespaces nameSpace = new XmlSerializerNamespaces(); nameSpace.Add("", ""); foreach (DatabaseEntry entry in Games.Values) { xmlSerializer.Serialize(writer, entry, nameSpace); } writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndDocument(); } catch (Exception e) { Logger.Instance.Error("Database: Error while trying to save current instance to '{0}'", path); SentryLogger.Log(e); throw; } finally { if (writer != null) { writer.Dispose(); } if (stream != null) { stream.Dispose(); } } Logger.Instance.Info("Database: Saved current instance to '{0}'", path); } return(true); }
private void Load(string path) { lock (Games) { if (!File.Exists(path)) { return; } Logger.Instance.Info("Database: Loading a database instance from '{0}'", path); XmlDocument document = new XmlDocument(); Stream stream = null; try { stream = new FileStream(path, FileMode.Open); stream = new GZipStream(stream, CompressionMode.Decompress); document.Load(stream); } catch (Exception e) { Logger.Instance.Error("Database: Error while reading database file from '{0}'", path); SentryLogger.Log(e); throw; } finally { if (stream != null) { stream.Dispose(); } } try { Games.Clear(); XmlNode gameListNode = document.SelectSingleNode("/" + XmlName_RootNode); if (gameListNode == null) { throw new InvalidDataException(); } Language = (StoreLanguage)Enum.Parse(typeof(StoreLanguage), XmlUtil.GetStringFromNode(gameListNode[XmlName_dbLanguage], "english"), true); LastHLTBUpdate = XmlUtil.GetIntFromNode(gameListNode[XmlName_LastHltbUpdate], 0); XmlNode dictonaryNode = gameListNode.SelectSingleNode(XmlName_Games); if (dictonaryNode == null) { throw new InvalidDataException(); } XmlSerializer xmlSerializer = new XmlSerializer(typeof(DatabaseEntry)); foreach (XmlNode appNode in dictonaryNode.ChildNodes) { using (XmlReader reader = new XmlNodeReader(appNode)) { DatabaseEntry entry = (DatabaseEntry)xmlSerializer.Deserialize(reader); AddOrUpdate(entry); } } } catch (Exception e) { Logger.Instance.Error("Database: Error while parsing database file from '{0}'", path); SentryLogger.Log(e); throw; } Logger.Instance.Info("Database: Loaded current instance from '{0}'", path); } }
public void ScrapeStore() { Logger.Instance.Verbose("Scraping {0}: Initializing store scraping for Id: {0}", Id); string page; int redirectTarget = -1; HttpWebResponse resp = null; Stream responseStream = null; try { string storePage = string.Format(CultureInfo.InvariantCulture, "http://store.steampowered.com/app/{0}/?l={1}", Id, Settings.Instance.StoreLanguage).ToLowerInvariant(); HttpWebRequest req = GetSteamRequest(storePage); resp = (HttpWebResponse)req.GetResponse(); int count = 0; while ((resp.StatusCode == HttpStatusCode.Found) && (count < 5)) { resp.Close(); // Check if we were redirected to the Steam Store front page if (resp.Headers[HttpResponseHeader.Location] == @"http://store.steampowered.com/") { Logger.Instance.Verbose("Scraping {0}: Redirected to main store page, aborting scraping", Id); return; } // Check if we were redirected to the same page if (resp.ResponseUri.ToString() == resp.Headers[HttpResponseHeader.Location]) { Logger.Instance.Verbose("Scraping {0}: Store page redirected to itself, aborting scraping", Id); return; } req = GetSteamRequest(resp.Headers[HttpResponseHeader.Location]); resp = (HttpWebResponse)req.GetResponse(); count++; } // Check if we were redirected too many times if ((count == 5) && (resp.StatusCode == HttpStatusCode.Found)) { Logger.Instance.Verbose("Scraping {0}: Too many redirects, aborting scraping", Id); return; } // Check if we were redirected to the Steam Store front page if (resp.ResponseUri.Segments.Length < 2) { Logger.Instance.Verbose("Scraping {0}: Redirected to main store page, aborting scraping", Id); return; } // Check if we were redirected outside of the app route if (resp.ResponseUri.Segments[1] != "app/") { Logger.Instance.Verbose("Scraping {0}: Redirected outside the app (app/) route, aborting scraping", Id); return; } // The URI ends with "/app/" ? if (resp.ResponseUri.Segments.Length < 3) { Logger.Instance.Verbose("Scraping {0}: Response URI ends with 'app' thus missing ID found, aborting scraping", Id); return; } // Check if we encountered an age gate, cookies should bypass this, but sometimes they don't seem to if (resp.ResponseUri.Segments[1] == "agecheck/") { // Encountered an age check with no redirect if ((resp.ResponseUri.Segments.Length < 4) || (resp.ResponseUri.Segments[3].TrimEnd('/') == Id.ToString(CultureInfo.InvariantCulture))) { Logger.Instance.Verbose("Scraping {0}: Encounterd an age check without redirect, aborting scraping", Id); return; } // Age check + redirect Logger.Instance.Verbose("Scraping {0}: Hit age check for Id: {1}", Id, resp.ResponseUri.Segments[3].TrimEnd('/')); // Check if we encountered an age gate without a numeric id if (!int.TryParse(resp.ResponseUri.Segments[3].TrimEnd('/'), out redirectTarget)) { return; } } // Check if we were redirected to a different Id else if (resp.ResponseUri.Segments[2].TrimEnd('/') != Id.ToString(CultureInfo.InvariantCulture)) { // if new app id is an actual number if (!int.TryParse(resp.ResponseUri.Segments[2].TrimEnd('/'), out redirectTarget)) { Logger.Instance.Verbose("Scraping {0}: Redirected to an unknown Id ({1}), aborting scraping", Id, resp.ResponseUri.Segments[2].TrimEnd('/')); return; } Logger.Instance.Verbose("Scraping {0}: Redirected to another app Id ({1})", Id, resp.ResponseUri.Segments[2].TrimEnd('/')); } responseStream = resp.GetResponseStream(); if (responseStream == null) { Logger.Instance.Verbose("Scraping {0}: The response stream was null, aborting scraping", Id); return; } using (StreamReader streamReader = new StreamReader(responseStream)) { page = streamReader.ReadToEnd(); Logger.Instance.Verbose("Scraping {0}: Page read", Id); } } catch (WebException e) { if (e.Status == WebExceptionStatus.Timeout) { return; } throw; } catch (Exception e) { SentryLogger.Log(e); throw; } finally { if (resp != null) { resp.Dispose(); } if (responseStream != null) { responseStream.Dispose(); } } // Check for server-sided errors if (page.Contains("<title>Site Error</title>")) { Logger.Instance.Verbose("Scraping {0}: Received Site Error, aborting scraping", Id); return; } // Double checking if this is an app (Game or Application) if (!RegGamecheck.IsMatch(page) && !RegSoftwarecheck.IsMatch(page)) { Logger.Instance.Verbose("Scraping {0}: Could not parse info from page, aborting scraping", Id); return; } LastStoreScrape = Utility.CurrentUnixTime(); GetAllDataFromPage(page); // Set or Update ParentId if we got a redirect target if (redirectTarget != -1) { ParentId = redirectTarget; } // Check whether it's DLC and return appropriately if (RegDlCcheck.IsMatch(page)) { Logger.Instance.Verbose("Scraping {0}: Parsed. DLC. Genre: {1}", Id, string.Join(",", Genres)); AppType = AppType.DLC; return; } Logger.Instance.Verbose("Scraping {0}: Parsed. Genre: {1}", Id, string.Join(",", Genres)); if (RegSoftwarecheck.IsMatch(page)) { AppType = AppType.Application; } if (RegGamecheck.IsMatch(page)) { AppType = AppType.Game; } }
public static AppInfo FromNode(AppInfoNode node) { if (node == null) { return(null); } if (!node.Items.ContainsKey("appinfo") || !node["appinfo"].Items.ContainsKey("common") || !node["appinfo"]["common"].Items.ContainsKey("gameid")) { return(null); } AppInfoNode dataNode = node["appinfo"]["common"]; string gameIdNode = dataNode["gameid"].Value; if (!int.TryParse(gameIdNode, out int appId)) { return(null); } AppInfo appInfo = new AppInfo(appId); if (dataNode.Items.ContainsKey("name")) { appInfo.Name = dataNode["name"].Value; } if (dataNode.Items.ContainsKey("type")) { string typeData = dataNode["type"].Value; if (Enum.TryParse(typeData, true, out AppType type)) { appInfo.AppType = type; } else { SentryLogger.Log(new DataException(string.Format(CultureInfo.InvariantCulture, "New AppType '{0}'", typeData))); } } if (dataNode.Items.ContainsKey("oslist")) { string osList = dataNode["oslist"].Value; if (osList.IndexOf("windows", StringComparison.OrdinalIgnoreCase) != -1) { appInfo.Platforms |= AppPlatforms.Windows; } if (osList.IndexOf("mac", StringComparison.OrdinalIgnoreCase) != -1) { appInfo.Platforms |= AppPlatforms.Mac; } if (osList.IndexOf("linux", StringComparison.OrdinalIgnoreCase) != -1) { appInfo.Platforms |= AppPlatforms.Linux; } } if (!dataNode.Items.ContainsKey("parent")) { return(appInfo); } string parentNode = dataNode["parent"].Value; if (int.TryParse(parentNode, out int parentId)) { appInfo.ParentId = parentId; } return(appInfo); }