So this simple File.AppendText()
method fails
using (var writer = File.AppendText(dst)){ ... }
when I call it right after I manually delete the file with the name dst
.
Manually here means using my own hand in a file explore.
Also, I am certain that there is no open StreamWriter
in the application scope as I am even using StreamWriter.WriteLine()
which is not asynchronous.
I could not find a relevant question in StackOverflow, unfortunately.
This situation happened with C# using Xamarin form on Android.
I believe this behaviour could be general enough so that can happen in a random platform using C#. Hence, there should be a proper way to deal with the situation by checking if File.AppendText()
method can properly open a StreamWriter
.
Could anyone give me some advice?
Thanks a lot.
p.s. The full lines of the thrown exception follow.
System.IO.IOException:
Could not create file "/storage/emulated/0/Documents/something".
File already exists.
at System.IO.FileStream..ctor (
System.String path,
System.IO.FileMode mode,
System.IO.FileAccess access,
System.IO.FileShare share,
System.Int32 bufferSize,
System.Boolean anonymous,
System.IO.FileOptions options) [0x001aa] in
/Users/builder/jenkins/workspace/archive-mono/2020-02/android/release
/mcs/class/corlib/System.IO/FileStream.cs:239
at System.IO.FileStream..ctor (
System.String path,
System.IO.FileMode mode,
System.IO.FileAccess access,
System.IO.FileShare share,
System.Int32 bufferSize,
System.IO.FileOptions options) [0x00000] in
/Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/
mcs/class/corlib/System.IO/FileStream.cs:106
at (wrapper remoting-invoke-with-check)
System.IO.FileStream..ctor(
string,System.IO.FileMode,
System.IO.FileAccess,
System.IO.FileShare,
int,
System.IO.FileOptions)
at System.IO.StreamWriter..ctor (
System.String path,
System.Boolean append,
System.Text.Encoding encoding,
System.Int32 bufferSize) [0x00055] in
/Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/
external/corefx/src/Common/src/CoreLib/System/IO/StreamWriter.cs:151
at System.IO.StreamWriter..ctor (
System.String path, System.Boolean append) [0x00000] in
/Users/builder/jenkins/workspace/archive-mono/2020-02/android/release
/external/corefx/src/Common/src/CoreLib/System/IO/StreamWriter.cs:131
at (wrapper remoting-invoke-with-check)
System.IO.StreamWriter..ctor(string,bool)
at System.IO.File.AppendText (System.String path) [0x0000e] in
/Users/builder/jenkins/workspace/archive-mono/2020-02/android/release
/external/corefx/src/System.IO.FileSystem/src/System/IO/File.cs:48
at MyNameSpace.Droid.MyService.MyMethod () [0x001fd]
in C:\workspace\my_project\my_code.cs:257
CodePudding user response:
I had do a simple to test the method.
string myfile = @"/mnt/sdcard/Android/data/com.companyname.apptest/cache/NewTextFile2.txt";
// Appending the given texts
using (StreamWriter sw = File.AppendText(myfile))
{
sw.WriteLine("some thing");
sw.WriteLine("some thing else");
}
If the file's path isn't exited, it will create a file and then append. If the file's path is exited, it will append directly.
CodePudding user response:
So, I believe this is an Android issue. The Android version and the hardware are not important aspects of this discussion. A System.IO.IOException
can happen in any situation in any environment unexpectedly. It is an engineer's responsibility to figure out what can be done in such a situation. I came up with a naive solution, and I post it as an answer.
The problem is the following. An application attempts to call the File.AppendText()
, File.CreateText()
, or File.Copy()
method right after the file with the identical name previously existed and a user just deleted. Say, one of the aforementioned methods is called in one second. Then an exception
System.IO.IOException:
Could not create file "/storage/emulated/0/Documents/something".
File already exists.
is thrown at the line where the app is calling File.AppendText()
, File.CreateText()
, or File.Copy()
.
The destination path was generated using
string public_dst = Path.Combine(
Android.OS.Environment.GetExternalStoragePublicDirectory(
Android.OS.Environment.DirectoryDocuments).AbsolutePath,
"my_file_name");
The source file name was generated using
string private_src = Path.Combine(
System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal),
$"my_source_file");
A naive way to alleviate this problem is to try again after a delay. A server-client networking system would try similar, no? The code reads
int CUR_TRIALS = 0;
int MAX_TRIALS = 10;
int INTERVAL = 1000; /* in milliseconds */
string str_ex = ""; /* @Console */
while (true)
{
try
{
File.Copy(private_dst, public_dst);
break;
}
catch (Exception ex)
{
str_ex = ex.ToString();
string known =
$"System.IO.IOException: Could not create file "
$"\"{public_dst}\". File already exists.";
bool is_known = str_ex.Contains(known);
if (is_known && (str_ex != ""))
{
#if DEBUG
Log.Info($"{TAG}.PrivateToPublic()",
$"System.IO.IOException: ",
$"{CUR_TRIALS.ToString("D4")}/"
$"{MAX_TRIALS.ToString("D4")}");
#endif
Task.Delay(INTERVAL).Wait();
}
}
CUR_TRIALS = 1;
if (CUR_TRIALS >= MAX_TRIALS)
{
#if DEBUG
Log.Info($"{TAG}.PrivateToPublic()", str_ex);
#endif
throw new ApplicationException($"0x00000089");
}
}
I manually specified how many times it can try and what time delay it suspends until it can try again. I easily can reconstruct this problem by deleting the file at the destination path and call one of the aforementioned methods right after it. The suggested routine definitely encounters the catch
block. It suspends for a second and then it retries. It usually succeed in the very next attempt, so this approach can be practical.