2

when I used FileSavePicker to select (or create) storage file, if I select existing file, it will prompt for replace. when I press yes, I expect the file to be replaced later but it doesn't.

// existing file is picked, I pressed yes when replace was requested.
file = await picker.PickSaveFileAsync();

so with above code, existing file is picked by FileSavePicker, I expected that file is now replaced but its not.

await file.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.AllowOnlyReaders)

This will only open file for write, it does not replace it, but I want it to be replaced. my issue is same as this question, but I'm not writing text file, I'm writing binary data.

How can I replace file with new one?

Ive tried following but it throws NRE, because GetParentAsync is returning null.

var file = await (await file.GetParentAsync()).CreateFileAsync(file.Name,
                        CreationCollisionOption.ReplaceExisting);

I don't want user to explicitly create a new file by deleting old one and creating a new one. What I want is that if user selects existing file and accepts replace request from FileSavePicker, the file should be replaced.


Ok, here is code that you can test on your machine.

var picker = new FileSavePicker
{
    SuggestedStartLocation = PickerLocationId.Desktop,
    DefaultFileExtension = ".bin",
    SuggestedFileName = "Binary file"
};
picker.FileTypeChoices.Add("Binary File", new List<string> {".bin"});

// in second run, choose existing file.
var file = await picker.PickSaveFileAsync();

using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.AllowOnlyReaders))
{
    using (var output = stream.GetOutputStreamAt(0))
    {
        using (var writer = new DataWriter(output))
        {
            writer.WriteBytes(Enumerable.Repeat<byte>(10, 10000).ToArray());
            await writer.StoreAsync();
            writer.DetachStream();
        }
        await output.FlushAsync();
    }
}

Run this two times, in first run, create file. in second run select existing file, and also change Repeat<byte>(10, 10000) to something smaller and different, like Repeat<byte>(80, 50) and check content of file.

M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118
  • FileSavePicker does not replace files, it merely provides you with a name. What you do *next* with that name matters. File operations in WinRT are transactional, you can't accidentally overwrite a file. Only when the file is correctly closed will it replace an existing file. So the most important code is missing from the snippets. – Hans Passant Sep 30 '17 at 11:43
  • right, how do I create a file with StorageFile that it gives to me? (then FileSavePicker is lying when its asking "replace with existing") @HansPassant – M.kazem Akhgary Sep 30 '17 at 11:44
  • I have no idea what you are saying. Post code that SO users can actually run themselves to verify your claims. – Hans Passant Sep 30 '17 at 11:45
  • @HansPassant ive provided you sample, please test this code if you can and let me know what you observe. thank you. – M.kazem Akhgary Sep 30 '17 at 11:53
  • WinRT doesn't have enough reasons to truncate the existing file. Consider StorageFolder.CreateFileAsync so you can specify CreationCollisionOption.ReplaceExisting – Hans Passant Sep 30 '17 at 12:04
  • yes, I've tried that. but I cant get parent folder that user has chose the file within. for example desktop folder. I was thinking that `GetParentAsync` should give me the folder, and I create a file with same name and `ReplaceExisting` option. but parent is null I don't know why @HansPassant – M.kazem Akhgary Sep 30 '17 at 12:06
  • @HansPassant I know we cant access everywhere for safety reasons, user must actively prompt access to something. but how am I going to replace file without telling user `"you must first remove it manually, then create a new one"`? – M.kazem Akhgary Sep 30 '17 at 12:09

1 Answers1

2

You can always SetLength of your opened stream to 0 first - which will clear all the old content and set stream's position to 0. Afterwards you can just write to the stream your new content:

var picker = new FileSavePicker
{
    SuggestedStartLocation = PickerLocationId.Desktop,
    DefaultFileExtension = ".bin",
    SuggestedFileName = "Binary file"
};
picker.FileTypeChoices.Add("Binary File", new List<string> { ".bin" });
var file = await picker.PickSaveFileAsync();

using (var stream = await file.OpenStreamForWriteAsync())
{
    stream.SetLength(0);
    var bytes = Encoding.ASCII.GetBytes("Content");
    await stream.WriteAsync(bytes, 0, bytes.Length);
    await stream.FlushAsync();
}
Romasz
  • 29,662
  • 13
  • 79
  • 154
  • Awesome, I wonder why this has not been issue to anyone yet? it took me a day to realize that my app works without bug but its the old content that is messing with data reader. – M.kazem Akhgary Sep 30 '17 at 19:46
  • I could also SetLength at end of write, `SetLength(bytesWritten)` so in case writer encounters problem, old data will not become lost. – M.kazem Akhgary Sep 30 '17 at 19:50
  • @M.kazemAkhgary Yes, *SetLength* will truncate or expand the stream if needed. – Romasz Sep 30 '17 at 19:53