-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Relative Path and x-plat I/O papercuts #64890
Comments
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsOn recommendation from @jeffhandley, opening an issue for discussion :) It started with this tweet:
Qualifying what I meant, I was referring to a type of application that I've often worked on ... some CLI tool. To be clear, none of these issues have been showstoppers, it's just that every time I build a tool, I inevitably forget all of the challenges that I've already solved before, and end up either rewriting the solutions entirely or copy/pasting from project to project. There's a chance that some of these have been solved by newer APIs that I'm just not familiar with. I'm going to try to remember and list some of these things below:
Honestly ... I know that I've had other x-plat papercuts, but like I said above, it's been a while and I'm having trouble remembering them. All I know is that I always think to myself, "I wish I didn't have to account for this" when my intent always feels relatively simple ("copy these files from here to there", for example). Happy to chat more, or answer any questions anyone might have for me around these scenarios. Thanks! Thanks @timheuer @jeffhandley and @khalidabuhakmeh for engaging on twitter :D
|
We are soon (7.0) going to introduce
Would that be still needed with
static async Task CopyToAsync(string source, string destination)
{
using FileStream src = new (source, FileMode.Open, FileAccess.Read, FileShare.None, 0, true);
using FileStream dst = new (destination, new FileStreamOptions()
{
Access = FileAccess.Write,
Share = FileShare.None,
Mode = FileMode.Create,
BufferSize = 0,
Options = FileOptions.Asynchronous,
PreallocationSize = src.Length
});
await src.CopyToAsync(dst);
} but I agree that we could introduce
The exist check is redundant, |
@joelmartinez thanks for taking the time of explaining your pain-points when using our APIs, I would like to add to @adamsitnik's comment my own thoughts about it.
There are many ways you can deal with this. The simplest one is probably the one you mentioned using
We can address that in the same API proposed above by adding a method, say CreateFile(bool createDirectory), to create the directory, if not exists, before creating the file.
I doubt we could implement some built-in support for expanding the tilde character in .NET, it would represent a huge breaking change, but is doable and it could be something that you may need to opt-in via an env. var. or something similar.
Would it be better if Path.Combite throws (or indicated that something was wrong) in such case? if so you could argue for an API (say
Similar-ish issues: I would like to think that the problem is when you use Windows paths ( |
I was going to make a API suggestion for this. Because we already HAVE a What am I missing?! |
On recommendation from @jeffhandley, opening an issue for discussion :) It started with this tweet:
https://twitter.com/joelmartinez/status/1490120089957552132
Qualifying what I meant, I was referring to a type of application that I've often worked on ... some CLI tool. To be clear, none of these issues have been showstoppers, it's just that every time I build a tool, I inevitably forget all of the challenges that I've already solved before, and end up either rewriting the solutions entirely or copy/pasting from project to project. There's a chance that some of these have been solved by newer APIs that I'm just not familiar with.
I'm going to try to remember and list some of these things below:
I'll often have a "target directory", which can sometimes be different than the current working directory. It would be useful to be able to set some sort of context so I didn't have to manage my own context and send around that "root" path in my code.
It's of course trivial to use
Path.Combine(target, filename)
, but in some instances, there's nested files, and if I'm enumerating all the files in some directory (ie.Directory.EnumerateFiles (from, "*.*", SearchOption.AllDirectories)
), then I have to do string manipulation to cut thefrom
directory from that path.There's no (easy) way to copy all the contents of a directory to another directory ... look at the example given here in the docs to copy all files in a directory to another directory. Sure it's not rocket science, but that sample is also only copying the contents of a single dir, and not enumerating all the subdirectories. To do that involves either introducing recursive logic, or
SearchOption.AllDirectories
, but then there's the pathing issues described in the previous bullet point.There's no built-in way (that I'm aware of) to copy async. Lots of digital ink spilled across blogs and forums about how to do this in different ways through the years. At a minimum, a
File.CopyAsync
would be awesome to have. There'sStream.CopyToAsync
that I thought was promising, but my first attempt to integrate this into myasync
-heavy program led me to issues where the stream would already be closed by the time the code happened to be invoked (I mixedyield return
withasync
, I think was my problem). Bit of a pit-of-failure rather than pit-of-success here.Once you start copying these relative and nested files, then you inevitably run into the fact that the file won't be created if the directory doesn't already exist. I wish there was at least an overload that created the directory, but I always end up writing some extension method like:
~
can be confusing for users, because on shells like powershell and bash, passing in a path on the CLI will automatically expand that; sweet, that's great. But users will often then think to add something like~/dev/blah
in a config file thinking that will do the same thing, which it obviously won't. So I've written code to look for~
at the beginning of a path, and replace it with something likeEnvironment.GetFolderPath
. In other cases, I've wanted~
to be a shorthand for "the application root", or "the project root" ... so this one feels like a common-enough thing that there could be built-in support somewhere.Talking about settings-provided paths ... users can be pretty inconsistent, sometimes they'll put relative paths that start with
Path.DirectorySeparatorChar
in the config file, which is problematic because this codePath.Combine ("some", "relative", "/path")
returns/path
😱 So I often have to carefully scrub that user input so it plays nice withPath.Combine
/
and\
... I usually end up writing some extension method that just.Replace
the opposite separator char withPath.DirectorySeparatorChar
, but I kind of wish I didn't have to worry about that and could just mix them all over the place since I'm usually combining paths that could be from different sources, ie. provided by a user on windows, but executed on a build agent on linux.Honestly ... I know that I've had other x-plat papercuts, but like I said above, it's been a while and I'm having trouble remembering them. All I know is that I always think to myself, "I wish I didn't have to account for this" when my intent always feels relatively simple ("copy these files from here to there", for example).
Happy to chat more, or answer any questions anyone might have for me around these scenarios. Thanks!
Thanks @timheuer @jeffhandley and @khalidabuhakmeh for engaging on twitter :D
The text was updated successfully, but these errors were encountered: