private bool FatAddDirectoryEntry(List <LocalDirectoryContentInfo> localDirectoryContentInfos, int directoryClusterIndex, int entryStartClusterIndex, string directoryPath, string fileName, string TOSFileName, byte attributeFlags, DateTime lastWriteDateTime, long fileSize) { byte[] directoryBuffer; int entryIndex = 0; int maxEntryIndex = directoryClusterIndex == _rootDirectoryClusterIndex ? _rootDirectoryBuffer.Length : Parameters.BytesPerCluster; if (directoryClusterIndex == _rootDirectoryClusterIndex) { directoryBuffer = _rootDirectoryBuffer; } else { directoryBuffer = _clusterInfos[directoryClusterIndex].DataBuffer; } // Check whether there is any space left in the cluster do { // No space left if (entryIndex >= maxEntryIndex) { int nextDirectoryClusterIndex = FatGetClusterValue(directoryClusterIndex); // This is the final cluster, allocate new cluster if (FAT16Helper.IsEndOfFile(nextDirectoryClusterIndex)) { try { int newDirectoryCluster = AddNextFreeClusterToChain(directoryClusterIndex); _clusterInfos[newDirectoryCluster] = new ClusterInfo() { FileOffset = -1, DataBuffer = new byte[Parameters.BytesPerCluster] }; _clusterInfos[newDirectoryCluster].LocalDirectoryContent = _clusterInfos[directoryClusterIndex].LocalDirectoryContent; entryIndex = 0; } catch (IndexOutOfRangeException outOfRangeEx) { int localDirectorySizeMiB = (int)Directory.GetFiles(Parameters.LocalDirectoryPath, "*", SearchOption.AllDirectories).Sum(file => (new FileInfo(file).Length)) / FAT16Helper.BytesPerMiB; _logger.LogException(outOfRangeEx, $"Local directory size is {localDirectorySizeMiB} MiB, which is too large for the given virtual disk size ({Parameters.DiskTotalBytes / FAT16Helper.BytesPerMiB} MiB)"); throw; } } else { directoryClusterIndex = nextDirectoryClusterIndex; } directoryBuffer = _clusterInfos[directoryClusterIndex].DataBuffer; entryIndex = 0; } // Find next unused entry in directory while (entryIndex < maxEntryIndex && directoryBuffer[entryIndex] != 0) { entryIndex += 32; } if (entryIndex >= maxEntryIndex) { if (directoryClusterIndex == _rootDirectoryClusterIndex) { Exception outofIndexesException = new Exception($"Exceeded available directory entries in {directoryPath}. There may be too many files in directory (max {(maxEntryIndex / 32) - 2} items)."); _logger.LogException(outofIndexesException, outofIndexesException.Message); throw outofIndexesException; } } } while (entryIndex >= maxEntryIndex); // Remember which local content matches this entry. if (TOSFileName[0] != '.') { LocalDirectoryContentInfo newLocalDirectoryContentInfo = new LocalDirectoryContentInfo() { ParentDirectory = _clusterInfos[directoryClusterIndex]?.LocalDirectoryContent, LocalFileName = fileName, TOSFileName = TOSFileName, EntryIndex = entryIndex, DirectoryCluster = directoryClusterIndex, StartCluster = entryStartClusterIndex }; localDirectoryContentInfos.Add(newLocalDirectoryContentInfo); // Cluster index 0 indicates empty file, so no data clusters to update if (entryStartClusterIndex != 0) { _clusterInfos[entryStartClusterIndex].LocalDirectoryContent = newLocalDirectoryContentInfo; int clusterValue = FatGetClusterValue(entryStartClusterIndex); while (!FAT16Helper.IsEndOfFile(clusterValue)) { _clusterInfos[clusterValue].LocalDirectoryContent = newLocalDirectoryContentInfo; clusterValue = FatGetClusterValue(clusterValue); } } } // File name. int fileNameIndex; for (fileNameIndex = 0; fileNameIndex < (8 + 3); fileNameIndex++) { directoryBuffer[entryIndex + fileNameIndex] = 0x20; } string[] nameAndExtender; byte[] asciiName; byte[] asciiExtender; if (TOSFileName == "." || TOSFileName == "..") { asciiName = ASCIIEncoding.ASCII.GetBytes(TOSFileName); asciiExtender = null; } else { nameAndExtender = TOSFileName.Split('.'); asciiName = ASCIIEncoding.ASCII.GetBytes(nameAndExtender[0]); asciiExtender = nameAndExtender.Length == 2 ? ASCIIEncoding.ASCII.GetBytes(nameAndExtender[1]) : null; } for (fileNameIndex = 0; fileNameIndex < asciiName.Length; fileNameIndex++) { directoryBuffer[entryIndex + fileNameIndex] = asciiName[fileNameIndex]; } if (asciiExtender != null) { for (fileNameIndex = 0; fileNameIndex < asciiExtender.Length; fileNameIndex++) { directoryBuffer[entryIndex + 8 + fileNameIndex] = asciiExtender[fileNameIndex]; } } // File attribute flags. directoryBuffer[entryIndex + 11] = attributeFlags; // File write time and date (little endian). UInt16 fatFileWriteTime = 0; UInt16 fatFileWriteDate = 0; int TwoSeconds = lastWriteDateTime.Second / 2; int Minutes = lastWriteDateTime.Minute; int Hours = lastWriteDateTime.Hour; int DayOfMonth = lastWriteDateTime.Day; int Month = lastWriteDateTime.Month; int YearsSince1980 = lastWriteDateTime.Year - 1980; fatFileWriteTime |= (UInt16)TwoSeconds; fatFileWriteTime |= (UInt16)(Minutes << 5); fatFileWriteTime |= (UInt16)(Hours << 11); fatFileWriteDate |= (UInt16)DayOfMonth; fatFileWriteDate |= (UInt16)(Month << 5); fatFileWriteDate |= (UInt16)(YearsSince1980 << 9); directoryBuffer[entryIndex + 22] = (byte)(fatFileWriteTime & 0xff); directoryBuffer[entryIndex + 23] = (byte)((fatFileWriteTime >> 8) & 0xff); directoryBuffer[entryIndex + 24] = (byte)(fatFileWriteDate & 0xff); directoryBuffer[entryIndex + 25] = (byte)((fatFileWriteDate >> 8) & 0xff); // Cluster (little endian). directoryBuffer[entryIndex + 26] = (byte)(entryStartClusterIndex & 0xff); directoryBuffer[entryIndex + 27] = (byte)((entryStartClusterIndex >> 8) & 0xff); // File size (little endian). directoryBuffer[entryIndex + 28] = (byte)(fileSize & 0xff); directoryBuffer[entryIndex + 29] = (byte)((fileSize >> 8) & 0xff); directoryBuffer[entryIndex + 30] = (byte)((fileSize >> 16) & 0xff); directoryBuffer[entryIndex + 31] = (byte)((fileSize >> 24) & 0xff); return(true); }
private void UpdateLocalDirectoryOrFile(List <LocalDirectoryContentInfo> localDirectoryContentInfos, byte[] directoryData, int directoryClusterIndex, int directoryEntryIndex, int entryStartClusterIndex, string newContentName) { string newContentPath = null; if (directoryClusterIndex != _rootDirectoryClusterIndex) { string newPathDirectory = _clusterInfos[directoryClusterIndex].LocalDirectoryContent.LocalPath; newContentPath = Path.Combine(newPathDirectory, newContentName); } else { newContentPath = newContentName; } try { if (directoryData[directoryEntryIndex + 11] == FAT16Helper.DirectoryIdentifier) { // Is it a directory with a valid start cluster? if (entryStartClusterIndex != 0) { _logger.Log("Creating local directory \"" + newContentPath + "\"", Constants.LoggingLevel.Info); var CreatedLocalDirectory = Directory.CreateDirectory(GetAbsolutePath(newContentPath)); var newLocalDirectoryContent = new LocalDirectoryContentInfo { ParentDirectory = _clusterInfos[directoryClusterIndex]?.LocalDirectoryContent, LocalFileName = newContentName, TOSFileName = newContentName, EntryIndex = directoryEntryIndex, DirectoryCluster = directoryClusterIndex, StartCluster = entryStartClusterIndex, FinalCluster = entryStartClusterIndex, WriteInProgress = false }; localDirectoryContentInfos.Add(newLocalDirectoryContent); _clusterInfos[entryStartClusterIndex].LocalDirectoryContent = newLocalDirectoryContent; _clusterInfos[entryStartClusterIndex].FileOffset = FAT16Helper.DirectoryFileOffset; } } // it's a file else { _logger.Log($"Checking if local file {newContentPath} should be updated", Constants.LoggingLevel.Debug); int fileSize = directoryData[directoryEntryIndex + 28] | (directoryData[directoryEntryIndex + 29] << 8) | (directoryData[directoryEntryIndex + 30] << 16) | (directoryData[directoryEntryIndex + 31] << 24); _logger.Log("File size to write:" + fileSize, Constants.LoggingLevel.All); int fileClusterIndex = entryStartClusterIndex; _logger.Log("Finding existing content info for local file \"" + newContentPath + "\".", Constants.LoggingLevel.All); var localDirectoryContent = localDirectoryContentInfos.Where(ldci => ldci.LocalPath == newContentPath).SingleOrDefault(); if (localDirectoryContent == null) { _logger.Log($"Creating local file: {newContentPath}", Constants.LoggingLevel.Info); File.Create(GetAbsolutePath(newContentPath)).Dispose(); var newLocalDirectoryContent = new LocalDirectoryContentInfo { ParentDirectory = _clusterInfos[directoryClusterIndex]?.LocalDirectoryContent, LocalFileName = newContentName, TOSFileName = newContentName, EntryIndex = directoryEntryIndex, DirectoryCluster = directoryClusterIndex, StartCluster = entryStartClusterIndex, FinalCluster = -1 }; localDirectoryContentInfos.Add(newLocalDirectoryContent); localDirectoryContent = newLocalDirectoryContent; } localDirectoryContent.StartCluster = entryStartClusterIndex; _logger.Log("File start cluster: " + localDirectoryContent.StartCluster + " (" + FatGetClusterValue(localDirectoryContent.StartCluster) + ")", Constants.LoggingLevel.All); // Entry cluster will be assigned if this is a non-empty file if (entryStartClusterIndex != 0) { localDirectoryContent.WriteInProgress = true; _logger.Log("Content marked for write: " + localDirectoryContent.WriteInProgress, Constants.LoggingLevel.All); int remainingBytes = fileSize; // Check if the file has been completely written. // Number of bytes in the cluster chain must be checked in case this file is being written // in multiple passes due to lack of available RAM on the Atari while (!FAT16Helper.IsEndOfClusterChain(fileClusterIndex)) { // Keep final cluster updated in case this file is being written in multiple passes localDirectoryContent.FinalCluster = fileClusterIndex; remainingBytes -= Parameters.BytesPerCluster; fileClusterIndex = FatGetClusterValue(fileClusterIndex); } bool allBytesAvailable = remainingBytes <= 0 && FAT16Helper.IsEndOfFile(fileClusterIndex); _logger.Log("All bytes available?: " + allBytesAvailable, Constants.LoggingLevel.All); // Final FAT cluster value in chain matches a fully written file if (allBytesAvailable) { localDirectoryContent.WriteInProgress = false; try { _logger.Log("Finding existing content info for local file \"" + newContentPath + "\".", Constants.LoggingLevel.All); localDirectoryContent = localDirectoryContentInfos.Where(ldci => ldci.LocalPath == newContentPath).Single(); localDirectoryContent.StartCluster = entryStartClusterIndex; _logger.Log("Writing to local file \"" + newContentPath + "\".", Constants.LoggingLevel.Info); using (BinaryWriter FileBinaryWriter = new BinaryWriter(File.OpenWrite(GetAbsolutePath(newContentPath)))) { fileClusterIndex = entryStartClusterIndex; remainingBytes = fileSize; int fileOffset = 0; while (!FAT16Helper.IsEndOfFile(fileClusterIndex)) { _clusterInfos[fileClusterIndex].LocalDirectoryContent = localDirectoryContent; _clusterInfos[fileClusterIndex].FileOffset = fileOffset; FileBinaryWriter.Write(_clusterInfos[fileClusterIndex].DataBuffer, 0, Math.Min(_clusterInfos[fileClusterIndex].DataBuffer.Length, remainingBytes)); remainingBytes -= _clusterInfos[fileClusterIndex].DataBuffer.Length; fileOffset += _clusterInfos[fileClusterIndex].DataBuffer.Length; // Buffer has been written to disk; free up RAM _clusterInfos[fileClusterIndex].DataBuffer = null; localDirectoryContent.FinalCluster = fileClusterIndex; fileClusterIndex = FatGetClusterValue(fileClusterIndex); } _logger.Log("Bytes remaining after write:" + remainingBytes, Constants.LoggingLevel.All); } } catch (Exception ex) { _logger.LogException(ex); } } } } } catch (Exception ex) { _logger.LogException(ex); } }