private void ImportLocalDirectory(List <LocalDirectoryContentInfo> localDirectoryContentInfos, DirectoryInfo directoryInfo, int parentDirectoryCluster) { int newDirectoryClusterIndex = AddNextFreeClusterToChain(); _clusterInfos[newDirectoryClusterIndex] = new ClusterInfo() { FileOffset = -1, DataBuffer = new byte[Parameters.BytesPerCluster] }; FatAddDirectoryEntry(localDirectoryContentInfos, parentDirectoryCluster, newDirectoryClusterIndex, directoryInfo.Parent.FullName, directoryInfo.Name, FAT16Helper.GetShortFileName(directoryInfo.Name), FAT16Helper.DirectoryIdentifier, directoryInfo.LastWriteTime, 0); FatAddDirectoryEntry(localDirectoryContentInfos, newDirectoryClusterIndex, newDirectoryClusterIndex, directoryInfo.FullName, ".", ".", FAT16Helper.DirectoryIdentifier, directoryInfo.LastWriteTime, 0); FatAddDirectoryEntry(localDirectoryContentInfos, newDirectoryClusterIndex, parentDirectoryCluster, directoryInfo.Parent.FullName, "..", "..", FAT16Helper.DirectoryIdentifier, directoryInfo.LastWriteTime, 0); ImportLocalDiskContent(localDirectoryContentInfos, directoryInfo.FullName, newDirectoryClusterIndex); }
private void ImportLocalFile(List <LocalDirectoryContentInfo> localDirectoryContentInfos, FileInfo fileInfo, int directoryClusterIndex) { long fileOffset = 0; int fileentryStartClusterIndex = 0; int? nextFileClusterIndex = null; while (fileOffset < fileInfo.Length) { try { nextFileClusterIndex = AddNextFreeClusterToChain(nextFileClusterIndex); if (fileentryStartClusterIndex == _rootDirectoryClusterIndex) { fileentryStartClusterIndex = nextFileClusterIndex.Value; } _clusterInfos[nextFileClusterIndex.Value] = new ClusterInfo() { FileOffset = fileOffset }; fileOffset += Parameters.BytesPerCluster; } 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; } } // handle duplicate short filenames string TOSFileName = FAT16Helper.GetShortFileName(fileInfo.Name); int duplicateId = 1; while (localDirectoryContentInfos.Where(ldi => ldi.TOSFileName.Equals(TOSFileName, StringComparison.InvariantCultureIgnoreCase) && ldi.DirectoryCluster == directoryClusterIndex).Any()) { int numberStringLength = duplicateId.ToString().Length + 1; // +1 for ~ int replaceIndex = TOSFileName.LastIndexOf('.') != -1 ? TOSFileName.LastIndexOf('.') : TOSFileName.Length; replaceIndex -= numberStringLength; TOSFileName = TOSFileName.Remove(replaceIndex, numberStringLength).Insert(replaceIndex, $"~{duplicateId}"); duplicateId++; } FatAddDirectoryEntry(localDirectoryContentInfos, directoryClusterIndex, fileentryStartClusterIndex, fileInfo.DirectoryName, fileInfo.Name, TOSFileName, 0x00, fileInfo.LastWriteTime, fileInfo.Length); }
public Disk(DiskParameters diskParams, ILogger logger) { _logger = logger; Parameters = diskParams; _rootDirectoryClusterIndex = 0; try { InitDiskContentVariables(); int maxRootDirectoryEntries = ((diskParams.RootDirectorySectors * diskParams.BytesPerSector) / 32) - 2; // Each entry is 32 bytes, 2 entries reserved for . and .. FAT16Helper.ValidateLocalDirectory(diskParams.LocalDirectoryPath, diskParams.DiskTotalBytes, maxRootDirectoryEntries, diskParams.SectorsPerCluster, diskParams.TOS); } catch (Exception ex) { _logger.LogException(ex, ex.Message); throw; } ImportLocalDiskContent(_localDirectoryContentInfos, Parameters.LocalDirectoryPath, _rootDirectoryClusterIndex); }
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); } }
private void SyncDirectoryClusterToLocalDisk(List <LocalDirectoryContentInfo> localDirectoryContentInfos, int clusterIndex) { byte[] directoryData; directoryData = GetDirectoryClusterData(clusterIndex); // Only check for changes if this cluster contains directory entry information if (clusterIndex == _rootDirectoryClusterIndex || _clusterInfos[clusterIndex].FileOffset == -1) { bool continueCheckingEntries = true; do { _logger.Log($"Updating directory cluster {clusterIndex}", Constants.LoggingLevel.All); int directoryEntryIndex = 0; while (directoryEntryIndex < directoryData.Length && directoryData[directoryEntryIndex] != 0) { // The entry is not "." or "..". if (directoryData[directoryEntryIndex] != 0x2e) { string fileName = ASCIIEncoding.ASCII.GetString(directoryData, directoryEntryIndex, 8).Trim(); string fileExtension = ASCIIEncoding.ASCII.GetString(directoryData, directoryEntryIndex + 8, 3).Trim(); if (fileExtension != "") { fileName += "." + fileExtension; } int entryStartClusterIndex = directoryData[directoryEntryIndex + 26] | (directoryData[directoryEntryIndex + 27] << 8); // Find the matching local content and check what happened to it. var localContent = FindLocalDirectoryContentInfo(localDirectoryContentInfos, clusterIndex, directoryEntryIndex, entryStartClusterIndex); if (localContent != null) { if (localContent.TOSFileName != fileName) { if (directoryData[directoryEntryIndex] == FAT16Helper.DeletedEntryIdentifier) { DeleteLocalDirectoryOrFile(localDirectoryContentInfos, directoryData, directoryEntryIndex, localContent); } else { RenameLocalDirectoryOrFile(directoryData, directoryEntryIndex, localContent, fileName); } } } // Entry is new else if (directoryData[directoryEntryIndex] != FAT16Helper.DeletedEntryIdentifier) { UpdateLocalDirectoryOrFile(localDirectoryContentInfos, directoryData, clusterIndex, directoryEntryIndex, entryStartClusterIndex, fileName); } } directoryEntryIndex += 32; } if (directoryEntryIndex < directoryData.Length || FAT16Helper.IsEndOfClusterChain(FatGetClusterValue(clusterIndex))) { continueCheckingEntries = false; } else { var nextClusterIndex = FatGetClusterValue(clusterIndex); _logger.Log($"Directory cluster {clusterIndex} continues in cluster {nextClusterIndex}", Constants.LoggingLevel.All); // If the next directory cluster is new, ensure it has a LocalDirectoryContentInfo assigned if (_clusterInfos[nextClusterIndex].LocalDirectoryContent == null) { _clusterInfos[nextClusterIndex].LocalDirectoryContent = _clusterInfos[clusterIndex].LocalDirectoryContent; _clusterInfos[nextClusterIndex].FileOffset = FAT16Helper.DirectoryFileOffset; _logger.Log($"Directory cluster {clusterIndex} extended to cluster {nextClusterIndex}", Constants.LoggingLevel.All); } clusterIndex = nextClusterIndex; directoryData = GetDirectoryClusterData(clusterIndex); } } while (continueCheckingEntries); } }
public void ValidDiskSizes(TOSVersion tosVersion, int expectedMaxDiskSizeBytes) { var maxDiskSizeBytes = FAT16Helper.MaxDiskSizeBytes(tosVersion, _sectorsPerCluster); Assert.AreEqual(expectedMaxDiskSizeBytes, maxDiskSizeBytes); }
public void CreateShortFileNameFromInvalidFileName(string invalidFileName, string expectedShortFileName) { var shortFileName = FAT16Helper.GetShortFileName(invalidFileName); Assert.AreEqual(expectedShortFileName, shortFileName); }
public void CreateShortFileNameFromLongFileName(string longFileName, string expectedShortFileName) { var shortFileName = FAT16Helper.GetShortFileName(longFileName); Assert.AreEqual(expectedShortFileName, shortFileName); }