/// <summary>
        /// This will assign all files to the proper destination.
        /// </summary>
        /// <param name="fileList">The list of files inside the mod archive.</param>
        /// <param name="stopPatterns">patterns matching files or directories that should be at the top of the directory structure.</param>
        /// <param name="progressDelegate">A delegate to provide progress feedback.</param>
        /// <param name="coreDelegate">A delegate for all the interactions with the js core.</param>
        protected async Task <List <Instruction> > BasicModInstall(List <string> fileList,
                                                                   List <string> stopPatterns,
                                                                   ProgressDelegate progressDelegate,
                                                                   CoreDelegates coreDelegate)
        {
            List <Instruction> FilesToInstall = new List <Instruction>();
            string             prefix         = ArchiveStructure.FindPathPrefix(fileList, stopPatterns);

            await Task.Run(() =>
            {
                foreach (string ArchiveFile in fileList)
                {
                    if (ArchiveFile.EndsWith("" + Path.DirectorySeparatorChar))
                    {
                        // don't include directories, only files
                        continue;
                    }
                    string destination;
                    if (ArchiveFile.StartsWith(prefix))
                    {
                        destination = ArchiveFile.Substring(prefix.Length);
                    }
                    else
                    {
                        destination = ArchiveFile;
                    }
                    FilesToInstall.Add(Instruction.CreateCopy(ArchiveFile, destination));
                    // Progress should increase.
                }
            });

            return(FilesToInstall);
        }
        private async Task <ArchiveTableInformation> ParseTableInformation(JObject json, Archive.Archive archive, Guid sessionId)
        {
            // Find the properties in the JSON
            ArchiveStructure.GetUserMeta(json, out var meta, out var user);
            var pathArr = meta["attachments"].ToObject <string[]>() ?? throw new ArgumentException("Table is missing 'attachments'");
            var path    = "" + sessionId + pathArr[0];

            // Find the file in the archive
            var zipEntry = archive.FindFile(path) ?? throw new ZipException($"Table file '{path}' not found in archive");

            var tableInformation = new ArchiveTableInformation
            {
                ZipEntry = zipEntry,
                Columns  = new List <DataField>(),
                Uri      = path,
                Units    = new List <string>(),
                TimeUnit = "",
            };

            // Open the table file
            using (var stream = await archive.OpenFile(zipEntry))
                using (var reader = new ParquetReader(stream))
                {
                    var fields   = reader.Schema.GetDataFields();
                    var rawUnits = new string[0];
                    if (meta.ContainsKey("units"))
                    {
                        rawUnits = meta["units"].Select(unit => unit.Value <string>()).ToArray();
                    }

                    // Find all the column information
                    int idx = 0;
                    foreach (var field in fields)
                    {
                        // Fetch unit for current column
                        string currUnit = "";
                        if (idx < rawUnits.Length)
                        {
                            currUnit = rawUnits[idx];
                        }
                        idx++;

                        if (field.Name.Equals("time", StringComparison.OrdinalIgnoreCase))
                        {
                            tableInformation.Time     = field;
                            tableInformation.TimeUnit = currUnit;
                        }
                        else
                        {
                            tableInformation.Columns.Add(field);
                            tableInformation.Units.Add(currUnit);
                        }
                    }
                }

            if (meta.ContainsKey("is_world_clock"))
            {
                tableInformation.IsWorldSynchronized = meta["is_world_clock"].Value <bool>();
            }

            // Return the collected info
            return(tableInformation);
        }