/// <summary> /// Gets (or creates) a DataPackage (collection of data around a map) /// Grabs data for a type/map /// Updates/creates set of data in DataPackage IndividualMaps with new data /// Makes master data stale /// When required, stale data will regenerate to master list /// Means that we only ever update individual maps, if required - rather than requiry all if we are requested to refresh one /// Also means we only update stale data when required (rather than every time, if we are e.g. loading multiple maps we do this at the end) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="map"></param> /// <param name="uri"></param> /// <param name="metaData"></param> /// <returns></returns> public async Task <int> GetDataAsync(string rawServerTimestamp, DateTime serverTimestamp, Dictionary <string, DataPackage> dataPackages, ServerConfig serverConfig, MainWindow mainWindow, bool forceLocalLoad) { // Note we DON'T currently do any parallel loading of data from the server to help cut down on server traffic. Lovely that we might // download data for all maps at once - but we don't want to eat into all the servers bandwidth and players are potentially affected! (E.g. web server is running on same pc as Ark Server) // ToDo: Make this a config option to enable/disable parallel download string mapName = MapName; try { string metaDataType = MetaData.ArkEntityType; DataPackage dataPackage; if (dataPackages.ContainsKey(metaDataType)) { dataPackage = dataPackages[metaDataType]; UIMapSelection.CacheState = "Updating existing data package"; } else { dataPackage = new DataPackage() { Metadata = MetaData, IndividualMaps = new Dictionary <string, MapPackage>() }; dataPackages.Add(metaDataType, dataPackage); UIMapSelection.CacheState = "New data package"; } UIMapSelection.DisplayState = "...Downloading"; mainWindow.Dispatcher.Invoke(() => Globals.MainWindow.MapsToInclude.Items.Refresh()); IEnumerable <IArkEntity> result; #if DEBUG // Local debugging test - load data from local file if (forceLocalLoad) { result = (IEnumerable <IArkEntity>)JsonSerializer.Deserialize(File.ReadAllText("./temp.json"), typeof(List <>).MakeGenericType(MetaData.JsonClassType)); } else #endif result = (IEnumerable <IArkEntity>) await Web.HttpClient.GetFromJsonAsync(DataUri, typeof(List <>).MakeGenericType(MetaData.JsonClassType)); // Extra processing of data goes here - e.g. calculating extra data values not in JSON and not part of JSON import translations // E.g. colour sort data, as we can't easily read single json colour values into 2 properties at once during import if (typeof(IArkEntityWithCreature).IsAssignableFrom(result.GetType().GetGenericArguments()[0])) { // It's a creature! it has colours :) foreach (IArkEntityWithCreature creature in result) { creature.C0_Sort = creature.C0?.SortOrder ?? -1; // Colour.SortKeyFromColor(creature.C0); creature.C1_Sort = creature.C1?.SortOrder ?? -1; creature.C2_Sort = creature.C2?.SortOrder ?? -1; creature.C3_Sort = creature.C3?.SortOrder ?? -1; creature.C4_Sort = creature.C4?.SortOrder ?? -1; creature.C5_Sort = creature.C5?.SortOrder ?? -1; } } UIMapSelection.DisplayState = "...Decoding"; mainWindow.Dispatcher.Invoke(() => Globals.MainWindow.MapsToInclude.Items.Refresh()); DataTablePlus newData = new(result, MetaData.JsonClassType, mapName, MetaData); MapPackage newMapPackage = new() { Data = newData }; UIMapSelection.DisplayState = "Loaded"; mainWindow.Dispatcher.Invoke(() => Globals.MainWindow.MapsToInclude.Items.Refresh()); // Only calculate timestamps if they haven't already just been snagged if (rawServerTimestamp == string.Empty) { DataTimestamp jsonTimestamp = await Web.HttpClient.GetFromJsonAsync <DataTimestamp>(TimestampUri); rawServerTimestamp = jsonTimestamp.Date; if (DateTime.TryParseExact(rawServerTimestamp, "yyyyMMdd_HHmm", CultureInfo.InvariantCulture, DateTimeStyles.None, out serverTimestamp)) { Debug.Print($"GetDataAsync updating from web site into newMapPackage.Timestamp from {newMapPackage.Timestamp} to {serverTimestamp} : {mapName}.{metaDataType}"); newMapPackage.Timestamp = serverTimestamp; } else { MessageBox.Show($"Error reading timestamp for {mapName}.{metaDataType}, value returned: '{rawServerTimestamp}'", "Date error", MessageBoxButton.OK, MessageBoxImage.Error); UIMapSelection.CacheState = "Timestamp error"; mainWindow.Dispatcher.Invoke(() => Globals.MainWindow.MapsToInclude.Items.Refresh()); } } else { Debug.Print($"GetDataAsync updating from cached timestamp into newMapPackage.Timestamp from {newMapPackage.Timestamp} to {serverTimestamp} : {mapName}.{metaDataType}"); newMapPackage.Timestamp = serverTimestamp; } newMapPackage.RawTimestamp = rawServerTimestamp; newMapPackage.ApproxNextServerUpdateTimestamp = newMapPackage.Timestamp.AddMinutes(serverConfig.RefreshRate); var maps = dataPackage.IndividualMaps; if (maps.ContainsKey(mapName)) { maps[mapName] = newMapPackage; } else { maps.Add(mapName, newMapPackage); } Debug.Print($"GetDataAsync CurrentDataPackage == dataPackage {mainWindow.CurrentDataPackage == dataPackage} to trigger visual refresh"); mainWindow.Dispatcher.Invoke(() => { mainWindow.MapData.ItemsSource = mainWindow.CurrentDataPackage?.IndividualMaps.ToList(); }); //This SHOULD work but doesn't visually --> if (CurrentDataPackage == dataPackage) ExtraInfoMapData.Items.Refresh(); }); dataPackage.DataIsStale = true; return(newData.Rows.Count); } catch (HttpRequestException ex) { Errors.ReportProblem(ex, $"Error retrieving JSON data for { MetaData.Description} on {mapName}: {ex.StatusCode}"); UIMapSelection.CacheState = "Retrieval error"; } catch (NotSupportedException ex) { Errors.ReportProblem(ex, $"Invalid content type in JSON data for { MetaData.Description} on {mapName}"); UIMapSelection.CacheState = "Content error"; } catch (JsonException ex) { Errors.ReportProblem(ex, $"Invalid JSON retrieving JSON data for { MetaData.Description} on {mapName}"); UIMapSelection.CacheState = "JSON error"; } catch (Exception ex) { Errors.ReportProblem(ex, $"Problem loading JSON data for { MetaData.Description} on {mapName}"); UIMapSelection.CacheState = "Loading error"; } UIMapSelection.DisplayState = "Error"; return(-1); }
// Goes through each individual map and generates a new master set of data by merging them together public void MakeSureDataIsUpToDate() { if (!DataIsStale) { return; } if (IndividualMaps.Count == 0) { MapsDescription = "No maps loaded"; return; } bool success = false; int attempts = 1; while (!success) { // it's possible for the data to be modified while we are processing it (e.g. background process is re-generating it) // which will throw an exception here. So we catch and handle this by trying again. try { DataTable = new DataTablePlus(); // Set up copy of datatable structure only (no data) var firstMap = IndividualMaps.First().Value; // Following only copies structure + ColumnPositions etc. - doesn't copy rows of data. This will be handled in the foreach below, which also adds this maps data. DataTable = firstMap.Data.DeepCopy(); MapsDescription = $"Showing data for {Metadata.Description}s"; Debug.Print($"Map data for {Metadata.Description}s is made up of..."); foreach (KeyValuePair <string, MapPackage> map in IndividualMaps) { MapPackage mapPackage = map.Value; Debug.Print($" {Metadata.ArkEntityType}.{map.Key} - {mapPackage.Data.Rows.Count}"); // ToDo: Check this isnt remerging the now cloned dataset into itself! this.DataTable.Merge(mapPackage.Data); } success = true; } catch (Exception ex) { Debug.Print($"Error generating map data - restarting"); attempts++; Thread.Sleep(attempts * 100); if (attempts > 5) { Errors.ReportProblem(ex, "Internal problem updating data. Map data might not be showing correctly. Will keep going though! Feel free to try again..."); success = true; } } } Debug.Print($"...{this.DataTable.Rows.Count} total (took {attempts} attempts)"); DataIsStale = false; }