• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

samsmithnz / SatisfactoryTree / 17704018097

14 Sep 2025 12:31AM UTC coverage: 87.212% (-3.3%) from 90.507%
17704018097

push

github

web-flow
Merge pull request #287 from samsmithnz/AddingNewWebsite

Added new blazor webassembly project

298 of 352 branches covered (84.66%)

Branch coverage included in aggregate %.

5 of 51 new or added lines in 1 file covered. (9.8%)

2 existing lines in 1 file now uncovered.

950 of 1079 relevant lines covered (88.04%)

827.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.94
/src/SatisfactoryTree.Logic/Extraction/FactoryCatalogExtractor.cs
1
using SatisfactoryTree.Logic.Models;
2
using System.Diagnostics;
3
using System.Security.Cryptography;
4
using System.Text.Json;
5

6
namespace SatisfactoryTree.Logic.Extraction
7
{
8
    public class FactoryCatalogExtractor
9
    {
10
        public static string InputFile { get; set; } = "";
11✔
11
        public static string OutputFile { get; set; } = "";
20✔
12

13
        public static bool GetContentFiles()
14
        {
9✔
15
            // Load the content file
16
            string contentPath = @"C:\Program Files (x86)\Steam\steamapps\common\Satisfactory\CommunityResources\Docs\en-US.json";
9✔
17
            DirectoryInfo? currentDir = new(Directory.GetCurrentDirectory());
9✔
18
            DirectoryInfo? parentDir = currentDir.Parent?.Parent?.Parent?.Parent?.Parent;
9!
19
            if (parentDir == null)
9!
20
            {
×
21
                throw new Exception("Parent directory structure is not as expected.");
×
22
            }
23
            string projectContentPath = Path.Combine(parentDir.FullName, "content");
9✔
24
            string projectContentFile = Path.Combine(projectContentPath, "en-US.json");
9✔
25

26
            // Only copy if the file doesn't exist or if the files are different
27
            if (File.Exists(contentPath) && Directory.Exists(projectContentPath))
9!
UNCOV
28
            {
×
NEW
29
                bool shouldCopy = !File.Exists(projectContentFile) || !FilesAreEqual(contentPath, projectContentFile);
×
30

NEW
31
                if (shouldCopy)
×
NEW
32
                {
×
NEW
33
                    Debug.WriteLine("Copying file to " + projectContentPath);
×
NEW
34
                    CopyFileWithRetry(contentPath, projectContentFile);
×
NEW
35
                }
×
36
                else
NEW
37
                {
×
NEW
38
                    Debug.WriteLine("File already exists and is identical, skipping copy.");
×
NEW
39
                }
×
UNCOV
40
            }
×
41

42
            InputFile = projectContentFile;
9✔
43
            OutputFile = Path.Combine(projectContentPath, "gameData.json");
9✔
44
            return true;
9✔
45
        }
9✔
46

47
        private static bool FilesAreEqual(string filePath1, string filePath2)
NEW
48
        {
×
49
            try
NEW
50
            {
×
51
                // First check file sizes - if different, files are definitely different
NEW
52
                FileInfo fileInfo1 = new(filePath1);
×
NEW
53
                FileInfo fileInfo2 = new(filePath2);
×
54

NEW
55
                if (fileInfo1.Length != fileInfo2.Length)
×
NEW
56
                {
×
NEW
57
                    return false;
×
58
                }
59

60
                // If sizes are the same, compare checksums
NEW
61
                using SHA256 sha256 = System.Security.Cryptography.SHA256.Create();
×
62

63
                byte[] hash1, hash2;
NEW
64
                using (FileStream stream1 = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.Read))
×
NEW
65
                {
×
NEW
66
                    hash1 = sha256.ComputeHash(stream1);
×
NEW
67
                }
×
68

NEW
69
                using (FileStream stream2 = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.Read))
×
NEW
70
                {
×
NEW
71
                    hash2 = sha256.ComputeHash(stream2);
×
NEW
72
                }
×
73

NEW
74
                return hash1.SequenceEqual(hash2);
×
75
            }
NEW
76
            catch (Exception ex)
×
NEW
77
            {
×
NEW
78
                Debug.WriteLine($"Error comparing files: {ex.Message}");
×
NEW
79
                return false; // Assume files are different if we can't compare them
×
80
            }
NEW
81
        }
×
82

83
        private static void CopyFileWithRetry(string source, string destination, int maxRetries = 3)
