1

I have a problem.

I save json from web, in json files on my computer, and the name of this json file, is the web adress of the json. For that, I get the web json into string, and then I append it in a file, with File.AppendAllText(path, content) After some time, i also need to read json from this file with File.ReadAllText(path)

My problem is sometimes, two json have a very similar name, for example :

*com/doc/BACr and *com/doc/BAcr

Problem, the path given in the methods of the class File are note case sensitive, and I end writing twice in the same file, corrupting it.

I've found on the internet solutions for the same problem for the method File.Exists(path), but nothing to replace the methods I use to read or write.

Any of you know a setting, or even another method that would be case sensitive on the path ?

Thank you

Edit : I'm obviously working on windows :(

Edit bis : I can't change the filename, because in some others json, there is reference to web path, and when I play again my local jsons, if the filename is modified, it won't be found. It's the reason I need both write and read with case sensitive path.

  • 5
    The thing is windows' paths aren't case sensitive. – dcg Jun 27 '17 at 15:23
  • 1
    Is it practical to encode the filenames in some way? Is it a requirement to be able to use the URL later to retrieve the files? – 15ee8f99-57ff-4f92-890c-b56153 Jun 27 '17 at 15:25
  • 1
    Try to add timestamp to the filename if its exist. – Joseph Jun 27 '17 at 15:26
  • @ThiaultJouan what are you trying to do and *why* are you trying to use the same name? – Panagiotis Kanavos Jun 27 '17 at 15:26
  • 1
    Windows *can* actually handle case-sensitive file systems (and NTFS preserves case), but it's not the default for any code running on the Win32 subsystem, and accessing this from C# is anything but trivial. Working around the problem is more practical. For instance, you could convert the file's ASCII characters to a hexstring and use that for the name, preserving case when converting it back. – Jeroen Mostert Jun 27 '17 at 15:27
  • 3
    you can convert the path to base64. so is unique also for same string with case difference – dovid Jun 27 '17 at 15:30
  • 1
    "if the filename is modified, it won't be found" -- then modify/wrap the code that loads files, so it passes through your translation layer. It should not be the case that your web paths dictate how your local paths look. – Jeroen Mostert Jun 27 '17 at 15:34
  • @PanagiotisKanavos I don't want the files to have almost the same name, actually it is more a "bug", it depends of the web path where I get my json, and in one !@#$%^ case, two json have almost the same path :( – Thibault Jouan Jun 27 '17 at 15:36
  • If you're desperate enough, P/Invoking to `CreateFile` to pass `FILE_FLAG_POSIX_SEMANTICS` should allow you to create/open files that differ only by case, but this can't be recommended as a general solution, both because of the difficulty of P/Invoking itself and the confusion these files will create for unaware tools (which would be nearly all of them). I'd probably still prefer wrapping all base file calls to do file name mangling over that. – Jeroen Mostert Jun 27 '17 at 15:38
  • 1
    @ThibaultJouan in other words, you want to store/cache data using the *URL* as an identifier. That's not necessarily the best idea - git and dropbox don't use *filenames* to identify their blobs. Dropbox uses an SQLite database to store dates, hashes, timestamps etc to find modified files or files with different hashes without scanning everything – Panagiotis Kanavos Jun 27 '17 at 15:51

3 Answers3

1

You need something that makes your files unique and in the same time something that allows you to rebuild this uniqueness when you want to read back these files.

Suppose that your couple of files is named "BAcr" and "BACr". You can get the HashCode of these two strings and you will get two different values

string file1 = "BAcr";
int file1Hash = file1.GetHashCode(); //742971449
string file2 = "BACr";
int file1Hash = file2.GetHashCode(); //-681949991

Now if you concatenate this hashcode to your filename you will get two different files and you will be able to recalculate the same hashcode for the same input filename

string newFile1 = $"{file1}.{file1Hash}";
string newFile2 = $"{file2}.{file2Hash}";

you will save your data in these two recalculated filenames and when you need to reload them you use the same trick to get the filename used to save the data starting from the same input "BAcr" or "BACr".

But string.GetHashCode doesn't guarantee uniqueness in its results so, still using the same general idea Jeroen Mostert uses this method to get an unique code from the input value

string unique1 = string.Join("", file1.Select(c => char.IsUpper(c) ? "1" : "0"))
string newFileName1 = $"{file1}.{unique1}";
Steve
  • 213,761
  • 22
  • 232
  • 286
  • Why would you use the hash code if you *know* it's not unique? I haven't investigated `String.GetHashCode` to see if any collisions are possible for strings differing in case alone, but I wouldn't really want to rely on that in the first place. It only takes one collision. Unless the file names are unusually long, I'd prefer their Base64-encoded versions. As a bonus, that could encode everything the file system can't handle, including forbidden characters, and it roundtrips, which the hash does not. – Jeroen Mostert Jun 27 '17 at 15:51
  • Yes, probably you are right. Rereading the answers in the link posted above from none less than E.Lippert now I have your same doubt on this solution. – Steve Jun 27 '17 at 15:56
  • If you insisted on keeping readable file names, and you were only worried about case, then `String.Join("", "abcABC".Select(c => char.IsUpper(c) ? "1" : "0"))` would record the case of each character explicitly in a way that roundtrips. `abcABC.000111` would then be the modified name. – Jeroen Mostert Jun 27 '17 at 15:56
  • @JeroenMostert that's better, please post your answer as soon as possible I will delete mine. – Steve Jun 27 '17 at 15:57
  • 2
    @Steve: just feel free to steal my idea, it was based on yours in the first place. :-) I'm not sure it would help the OP to begin with, given their additional comments. – Jeroen Mostert Jun 27 '17 at 15:58
0

Windows paths are indeed case insensitive, so you cannot have these filenames.

One solution would be to change the filename if it already exists... For example;

if (File.Exists(fileNameToSaveTo)){
    // Note: Your example file names did not have an extension,
    //       but if they do, you will need to first extract that then add it back on
    fileNameToSaveTo = fileNameToSaveTo + "1";
}

If using this solution, you would have to also update whatever identifier your program uses to read back from the file at a later date... as you have not posted any code I cannot guess as to what form this takes, but hopefully you get the idea?

Edit: Upon re-reading your question... it appears you use AppendAllText... In this case, this should not 'corrupt' the file as you suggest, but should simply add the contents to the end of the file? Is this not what you observe?

Edit2: After reading comments Iomed - you could use Convert.ToBase64String on the filename in your write before writing the file, the use Convert.FromBase64String on the filename in your read function before reading the file. This will allow the filename to be different based on the capitalization.

Another alternative would be to parse the JSON (the new one AND the existing file) and add the objects to an array, then write that to the file instead, avoiding your 'corruption' issue?

Milney
  • 6,253
  • 2
  • 19
  • 33
  • yes, but with the Json, as I add my json at the end of the first one, it become corrupted, because the file contains 2 json objects, instead of one :( – Thibault Jouan Jun 27 '17 at 15:31
  • You could parse the JSON (the new one AND the existing file) and add the objects to an array, then write that to the file? – Milney Jun 27 '17 at 15:35
  • @ThibaultJouan See my Edit2 for two solutions which would work – Milney Jun 27 '17 at 15:38
0

given paths: PathA, patha

for two files, use base64 trick:

string PathToFile(string url) =>  System.Convert.ToBase64String(Encoding.UTF8.GetBytes(url));

so:

Console.WriteLine(PathToFile("pathA")); //cGF0aEE=
Console.WriteLine(PathToFile("patha")); //cGF0aGE=
dovid
  • 6,354
  • 3
  • 33
  • 73