I try to figure out how I could read and write a large text file in WinRT line by line.
FileIO.ReadLinesAsync respectively FileIO.WriteLinesAsync would not work because they work with a list of strings that is passed respectively returned. With large text files that may cause an OutOfMemoryException.
I could of course write line by line using FileIO.AppendTextAsync but that would be inefficient.
What I figured out was that I could use a StreamReader or StreamWriter like in the sample code below.
But is there really no native Windows Runtime way to achieve this?
I know by the way that Windows Store Apps are not supposed to read or write large text files. I need a solution just because I am writing a recipe book for programmers.
Sample: Reading using a StreamReader:
StorageFile file = ...
await Task.Run(async () =>
{
using (IRandomAccessStream winRtStream = await file.OpenAsync(FileAccessMode.Read))
{
Stream dotnetStream = winRtStream.AsStreamForRead();
using (StreamReader streamReader = new StreamReader(dotnetStream))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
...
}
}
}
});
Actually, your assumpltion about list of string is not completelly correct. FileIO.WriteLinesAsync accepts IEnumerable<string> as a parameter. So you can just do somethig like this:
IEnumerable<string> GenerateLines()
{
for (int ix = 0; ix < 10000000; ix++)
yield return "This is a line #" + ix;
}
//....
WriteLinesAsync(file, GenerateLines());
As for reading of a big file, you are right, we need some custom work like you did in your sample.
Here is my solution for writing large files using a StreamWriter. The used memory stays low even when writing very large files:
StorageFile file = ...
using (var randomAccessStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
using (var outputStream = randomAccessStream.GetOutputStreamAt(0))
{
using (StreamWriter streamWriter =
new StreamWriter(outputStream.AsStreamForWrite()))
{
for (...)
{
await streamWriter.WriteLineAsync(...);
}
}
}
}
Related
I'm looking to write custom ASP.NET Core TextOutputFormatter so I can throw any IEnumerable as an action result and it will produce comma separated list as an output. I don't want to save the results to a file or anything like it, just serve it as a response directly. CsvHelper seems to be very intuitive to use but I don't know how to write to response body. Any help will be much appreciated!
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
NewLine = Environment.NewLine,
Encoding = selectedEncoding,
};
if (context.Object is IEnumerable<object> list)
{
using var writer = new StreamWriter(response.Body);
using var csv = new CsvWriter(writer, config);
await csv.WriteRecordsAsync(list); // does not work
}
}
Edit:
When I replace
using var writer = new StreamWriter(response.Body);
with
StringBuilder sb = new();
using var writer = new StringWriter(sb);
I can see my sb is being built just as expected so it's not a problem with my IEnumerable<object> or with CsvWriter. I just don't know how to write results it produce to HttpContext.Response.Body
I'm not sure if this will be the best answer, but I was able to make this work using StringBuilder and replacing StreamWriter with StringWriter
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
NewLine = Environment.NewLine,
Encoding = selectedEncoding,
};
if (context.Object is IEnumerable<object> list)
{
StringBuilder sb = new();
using var writer = new StringWriter(sb);
using var csv = new CsvWriter(writer, config);
await csv.WriteRecordsAsync(list);
await response.WriteAsync(sb.ToString(), selectedEncoding);
}
}
WriteAsync requires adding Microsoft.AspNetCore.Http namespace
I'm choosing a file this way:
Future<String> getFile() {
final completer = new Completer<String>();
final InputElement input = document.createElement('input');
input
..type = 'file'
..accept = 'image/*';
input.onChange.listen((e) async {
final List<File> files = input.files;
final reader = new FileReader();
reader.readAsDataUrl(files[0]);
reader.onError.listen((error) => completer.completeError(error));
await reader.onLoad.first;
completer.complete(reader.result as String);
});
input.click();
return completer.future;
}
I would typically display the file using the widget returned by Image.file(...) but this accepts a dart:io File, not a dart:html one. What's the best way to display dart:html in Flutter web?
As a bonus question, are there any known browser limitations? I'm worried this solution will only work on Chrome.
This is how i read my textfile in android.
#if UNITY_ANDROID
string full_path = string.Format("{0}/{1}",Application.streamingAssetsPath, path_with_extention_under_streaming_assets_folder);
// Android only use WWW to read file
WWW reader = new WWW(full_path);
while (!reader.isDone){}
json = reader.text;
// PK Debug 2017.12.11
Debug.Log(json);
#endif
and this is how i read my textfile from pc.
#if UNITY_STANDALONE
string full_path = string.Format("{0}/{1}", Application.streamingAssetsPath, path_with_extention_under_streaming_assets_folder);
StreamReader reader = new StreamReader(full_path);
json = reader.ReadToEnd().Trim();
reader.Close();
#endif
Now my problem is that i don't know how to write the file on mobile cause i do it like this on the standalone
#if UNITY_STANDALONE
StreamWriter writer = new StreamWriter(path, false);
writer.WriteLine(json);
writer.Close();
#endif
Help anyone
UPDATED QUESTION
This is the json file that it is in my streamingasset folder that i need to get
Now my problem is that i don't know how to write the file on mobile
cause I do it like this on the standalone
You can't save to this location. Application.streamingAssetsPath is read-only. It doesn't matter if it works on the Editor or not. It is read only and cannot be used to load data.
Reading data from the StreamingAssets:
IEnumerator loadStreamingAsset(string fileName)
{
string filePath = System.IO.Path.Combine(Application.streamingAssetsPath, fileName);
string result;
if (filePath.Contains("://") || filePath.Contains(":///"))
{
WWW www = new WWW(filePath);
yield return www;
result = www.text;
}
else
{
result = System.IO.File.ReadAllText(filePath);
}
Debug.Log("Loaded file: " + result);
}
Usage:
Let's load your "datacenter.json" file from your screenshot:
void Start()
{
StartCoroutine(loadStreamingAsset("datacenter.json"));
}
Saving Data:
The path to save a data that works on all platform is Application.persistentDataPath. Make sure to create a folder inside that path before saving data to it. The StreamReader in your question can be used to read or write to this path.
Saving to the Application.persistentDataPath path:
Use File.WriteAllBytes
Reading from the Application.persistentDataPath path
Use File.ReadAllBytes.
See this post for a complete example of how to save data in Unity.
This is the way I do it without the WWW class (works for Android an iOS), hope its useful
public void WriteDataToFile(string jsonString)
{
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
if (!File.Exists(filePath))
{
File.Create(filePath).Close();
File.WriteAllText(filePath, jsonString);
}
else
{
File.WriteAllText(filePath, jsonString);
}
}
Where do I find the location of the folders and text files I created in windows phone 8. Can we see it in the explorer like we search for the app data in Windows 8? I'm not using IsolatedStorage, instead Windows.Storage. I want to check if the folders and files are created as I want.
This is how I write the file
IStorageFolder dataFolder = await m_localfolder.CreateFolderAsync(App.ALL_PAGE_FOLDER, CreationCollisionOption.OpenIfExists);
StorageFile PageConfig = null;
try
{
PageConfig = await dataFolder.CreateFileAsync("PageConfig.txt", CreationCollisionOption.OpenIfExists);
}
catch (FileNotFoundException)
{
return false;
}
EDIT
try
{
if (PageConfig != null)
{
using (var stream = await PageConfig.OpenStreamForWriteAsync())
{
DataWriter writer = new DataWriter(stream.AsOutputStream());
writer.WriteString(jsonString);
}
}
}
catch (Exception e)
{
string txt = e.Message;
return false;
}
And this is how I read the file from the folder
try
{
var dataFolder = await m_localfolder.GetFolderAsync(App.ALL_PAGE_FOLDER);
var retpng = await dataFolder.OpenStreamForReadAsync("PageConfig.txt");
if (retpng != null)
{
try
{
using (StreamReader streamReader = new StreamReader(retpng))
{
jsonString = streamReader.ReadToEnd();
}
return jsonString;
}
catch (Exception)
{
}
}
}
catch (FileNotFoundException)
{
}
There are also other folders created. I dont receive any exceptions while writing but when I read the string is empty.
Windows.Storage.ApplicationData.LocalFolder(MSDN link here) is another name for Isolated Storage that is in Windows.Storage namespace. The only other location you can access is your app's install directory (and only read-only).
You can use Windows Phone Power Tools to browse what files are in your app's Isolated Storage, or the command line tool that comes with the SDK.
With the help of Windows Phone Power tools, I figured out that there was no text being written in file.
So I converted string to byte and then wrote it to the file and it works! Don't know why the other one does not work though..
using (var stream = await PageConfig.OpenStreamForWriteAsync())
{
byte[] fileBytes = System.Text.Encoding.UTF8.GetBytes(jsonString);
stream.Write(fileBytes, 0, fileBytes.Length);
}
The command line tool that comes with Windows Phone SDK 8.0 is Isolated Storage Explorer (ISETool.exe) which reside in "Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0\Tools\IsolatedStorageExplorerTool" folder for default installation
ISETool.exe is used to view and manage the contents of the local folder
i know there are two classes for IRandomAccessStream interface in WinRT,
FileRandomAccessStream and InMemoryRandomAccessStream.
now i'm porting one of my apps to wp8 and some of my methods need IRandomAccessStream as return type,but i cannot find InMemoryRandomAccessStream.
how can i create a memorystream and get a IRandomAccessStream from it without InMemoryRandomAccessStream in Windows Phone 8?
ran into the same thing. I ended up using a temp file stream with random access.
This is kludged together from
windows.storage.applicationdata.temporaryfolder which is not actually implemented, so I created a file in the localfolder instead (make sure to delete it after, since you want something temporary?).
and
windows.storage.streams.inmemoryrandomaccessstream
here it is the slightly adopted sample code:
private async void TransferData()//object sender, RoutedEventArgs e)
{
Windows.Storage.StorageFolder temporaryFolder = ApplicationData.Current.LocalFolder;
// Write data to a file
StorageFile sampleFile = await temporaryFolder.CreateFileAsync("tempStream.txt", Windows.Storage.CreationCollisionOption.ReplaceExisting);
IRandomAccessStream acccessStream = await sampleFile.OpenAsync(FileAccessMode.ReadWrite);
// Initialize the in-memory stream where data will be stored.
using (var stream = acccessStream)
{
// Create the data writer object backed by the in-memory stream.
using (var dataWriter = new Windows.Storage.Streams.DataWriter(stream))
{
dataWriter.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
dataWriter.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
// Parse the input stream and write each element separately.
string[] inputElements = "this;really;works".Split(';');
foreach (string inputElement in inputElements)
{
uint inputElementSize = dataWriter.MeasureString(inputElement);
dataWriter.WriteUInt32(inputElementSize);
dataWriter.WriteString(inputElement);
System.Diagnostics.Debug.WriteLine("Wrote: " + inputElement);
}
// Send the contents of the writer to the backing stream.
await dataWriter.StoreAsync();
// For the in-memory stream implementation we are using, the flushAsync call
// is superfluous,but other types of streams may require it.
await dataWriter.FlushAsync();
// In order to prolong the lifetime of the stream, detach it from the
// DataWriter so that it will not be closed when Dispose() is called on
// dataWriter. Were we to fail to detach the stream, the call to
// dataWriter.Dispose() would close the underlying stream, preventing
// its subsequent use by the DataReader below.
dataWriter.DetachStream();
}
// Create the input stream at position 0 so that the stream can be read
// from the beginning.
using (var inputStream = stream.GetInputStreamAt(0))
{
using (var dataReader = new Windows.Storage.Streams.DataReader(inputStream))
{
// The encoding and byte order need to match the settings of the writer
// we previously used.
dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
dataReader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
// Once we have written the contents successfully we load the stream.
await dataReader.LoadAsync((uint)stream.Size);
var receivedStrings = "";
// Keep reading until we consume the complete stream.
while (dataReader.UnconsumedBufferLength > 0)
{
// Note that the call to readString requires a length of "code units"
// to read. This is the reason each string is preceded by its length
// when "on the wire".
uint bytesToRead = dataReader.ReadUInt32();
receivedStrings += dataReader.ReadString(bytesToRead) + "\n";
}
// Populate the ElementsRead text block with the items we read
// from the stream.
System.Diagnostics.Debug.WriteLine("Read: " + receivedStrings);
}
}
}
}
make sure to use windows.storage namespaces.
the key is this line:
IRandomAccessStream acccessStream = await sampleFile.OpenAsync(FileAccessMode.ReadWrite);
I am not sure what the speed implications are.