NEW
84
        {
×
NEW
85
            for (int i = 0; i < maxRetries; i++)
×
NEW
86
            {
×
87
                try
NEW
88
                {
×
NEW
89
                    using var sourceStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read);
×
NEW
90
                    using var destinationStream = new FileStream(destination, FileMode.Create, FileAccess.Write);
×
NEW
91
                    sourceStream.CopyTo(destinationStream);
×
NEW
92
                    return; // Success
×
93
                }
NEW
94
                catch (IOException ex) when (i < maxRetries - 1)
×
NEW
95
                {
×
NEW
96
                    Debug.WriteLine($"Copy attempt {i + 1} failed: {ex.Message}. Retrying in 1 second...");
×
NEW
97
                    Thread.Sleep(1000); // Wait 1 second before retry
×
NEW
98
                }
×
NEW
99
            }
×
NEW
100
        }
×
101

102
        public static async Task<FactoryCatalog> ProcessGameFile()
103
        {
1✔
104
            Stopwatch stopwatch = new();
1✔
105
            stopwatch.Start();
1✔
106
            //try
107
            //{
108
            //Read file contexts from text file
109
            GetContentFiles();
1✔
110
            string fileContent = File.ReadAllText(InputFile);
1✔
111
            List<dynamic>? rawData = System.Text.Json.JsonSerializer.Deserialize<List<dynamic>>(fileContent);
1✔
112
            List<JsonElement> data = new();
1✔
113
            List<JsonElement> rawResourcesData = new();
1✔
114
            if (rawData != null)
1✔
115
            {
1✔
116
                foreach (JsonElement entry in rawData)
227✔
117
                {
112✔
118
                    string? nativeClass = entry.TryGetProperty("NativeClass", out JsonElement nativeClassElement) ? nativeClassElement.GetString() : string.Empty;
112!
119
                    if (entry.TryGetProperty("Classes", out JsonElement classesElement) && classesElement.ValueKind == JsonValueKind.Array)
112!
120
                    {
112✔
121
                        data.AddRange(classesElement.EnumerateArray());
112✔
122
                    }
112✔
123
                }
112✔
124
            }
1✔
125

126
            // Get an array of all buildings that produce something
127
            List<string> producingBuildings = ProcessRawBuildings.GetProducingBuildings(data);
1✔
128

129
            // Get power consumption for the producing buildings
130
            Dictionary<string, double> buildings = ProcessRawBuildings.GetPowerConsumptionForBuildings(data, producingBuildings);
1✔
131

132
            // Pass the producing buildings with power data to getRecipes to calculate perMin and powerPerProduct
133
            List<Recipe> recipes = ProcessRawRecipes.GetProductionRecipes(data, buildings);
1✔
134

135
            // Get parts
136
            RawPartsAndRawMaterials items = ProcessRawParts.GetItems(data, recipes);
1✔
137
            ProcessRawParts.FixItemNames(items);
1✔
138
            ProcessRawParts.FixTurbofuel(items, recipes);
1✔
139

140
            // IMPORTANT: The order here matters - don't run this before fixing the turbofuel.
141
            List<PowerGenerationRecipe> powerGenerationRecipes = ProcessRawRecipes.GetPowerGeneratingRecipes(data, items, buildings);
1✔
142

143
            // Since we've done some manipulation of the items data, re-sort it
144
            Dictionary<string, Part> sortedItems = new();
1✔
145
            foreach (string? key in items.Parts.Keys.OrderBy(k => k))
507✔
146
            {
168✔
147
                sortedItems[key] = items.Parts[key];
168✔
148
            }
168✔
149
            items.Parts = sortedItems;
1✔
150

151
            //Build the new recipe collection
152
            List<Recipe> newRecipes = new();
1✔
153
            foreach (Recipe recipe in recipes)
585✔
154
            {
291✔
155
                newRecipes.Add(new()
291✔
156
                {
291✔
157
                    Name = recipe.Name,
291✔
158
                    DisplayName = recipe.DisplayName,
291✔
159
                    Ingredients = recipe.Ingredients,
291✔
160
                    Products = recipe.Products,
291✔
161
                    Building = recipe.Building,
291✔
162
                    IsAlternate = recipe.IsAlternate,
291✔
163
                    IsFicsmas = recipe.IsFicsmas,
291✔
164
                    UsesSAMOre = recipe.UsesSAMOre
291✔
165
                });
291✔
166
            }
291✔
167
            //Now add the power generation recipes
168
            foreach (PowerGenerationRecipe recipe in powerGenerationRecipes)
37✔
169
            {
17✔
170
                List<Ingredient> ingredients = new();
17✔
171
                foreach (PowerIngredient ingredient in recipe.ingredients)
97✔
172
                {
23✔
173
                    ingredients.Add(new Ingredient()
23✔
174
                    {
23✔
175
                        part = ingredient.part,
23✔
176
                        amount = ingredient.perMin,
23✔
177
                        perMin = ingredient.perMin,
23✔
178
                        mwPerItem = ingredient.mwPerItem,
23✔
179
                    });
23✔
180
                }
23✔
181
                List<Product> products = new();
17✔
182
                if (recipe.byproduct != null)
17✔
183
                {
2✔
184
                    products.Add(
2✔
185
                        new Product()
2✔
186
                        {
2✔
187
                            part = recipe.byproduct.part,
2✔
188
                            amount = recipe.byproduct.perMin,
2✔
189
                            perMin = recipe.byproduct.perMin,
2✔
190
                            isByProduct = true
2✔
191
                        }
2✔
192
                    );
2✔
193
                }
2✔
194

195
                // Check if any ingredient is SAMIngot to set usesSAMOre flag
196
                bool usesSAMOre = ingredients.Any(ingredient => ingredient.part == "SAMIngot");
40✔
197

198
                newRecipes.Add(new()
17✔
199
                {
17✔
200
                    Name = recipe.id,
17✔
201
                    DisplayName = recipe.displayName,
17✔
202
                    Ingredients = ingredients,
17✔
203
                    Products = products,
17✔
204
                    Building = recipe.building,
17✔
205
                    IsAlternate = false,
17✔
206
                    IsFicsmas = false,
17✔
207
                    UsesSAMOre = usesSAMOre
17✔
208
                });
17✔
209
            }
17✔
210
            //sort the new recipes list by id
211
            newRecipes = newRecipes.OrderBy(r => r.Name).ToList();
309✔
212

213
            // Construct the final JSON object
214
            FactoryCatalog factoryCatalog = new(
1✔
215
                buildings,
1✔
216
                items,
1✔
217
                recipes,
1✔
218
                powerGenerationRecipes);
1✔
219

220
            // Write the output to the file
221
            JsonSerializerOptions options = new() { WriteIndented = true, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
1✔
222
            string outputJson = System.Text.Json.JsonSerializer.Serialize(factoryCatalog, options);
1✔
223
            await File.WriteAllTextAsync(OutputFile, outputJson);
1✔
224
            stopwatch.Stop();
1✔
225

226
            System.Console.WriteLine($"Processed {items.Parts.Count} parts, {buildings.Count} buildings, and {recipes.Count} recipes, all written to {OutputFile}.");
1✔
227
            System.Console.WriteLine($"Total processing time: {stopwatch.Elapsed.TotalMilliseconds} ms");
1✔
228
            return factoryCatalog;
1✔
229
            //}
230
            //catch (Exception ex)
231
            //{
232
            //    System.Console.Error.WriteLine($"Error processing file: {ex.Message}");
233
            //    return null;
234
            //}
235
        }
1✔
236

237
        public static async Task<FactoryCatalog?> LoadDataFromFile()
238
        {
8✔
239
            try
240
            {
8✔
241
                GetContentFiles();
8✔
242
                string targetFile = OutputFile;
8✔
243

244
                if (!File.Exists(targetFile))
8!
245
                {
×
246
                    throw new FileNotFoundException($"Configuration file not found: {targetFile}");
×
247
                }
248

249
                string jsonContent = await File.ReadAllTextAsync(targetFile);
8✔
250

251
                JsonSerializerOptions options = new()
8✔
252
                {
8✔
253
                    PropertyNameCaseInsensitive = true,
8✔
254
                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
8✔
255
                };
8✔
256

257
                FactoryCatalog? factoryCatalog = JsonSerializer.Deserialize<FactoryCatalog>(jsonContent, options);
8✔
258

259
                if (factoryCatalog == null)
8!
260
                {
×
261
                    throw new InvalidOperationException("Failed to deserialize the configuration file");
×
262
                }
263

264
                System.Console.WriteLine($"Successfully loaded data from {targetFile}");
8✔
265
                System.Console.WriteLine($"Loaded {factoryCatalog.Parts?.Count ?? 0} parts, {factoryCatalog.Buildings?.Count ?? 0} buildings, {factoryCatalog.Recipes?.Count ?? 0} recipes, and {factoryCatalog.PowerGenerationRecipes?.Count ?? 0} power generation recipes");
8!
266

267
                return factoryCatalog;
8✔
268
            }
269
            catch (Exception ex)
×
270
            {
×
271
                System.Console.Error.WriteLine($"Error loading data from file: {ex.Message}");
×
272
                return null;
×
273
            }
274
        }
8✔
275
    }
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc