From 81234d1cd901eb82dfde142fb4f6da69aa37ebb1 Mon Sep 17 00:00:00 2001 From: fanoI Date: Sat, 18 Nov 2017 11:12:17 +0100 Subject: [PATCH] StreamReader implemented but found bug in IL2CPU plug resolution :-( --- .../Helper/EqualityHelper.cs | 38 +++ Tests/Cosmos.Kernel.Tests.Fat/Kernel.cs | 2 +- .../System.IO/StreamWriterTest.cs | 72 +++-- .../Cosmos.TestRunner.Core/TestKernelSets.cs | 8 +- source/Cosmos.Core_Plugs/System/StringImpl.cs | 2 +- .../Cosmos.System2/Encoding/CosmosEncoding.cs | 41 ++- .../Encoding/CosmosUTF8Encoding.cs | 248 ++++++++++++++++-- .../System/IO/CosmosFileSystem.cs | 56 +++- .../System/IO/FileImpl.cs | 87 +++--- .../System/IO/FileStreamImpl.cs | 33 ++- .../System/IO/PathInternalImpl.cs | 2 +- .../System/IO/StreamWriterImpl.cs | 119 +++++---- 12 files changed, 560 insertions(+), 148 deletions(-) diff --git a/Tests/Cosmos.Compiler.Tests.Bcl/Helper/EqualityHelper.cs b/Tests/Cosmos.Compiler.Tests.Bcl/Helper/EqualityHelper.cs index 248208968..5c49776e8 100644 --- a/Tests/Cosmos.Compiler.Tests.Bcl/Helper/EqualityHelper.cs +++ b/Tests/Cosmos.Compiler.Tests.Bcl/Helper/EqualityHelper.cs @@ -16,5 +16,43 @@ namespace Cosmos.Compiler.Tests.Bcl.Helper else return false; } + + /// + /// Utility method to test Byte[] equality. + /// + /// Byte array. + /// Byte array. + /// True if the elements in the arrays are equal otherwise false. + public static bool ByteArrayAreEquals(byte[] a1, byte[] a2) + { + if (ReferenceEquals(a1, a2)) + { + //mDebugger.Send("a1 and a2 are the same Object"); + return true; + } + + if (a1 == null || a2 == null) + { + //mDebugger.Send("a1 or a2 is null so are different"); + return false; + } + + if (a1.Length != a2.Length) + { + //mDebugger.Send("a1.Length != a2.Length so are different"); + return false; + } + + for (int i = 0; i < a1.Length; i++) + { + if (a1[i] != a2[i]) + { + //mDebugger.Send("In position " + i + " a byte is different"); + return false; + } + } + + return true; + } } } diff --git a/Tests/Cosmos.Kernel.Tests.Fat/Kernel.cs b/Tests/Cosmos.Kernel.Tests.Fat/Kernel.cs index 8f9b9d72c..a7fc81dd2 100644 --- a/Tests/Cosmos.Kernel.Tests.Fat/Kernel.cs +++ b/Tests/Cosmos.Kernel.Tests.Fat/Kernel.cs @@ -39,7 +39,7 @@ namespace Cosmos.Kernel.Tests.Fat PathTest.Execute(mDebugger); DirectoryTest.Execute(mDebugger); #endif - //FileTest.Execute(mDebugger); +// FileTest.Execute(mDebugger); #if false FileStreamTest.Execute(mDebugger); DirectoryInfoTest.Execute(mDebugger); diff --git a/Tests/Cosmos.Kernel.Tests.Fat/System.IO/StreamWriterTest.cs b/Tests/Cosmos.Kernel.Tests.Fat/System.IO/StreamWriterTest.cs index 4c3fec2cc..768b0c376 100644 --- a/Tests/Cosmos.Kernel.Tests.Fat/System.IO/StreamWriterTest.cs +++ b/Tests/Cosmos.Kernel.Tests.Fat/System.IO/StreamWriterTest.cs @@ -2,6 +2,7 @@ using Cosmos.TestRunner; using Cosmos.Debug.Kernel; using System; +using System.Text; namespace Cosmos.Kernel.Tests.Fat.System.IO { @@ -11,32 +12,63 @@ namespace Cosmos.Kernel.Tests.Fat.System.IO /// Tests System.IO.StreamWriter plugs. /// public static void Execute(Debugger mDebugger) - { + { + string file = @"0:\test.txt"; + mDebugger.Send("START TEST: StreamWriter:"); - mDebugger.Send("Create StreamWriter"); - using (var xSW = new StreamWriter(@"0:\test.txt")) + /* + * To Show that UTF-8 is effectively working you write in the file "Cosmos is wonderful!" in Japanase + * and read it again + */ + var text = "Cosmos 素晴らしいです!"; + + using (var xSW = new StreamWriter(file)) { - - if (xSW != null) - { - try - { - mDebugger.Send("Start writing"); + if (xSW == null) + Assert.IsTrue(false, $"Failed to create StreamWriter for file {file}"); - xSW.Write("0123"); - //xSW.Write("A line of text for testing\nSecond line"); - } - catch (Exception e) - { - Assert.IsTrue(false, $"Couldn't write to file 0:\test.txt using StreamWriter {e.Message}"); - } - } - else + try { - Assert.IsTrue(false, @"Failed to create StreamWriter for file 0:\test.txt"); + mDebugger.Send("Start writing"); + + xSW.Write(text); } - + catch + { + Assert.IsTrue(false, $"Couldn't write to file {file} using StreamWriter"); + } + } + mDebugger.Send("END TEST"); + +#if true + /* We use StreamReader() instead of File now it is more "correct" and we test 2 classes in one too! */ + mDebugger.Send("START TEST: StreamReader:"); + using (var xSR = new StreamReader(file)) + { + if (xSR == null) + Assert.IsTrue(false, $"Failed to create StreamReader for file {file}"); + + try + { + mDebugger.Send("Start reading"); + var readText = xSR.ReadToEnd(); + Assert.IsTrue(text == readText, "Failed to write and read file"); + } + catch + { + Assert.IsTrue(false, $"Couldn't read from file {file} using StreamReader"); + } + } +#endif + + using (StreamReader xSR2 = new StreamReader(file, Encoding.UTF8, true)) + { + mDebugger.Send("Reading using different Ctor!!!"); + var readText2 = xSR2.ReadToEnd(); + mDebugger.Send($"Read {readText2}"); + + Assert.IsTrue(text == readText2, "Failed to write and read file"); } mDebugger.Send("END TEST"); diff --git a/Tests/Cosmos.TestRunner.Core/TestKernelSets.cs b/Tests/Cosmos.TestRunner.Core/TestKernelSets.cs index d5eae9a34..e9fbfd384 100644 --- a/Tests/Cosmos.TestRunner.Core/TestKernelSets.cs +++ b/Tests/Cosmos.TestRunner.Core/TestKernelSets.cs @@ -17,17 +17,19 @@ namespace Cosmos.TestRunner.Core { //yield return typeof(BoxingTests.Kernel); - yield return typeof(Cosmos.Compiler.Tests.TypeSystem.Kernel); - yield return typeof(Cosmos.Compiler.Tests.Bcl.Kernel); +// yield return typeof(Cosmos.Compiler.Tests.TypeSystem.Kernel); +// yield return typeof(Cosmos.Compiler.Tests.Bcl.Kernel); //yield return typeof(Cosmos.Compiler.Tests.Encryption.Kernel); +#if false yield return typeof(Cosmos.Compiler.Tests.Exceptions.Kernel); yield return typeof(Cosmos.Compiler.Tests.LinqTests.Kernel); yield return typeof(Cosmos.Compiler.Tests.MethodTests.Kernel); yield return typeof(Cosmos.Compiler.Tests.SimpleWriteLine.Kernel); yield return typeof(Cosmos.Compiler.Tests.SingleEchoTest.Kernel); +#endif /* Let's test only this for now */ yield return typeof(Cosmos.Kernel.Tests.Fat.Kernel); - yield return typeof(Cosmos.Kernel.Tests.IO.Kernel); + //yield return typeof(Cosmos.Kernel.Tests.IO.Kernel); #if false yield return typeof(SimpleStructsAndArraysTest.Kernel); yield return typeof(VGACompilerCrash.Kernel); diff --git a/source/Cosmos.Core_Plugs/System/StringImpl.cs b/source/Cosmos.Core_Plugs/System/StringImpl.cs index 035b80149..deb729227 100644 --- a/source/Cosmos.Core_Plugs/System/StringImpl.cs +++ b/source/Cosmos.Core_Plugs/System/StringImpl.cs @@ -1,4 +1,4 @@ -#define COSMOSDEBUG +//#define COSMOSDEBUG using System; using System.Globalization; using Cosmos.Common; diff --git a/source/Cosmos.System2/Encoding/CosmosEncoding.cs b/source/Cosmos.System2/Encoding/CosmosEncoding.cs index 36e09c2f9..fa61ab62e 100644 --- a/source/Cosmos.System2/Encoding/CosmosEncoding.cs +++ b/source/Cosmos.System2/Encoding/CosmosEncoding.cs @@ -6,8 +6,45 @@ namespace Cosmos.System2.Encoding { public abstract class CosmosEncoding { - public abstract Byte[] GetBytes(String s); - public abstract String GetString(Byte[] bytes); + public abstract int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex); + + //public abstract String GetString(Byte[] bytes); + public abstract int GetMaxByteCount(int ByteCount); + + public virtual byte[] GetBytes(string s) + { + byte[] bytes = new byte[GetMaxByteCount(s.Length)]; + char[] textToEncode = s.ToCharArray(); + int nBytes; + + nBytes = GetBytes(textToEncode, 0, textToEncode.Length, bytes, 0); + + /* + * This could be not the fastest method (it creates a new array and then does a copy of the old + * until 'nBytes') but the alternative way was to call a version of GetBytes() that only counts + * the encoded bytes and allocate the array using the correct size and then call GetBytes(). + * This is the approach used by the real Encoding class I'm unsure it is faster sincerely... + * in the end is doing the encoding two times! + * + * Remeber - in any case - that this is a temporary solution we should plug the real Encoding class... + */ + Array.Resize(ref bytes, nBytes); + return bytes; + } + + public abstract int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex); + + public string GetString(byte[] bytes) + { + int numChar; + char[] chars = new char[bytes.Length]; + + numChar = GetChars(bytes, 0, bytes.Length, chars, 0); + + Array.Resize(ref chars, numChar); + + return new string(chars); + } } } diff --git a/source/Cosmos.System2/Encoding/CosmosUTF8Encoding.cs b/source/Cosmos.System2/Encoding/CosmosUTF8Encoding.cs index 8d2fa17eb..75a558970 100644 --- a/source/Cosmos.System2/Encoding/CosmosUTF8Encoding.cs +++ b/source/Cosmos.System2/Encoding/CosmosUTF8Encoding.cs @@ -8,32 +8,242 @@ namespace Cosmos.System2.Encoding { public class CosmosUTF8Encoding : CosmosEncoding { - public override byte[] GetBytes(string s) - { - Global.mFileSystemDebugger.SendInternal($"Encoding string {s}"); + private const uint UNI_REPLACEMENT_CHAR = 0x0000FFFD; + private const uint UNI_SUR_HIGH_START = 0xD800; + private const uint UNI_SUR_HIGH_END = 0xDBFF; + private const uint UNI_SUR_LOW_START = 0xDC00; + private const uint UNI_SUR_LOW_END = 0xDFFF; + private const uint UNI_MAX_BMP = 0x0000FFFF; + private const uint UNI_MAX_UTF16 = 0x0010FFFF; + private const int halfShift = 10; + private const int halfBase = 0x0010000; + private const uint halfMask = 0x3FF; - //byte[] xResult = new byte[GetMaxByteCount(s.Length)]; - List xResult = new List(); - /* Only Ascii for now */ - foreach (var aChar in s) + /* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ + private static int[] trailingBytesForUTF8 = new int[] { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + }; + + /* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ + static uint[] offsetsFromUTF8 = new uint[] { 0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, + 0x82082080 }; + + private static int GetCharBytes(uint ch, byte[] bytes, int byteIndex, int bytePos) + { + int bytesToWrite; + + // Filter out byte order marks and invalid character 0xFFFF + if ((ch == 0xFEFF) || (ch == 0xFFFE) || (ch == 0xFFFF)) { - Global.mFileSystemDebugger.SendInternal($"Encoding char {aChar}"); - if (aChar > 0 || aChar < 127) - xResult.Add((byte)aChar); - else - throw new ArgumentOutOfRangeException("Input string contains invalid characters for UTF-8"); + return bytePos; } - return xResult.ToArray(); - //throw new NotImplementedException("GetBytes()"); + /* Figure out how many bytes the result will require */ + if (ch < 0x80) /* 0XXX XXXX one byte */ + bytesToWrite = 1; + else if (ch < 0x800) /* 110X XXXX two bytes */ + bytesToWrite = 2; + else if (ch < 0x10000) /* 1110 XXXX three bytes */ + bytesToWrite = 3; + else if (ch < 0x110000) /* 1111 0XXX four bytes */ + bytesToWrite = 4; + else /* Invalid Unicode sequence Encode it as UNI_REPLACEMENT_CHAR */ + { + ch = UNI_REPLACEMENT_CHAR; + return GetCharBytes(ch, bytes, byteIndex, bytePos); + } + + /* Check if there is sufficient space on bytes before writing on it */ + if (bytes.Length - (byteIndex + bytePos) < bytesToWrite) + throw new ArgumentException("bytes has no sufficient space"); + + switch (bytesToWrite) + { + case 1: + bytes[byteIndex + bytePos + 0] = (byte)ch; + break; + + case 2: + bytes[byteIndex + bytePos + 0] = (byte)(0xC0 | (ch >> 6)); + bytes[byteIndex + bytePos + 1] = (byte)(0x80 | (ch & 0x3F)); + break; + + case 3: + bytes[byteIndex + bytePos + 0] = (byte)(0xE0 | (ch >> 12)); + bytes[byteIndex + bytePos + 1] = (byte)(0x80 | ((ch >> 6) & 0x3F)); + bytes[byteIndex + bytePos + 2] = (byte)(0x80 | (ch & 0x3F)); + break; + + case 4: + bytes[byteIndex + bytePos + 0] = (byte)(0xF0 | (ch >> 18)); + bytes[byteIndex + bytePos + 1] = (byte)(0x80 | ((ch >> 12) & 0x3F)); + bytes[byteIndex + bytePos + 2] = (byte)(0x80 | ((ch >> 6) & 0x3F)); + bytes[byteIndex + bytePos + 3] = (byte)(0x80 | (ch & 0x3F)); + break; + } + + //bytePos += bytesToWrite; + return bytesToWrite; } - /* Some UFT-8 char can occupy 3 bytes */ - public override int GetMaxByteCount(int ByteCount) => 3 * ByteCount; - - public override string GetString(byte[] bytes) + private static uint HandleSurrogatePairs(uint SurrFirst, uint SurrSecond) { - throw new NotImplementedException("GetString()"); + if (SurrSecond >= UNI_SUR_LOW_START && SurrSecond <= UNI_SUR_LOW_END) + { + return ((SurrFirst - UNI_SUR_HIGH_START) << halfShift) + + (SurrSecond - UNI_SUR_LOW_START) + halfBase; + } + else /* it's an unpaired high surrogate */ + { + throw new ArgumentException("Source contains unpaired surrogate"); + } + } + + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + { + if (chars == null) + { + Global.mFileSystemDebugger.SendInternal($"chars is null returning 0"); + return 0; + } + + if (charIndex == 0 && charCount == 0) + { + Global.mFileSystemDebugger.SendInternal($"charIndex and charCount both 0 returning 0"); + return 0; + } + + int bytePos = 0; + + for (int i = charIndex; i < charCount; i++) + { + uint ch = chars[i]; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) + { + /* There is the next part of the surrogate? */ + if (chars.Length >= i + 1) + { + i++; + ch = HandleSurrogatePairs(ch, chars[i]); + } + else + throw new ArgumentException("Source contains unpaired surrogate"); + } + + bytePos += GetCharBytes(ch, bytes, byteIndex, bytePos); + } + + return bytePos; + } + + /* Some UFT-8 "character" can occupy 4 bytes */ + public override int GetMaxByteCount(int ByteCount) => 4 * ByteCount; + + private static uint GetCharFromUFT8(byte[] bytes, out int bytesConsumed, int bytePos) + { + //uint ch = bytes[bytePos]; + uint ch = 0; + + int UtfTrailingBytes = trailingBytesForUTF8[bytes[bytePos]]; + int Uft8CharLen = UtfTrailingBytes + 1; + bytesConsumed = Uft8CharLen; + + int i = bytePos; + /* We "consume" the bytes and do the needed bitmasking to obtain the corrisponding codepoint */ + do + { + ch += bytes[i]; + i++; + --Uft8CharLen; + if (Uft8CharLen != 0) + ch <<= 6; + } while (Uft8CharLen > 0); + ch -= offsetsFromUTF8[UtfTrailingBytes]; + + /* Target is a character <= 0xFFFF */ + if (ch <= UNI_MAX_BMP) + { + /* Invalid surrugates */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) + return UNI_REPLACEMENT_CHAR; + /* normal case */ + else + return (char)ch; + } + else if (ch > UNI_MAX_UTF16) + { + return UNI_REPLACEMENT_CHAR; + } + /* surrogate pairs */ + else + { + ushort lo = 0; + ushort hi = 0; + ch -= halfBase; + hi = (ushort)((ch >> halfShift) + UNI_SUR_HIGH_START); + lo = (ushort)((ch & halfMask) + UNI_SUR_LOW_START); + /* + * We pack the two halves of the pair in an uint sadly we need to unpack them later + * the alternative was to make this function return an array of character that will be really + * used only in this case :-( + */ + ch = (uint)((uint)hi << 16 | (uint)lo); + + return ch; + } + } + + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + { + uint ch = 0; + int bytesConsumed = 0; + //for (i = byteIndex; i < byteCount; i++) + int numChar = 0; + int bytePos = byteIndex; + while (byteCount != 0) + { + ch = GetCharFromUFT8(bytes, out bytesConsumed, bytePos); + /* check that chars has sufficient space */ + if (chars.Length < (charIndex + numChar)) + throw new ArgumentException("chars has no sufficient space"); + + if (ch < UNI_SUR_HIGH_START) + chars[charIndex + numChar] = (char)ch; + else + { + /* Unpach the uint in the two paired surrugates */ + char chHigh = (char)(ch >> 16); + char chLow = (char)(ch & 0xFFFF); + chars[charIndex + numChar] = chHigh; + chars[charIndex + numChar + 1] = chLow; + numChar++; + } + + /* skip the part of 'bytes' we have already consumed */ + byteCount -= bytesConsumed; + bytePos += bytesConsumed; + numChar++; + } + + return numChar; } } } diff --git a/source/Cosmos.System2_Plugs/System/IO/CosmosFileSystem.cs b/source/Cosmos.System2_Plugs/System/IO/CosmosFileSystem.cs index f6c8872e2..e1507f98e 100644 --- a/source/Cosmos.System2_Plugs/System/IO/CosmosFileSystem.cs +++ b/source/Cosmos.System2_Plugs/System/IO/CosmosFileSystem.cs @@ -89,15 +89,15 @@ namespace Cosmos.System_Plugs.System.IO public static object /* FileStreamBase */ Open(object aThis, string fullPath, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, FileStream parent) { - Global.mFileSystemDebugger.SendInternal("In FileStream.InitializeStream"); + Global.mFileSystemDebugger.SendInternal("In CosmosFileSystem.Open"); if (fullPath == null) { - Global.mFileSystemDebugger.SendInternal("In FileStream.Ctor: Path == null is true"); + Global.mFileSystemDebugger.SendInternal("In CosmosFileSystem.Open: Path == null is true"); throw new ArgumentNullException("The file path cannot be null."); } if (fullPath.Length == 0) { - Global.mFileSystemDebugger.SendInternal("In FileStream.Ctor: Path.Length == 0 is true"); + Global.mFileSystemDebugger.SendInternal("In CosmosFileSystem.Open: Path.Length == 0 is true"); throw new ArgumentException("The file path cannot be empty."); } @@ -108,6 +108,17 @@ namespace Cosmos.System_Plugs.System.IO Stream aStream = null; + Global.mFileSystemDebugger.SendInternal($"Create Mode aPath {fullPath}"); + + var xEntry = VFSManager.CreateFile(fullPath); + if (xEntry == null) + { + return null; + } + + aStream = VFSManager.GetFileStream(fullPath); + +#if false switch (mode) { case FileMode.Append: @@ -129,7 +140,8 @@ namespace Cosmos.System_Plugs.System.IO case FileMode.Create: Global.mFileSystemDebugger.SendInternal("Create Mode aPath will be overwritten if existing"); // TODO it seems that GetFileStream effectively Creates the file if not exist - aStream = File.Create(fullPath); + //aStream = File.Create(fullPath); + aStream = VFSManager.GetFileStream(fullPath); break; case FileMode.CreateNew: @@ -141,7 +153,8 @@ namespace Cosmos.System_Plugs.System.IO Global.mFileSystemDebugger.SendInternal("CreateNew Mode with aPath not existing new file created"); // TODO it seems that GetFileStream effectively Creates the file if it does not exist - aStream = File.Create(fullPath); + //aStream = File.Create(fullPath); + aStream = VFSManager.GetFileStream(fullPath); break; case FileMode.Open: @@ -181,6 +194,7 @@ namespace Cosmos.System_Plugs.System.IO Global.mFileSystemDebugger.SendInternal("The mode " + mode + "is out of range"); throw new ArgumentOutOfRangeException("The file mode is invalid"); } +#endif return aStream; } @@ -211,5 +225,37 @@ namespace Cosmos.System_Plugs.System.IO } } } + + public static void DeleteFile(object aThis, string fullPath) + { + Global.mFileSystemDebugger.SendInternal($"DeleteFile : fullPath = {fullPath}"); + VFSManager.DeleteFile(fullPath); + } + + public static void CopyFile(object aThis, string sourceFullPath, string destFullPath, bool overwrite) + { + Global.mFileSystemDebugger.SendInternal($"CopyFile {sourceFullPath} into {destFullPath}"); + + // The destination path may just be a directory into which the file should be copied. + // If it is, append the filename from the source onto the destination directory + if (Directory.Exists(destFullPath)) + { + destFullPath = Path.Combine(destFullPath, Path.GetFileName(sourceFullPath)); + } + + // Copy the contents of the file from the source to the destination, creating the destination in the process + using (var src = new FileStream(sourceFullPath, FileMode.Open)) + using (var dst = new FileStream(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew)) + { + int xSize = (int)src.Length; + Global.mFileSystemDebugger.SendInternal($"size of {sourceFullPath} is {xSize} bytes"); + byte[] content = new byte[xSize]; + Global.mFileSystemDebugger.SendInternal($"content byte buffer allocated"); + src.Read(content, 0, xSize); + Global.mFileSystemDebugger.SendInternal($"content byte buffer read"); + dst.Write(content, 0, xSize); + Global.mFileSystemDebugger.SendInternal($"content byte buffer written"); + } + } } } diff --git a/source/Cosmos.System2_Plugs/System/IO/FileImpl.cs b/source/Cosmos.System2_Plugs/System/IO/FileImpl.cs index 52b0a04f4..d5d456e37 100644 --- a/source/Cosmos.System2_Plugs/System/IO/FileImpl.cs +++ b/source/Cosmos.System2_Plugs/System/IO/FileImpl.cs @@ -9,6 +9,7 @@ using Cosmos.IL2CPU.API; using Cosmos.IL2CPU.API.Attribs; using Cosmos.System.FileSystem; using Cosmos.System.FileSystem.VFS; +using System.Text; namespace Cosmos.System_Plugs.System.IO { @@ -16,6 +17,7 @@ namespace Cosmos.System_Plugs.System.IO [Plug(Target = typeof(File))] public static class FileImpl { +#if false public static bool Exists(string aFile) { Global.mFileSystemDebugger.SendInternal("File.Exists:"); @@ -29,7 +31,17 @@ namespace Cosmos.System_Plugs.System.IO return VFSManager.FileExists(aFile); } - +#endif + public static string ReadAllText(string aFile) + { + string result; + using (StreamReader streamReader = new StreamReader(aFile, Encoding.UTF8, true)) + { + result = streamReader.ReadToEnd(); + } + return result; + } +#if false public static string ReadAllText(string aFile) { Global.mFileSystemDebugger.SendInternal("File.ReadAllText:"); @@ -55,7 +67,9 @@ namespace Cosmos.System_Plugs.System.IO return xResultStr; } } +#endif +#if false public static void WriteAllText(string aFile, string aText) { Global.mFileSystemDebugger.SendInternal("Creating stream with file " + aFile); @@ -93,6 +107,7 @@ namespace Cosmos.System_Plugs.System.IO } } + public static void AppendAllText(string aFile, string aText) { Global.mFileSystemDebugger.SendInternal("Creating stream in Append Mode with file " + aFile); @@ -106,6 +121,7 @@ namespace Cosmos.System_Plugs.System.IO } } + public static string[] ReadAllLines(string aFile) { String text = ReadAllText(aFile); @@ -119,15 +135,34 @@ namespace Cosmos.System_Plugs.System.IO return result; } - +#endif + + /* + * Plug needed for the usual issue that Array can not be converted in IEnumerable... it is starting + * to become annoying :-( + */ public static void WriteAllLines(string aFile, string[] contents) { - string text = String.Join(Environment.NewLine, contents); - + if (aFile == null) + { + throw new ArgumentNullException("path"); + } + if (contents == null) + { + throw new ArgumentNullException("contents"); + } + if (aFile.Length == 0) + { + throw new ArgumentException("Empty", "aFile"); + } + Global.mFileSystemDebugger.SendInternal("Writing contents"); - Global.mFileSystemDebugger.SendInternal(text); - - WriteAllText(aFile, text); + + using (var xSW = new StreamWriter(aFile)) + { + foreach (var current in contents) + xSW.WriteLine(current); + } } public static byte[] ReadAllBytes(string aFile) @@ -155,40 +190,14 @@ namespace Cosmos.System_Plugs.System.IO } } +#if false public static void Copy(string srcFile, string destFile) { - try + using (var xFS = new FileStream(srcFile, FileMode.Open)) { - byte[] srcFileBytes = File.ReadAllBytes(srcFile); - File.WriteAllBytes(destFile, srcFileBytes); - } - catch (IOException ioEx) - { - throw new IOException("File Copy", ioEx); - } - } - - public static void Copy(string srcFile, string destFile, bool overwriting) - { - if (overwriting) - { - if (File.Exists(destFile)) - { - File.Delete(destFile); - } - - Copy(srcFile, destFile); - } - else - { - if (!File.Exists(destFile)) - { - Copy(srcFile, destFile); - } - else - { - throw new IOException("destFileName exists and overwrite is false."); - } + var xBuff = new byte[(int)xFS.Length]; + var yFS = new FileStream(destFile, FileMode.Create); + yFS.Write(xBuff, 0, xBuff.Length); } } @@ -199,6 +208,7 @@ namespace Cosmos.System_Plugs.System.IO VFSManager.DeleteFile(xFullPath); } + public static FileStream Create(string aFile) { Global.mFileSystemDebugger.SendInternal("File.Create:"); @@ -216,5 +226,6 @@ namespace Cosmos.System_Plugs.System.IO return new FileStream(aFile, FileMode.Open); } +#endif } } diff --git a/source/Cosmos.System2_Plugs/System/IO/FileStreamImpl.cs b/source/Cosmos.System2_Plugs/System/IO/FileStreamImpl.cs index 9c6c646a1..1f85d8078 100644 --- a/source/Cosmos.System2_Plugs/System/IO/FileStreamImpl.cs +++ b/source/Cosmos.System2_Plugs/System/IO/FileStreamImpl.cs @@ -19,19 +19,37 @@ namespace Cosmos.System_Plugs.System.IO // public static unsafe void Ctor(String aThis, [FieldAccess(Name = "$$Storage$$")]ref Char[] aStorage, Char[] aChars, int aStartIndex, int aLength, - public static void Ctor(FileStream aThis, string aPathname, FileMode aMode, - [FieldAccess(Name = InnerStreamFieldId)] ref Stream innerStream) + private static void Init(string aPathname, FileMode aMode, ref Stream innerStream) { Global.mFileSystemDebugger.SendInternal("FileStream.Ctor:"); innerStream = InitializeStream(aPathname, aMode); } + + public static void Ctor(FileStream aThis, string aPathname, FileMode aMode, + [FieldAccess(Name = InnerStreamFieldId)] ref Stream innerStream) + { + Init(aPathname, aMode, ref innerStream); +#if false + Global.mFileSystemDebugger.SendInternal("FileStream.Ctor:"); + + innerStream = InitializeStream(aPathname, aMode); +#endif + } + public static void CCtor() { // plug cctor as it (indirectly) uses Thread.MemoryBarrier() } + public static void Ctor(FileStream aThis, string aPathname, FileMode aMode, FileAccess access, + FileShare share, int bufferSize, FileOptions options, + [FieldAccess(Name = InnerStreamFieldId)] ref Stream innerStream) + { + Init(aPathname, aMode, ref innerStream); + } + public static int Read(FileStream aThis, byte[] aBuffer, int aOffset, int aCount, [FieldAccess(Name = InnerStreamFieldId)] ref Stream innerStream) { @@ -48,7 +66,7 @@ namespace Cosmos.System_Plugs.System.IO public static void Write(FileStream aThis, byte[] aBuffer, int aOffset, int aCount, [FieldAccess(Name = InnerStreamFieldId)] ref Stream innerStream) { - Global.mFileSystemDebugger.SendInternal("FileStream.Write:"); + Global.mFileSystemDebugger.SendInternal($"FileStream.Write: aOffset {aOffset} aCount {aCount}"); innerStream.Write(aBuffer, aOffset, aCount); } @@ -83,6 +101,7 @@ namespace Cosmos.System_Plugs.System.IO public static void Flush(FileStream aThis, [FieldAccess(Name = InnerStreamFieldId)] ref Stream innerStream) { + Global.mFileSystemDebugger.SendInternal($"In FileStream.InitializeStream Flush()"); innerStream.Flush(); } @@ -143,7 +162,13 @@ namespace Cosmos.System_Plugs.System.IO case FileMode.Create: Global.mFileSystemDebugger.SendInternal("Create Mode aPath will be overwritten if existing"); // TODO it seems that GetFileStream effectively Creates the file if not exist - aStream = File.Create(aPath); + var xEntry = VFSManager.CreateFile(aPath); + if (xEntry == null) + { + return null; + } + //aStream = File.Create(aPath); + aStream = VFSManager.GetFileStream(aPath); break; case FileMode.CreateNew: diff --git a/source/Cosmos.System2_Plugs/System/IO/PathInternalImpl.cs b/source/Cosmos.System2_Plugs/System/IO/PathInternalImpl.cs index df417b683..2bae1507e 100644 --- a/source/Cosmos.System2_Plugs/System/IO/PathInternalImpl.cs +++ b/source/Cosmos.System2_Plugs/System/IO/PathInternalImpl.cs @@ -2,7 +2,7 @@ using Cosmos.IL2CPU.API.Attribs; using Cosmos.System; -namespace Cosmos.Kernel.Tests.Fat.System.IO +namespace Cosmos.System_Plugs.System.IO { [Plug(TargetName = "System.IO.PathInternal, System.IO.FileSystem")] public static class PathInternalImpl diff --git a/source/Cosmos.System2_Plugs/System/IO/StreamWriterImpl.cs b/source/Cosmos.System2_Plugs/System/IO/StreamWriterImpl.cs index 484cc3789..7f58f6f93 100644 --- a/source/Cosmos.System2_Plugs/System/IO/StreamWriterImpl.cs +++ b/source/Cosmos.System2_Plugs/System/IO/StreamWriterImpl.cs @@ -4,6 +4,7 @@ using System.IO; using Cosmos.System; using Cosmos.IL2CPU.API.Attribs; using Cosmos.System2.Encoding; +using System.Text; namespace Cosmos.System_Plugs.System.IO { @@ -11,38 +12,19 @@ namespace Cosmos.System_Plugs.System.IO [Plug(Target = typeof(StreamWriter))] public static class StreamWriterImpl { + private const string StreamFieldId = "System.IO.Stream System.IO.StreamWriter._stream"; + private const string CharPosFieldId = "System.Int32 System.IO.StreamWriter._charPos"; + private const string CharLenFieldId = "System.Int32 System.IO.StreamWriter._charLen"; + private const string CharBufferFieldId = "System.Char[] System.IO.StreamWriter._charBuffer"; + private const string ByteBufferFieldId = "System.Byte[] System.IO.StreamWriter._byteBuffer"; -#if false - public static void Ctor(StreamWriter aThis, string path) - { - throw new NotImplementedException("StreamWriter Ctor(String path)"); - } -#endif private static CosmosEncoding FileEncoding; //private static Stream InnerStream; -#if false - private static void Init(Stream stream, CosmosEncoding encoding) + private static void Init(String path, bool append, ref Stream stream, CosmosEncoding encoding, + ref char[] charBuffer, int bufferSize, ref byte[] byteBuffer, bool shouldLeaveOpen, + ref char[] CoreNewLine) { - //InnerStream = stream; - FileEncoding = encoding; - } -#endif - private static void Init(CosmosEncoding encoding, ref char[] charBuffer, int bufferSize, ref byte[] byteBuffer, bool shouldLeaveOpen) - { - FileEncoding = encoding; - charBuffer = new char[bufferSize]; - byteBuffer = new byte[bufferSize * FileEncoding.GetMaxByteCount(bufferSize)]; - } - - public static void Ctor(StreamWriter aThis, string path, - [FieldAccess(Name = "System.IO.Stream System.IO.StreamWriter._stream")] ref Stream _stream, - [FieldAccess(Name = "System.Int32 System.IO.StreamWriter._charPos")] ref int _charPos, - [FieldAccess(Name = "System.Char[] System.IO.StreamWriter._charBuffer")] ref char[] _charBuffer, - [FieldAccess(Name = "System.Byte[] System.IO.StreamWriter._byteBuffer")] ref byte[] _byteBuffer - ) - { - Global.mFileSystemDebugger.SendInternal($"StreamWriter.Ctor() with path {path}"); if (path == null) { throw new ArgumentNullException("path"); @@ -52,47 +34,76 @@ namespace Cosmos.System_Plugs.System.IO throw new ArgumentException("Empty path"); } - _stream = new FileStream(path, FileMode.Create); - Init(new CosmosUTF8Encoding(), ref _charBuffer, 128, ref _byteBuffer, false); + Global.mFileSystemDebugger.SendInternal($"StreamWriter.Init() with path {path} append {append} bufferSize {bufferSize}"); + stream = new FileStream(path, append ? FileMode.Append : FileMode.Create); + FileEncoding = encoding; + charBuffer = new char[bufferSize]; + byteBuffer = new byte[FileEncoding.GetMaxByteCount(bufferSize)]; + CoreNewLine = new char[] { '\n', '\r' }; + } + + /* + * This constructor is really plugged only to enforce our simplified version of UTF8 encoding using other + * enconding will be silently ignored (I liked to check for this and throw Exception but == operator + * does not work with Encoding neither Equals) + */ + public static void Ctor(StreamWriter aThis, string path, bool append, Encoding encoding, int bufferSize, + [FieldAccess(Name = StreamFieldId)] ref Stream _stream, + [FieldAccess(Name = CharPosFieldId)] ref int _charPos, + [FieldAccess(Name = CharLenFieldId)] ref int _charLen, + [FieldAccess(Name = CharBufferFieldId)] ref char[] _charBuffer, + [FieldAccess(Name = ByteBufferFieldId)] ref byte[] _byteBuffer, + [FieldAccess(Name = "System.Char[] System.IO.TextWriter.CoreNewLine")] ref char[] CoreNewLine + ) + { +#if false + //if (!Equals(encoding, Encoding.UTF8)) + if (!encoding.Equals(Encoding.UTF8)) + throw new NotImplementedException("Only UFT8 Encoding implemented"); +#endif + + Global.mFileSystemDebugger.SendInternal($"StreamWriter.Ctor() with path {path} append {append} Encoding and bufferSize {bufferSize}"); + Init(path, append, ref _stream, new CosmosUTF8Encoding(), ref _charBuffer, _charLen = bufferSize, ref _byteBuffer, false, ref CoreNewLine); } public static void Flush(StreamWriter aThis, bool flushStream, bool flushEncoder, - [FieldAccess(Name = "System.IO.Stream System.IO.StreamWriter._stream")] ref Stream _stream, - [FieldAccess(Name = "System.Int32 System.IO.StreamWriter._charPos")] ref int _charPos, - [FieldAccess(Name = "System.Char[] System.IO.StreamWriter._charBuffer")] ref char[] _charBuffer, - [FieldAccess(Name = "System.Byte[] System.IO.StreamWriter._byteBuffer")] ref byte[] _byteBuffer + [FieldAccess(Name = StreamFieldId)] ref Stream _stream, + [FieldAccess(Name = CharPosFieldId)] ref int _charPos, + [FieldAccess(Name = CharBufferFieldId)] ref char[] _charBuffer, + [FieldAccess(Name = ByteBufferFieldId)] ref byte[] _byteBuffer ) { - Global.mFileSystemDebugger.SendInternal($"_charPos is {_charPos}"); - // Debug code why is not working? - if (_charBuffer == null) - Global.mFileSystemDebugger.SendInternal("_charBuffer is NULL!"); - else if (_charBuffer.Length == 0) - Global.mFileSystemDebugger.SendInternal("_charBuffer is Empty!"); - /* First 4 chars should be '0', '1', '2' and '3' */ - else + if (_stream == null) { - Global.mFileSystemDebugger.SendInternal("Printing first 4 chars of _charBuffer: "); - Global.mFileSystemDebugger.SendInternal(_charBuffer[0]); - Global.mFileSystemDebugger.SendInternal(_charBuffer[1]); - Global.mFileSystemDebugger.SendInternal(_charBuffer[2]); - Global.mFileSystemDebugger.SendInternal(_charBuffer[3]); + throw new ObjectDisposedException(null, "Object already disposed"); + } + if (_charPos == 0 && !flushStream && !flushEncoder) + { + return; } + if (_charBuffer == null) + return; + if (_charBuffer.Length == 0) + return; -#if false - Global.mFileSystemDebugger.SendInternal($"StreamWriter.Flush() with _charPos {_charPos} and _charBuffer{new String(_charBuffer)}"); + int numBytes = FileEncoding.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0); + _charPos = 0; - FileEncoding.GetBytes(new string(_charBuffer)); - _stream.Write(_byteBuffer, 0, _byteBuffer.Length); - _stream.Flush(); -#endif - throw new NotImplementedException("Flush()"); + if (numBytes > 0) + { + Global.mFileSystemDebugger.SendInternal($"numBytes is {numBytes} doing Write..."); + _stream.Write(_byteBuffer, 0, numBytes); + Global.mFileSystemDebugger.SendInternal("Write done!"); + } + + Global.mFileSystemDebugger.SendInternal("Flush() ended"); + + //_stream.Flush(); } public static void Cctor() { } } - }