7

This is NOT a dupe of this question. This question is NOT about Windows. It's a general question across OSes.

Is there an efficient way to get the correct case of a filename in node.js other than getting the directory and finding the matching name?

Example: Assume I have a folder with 3 files

+-someFolder
  +-fooBar.txt
  +-Moo.txt
  +-ReadMe.txt

I want a function that passed somefolder/readme.txt returns someFolder/ReadMe.txt.

AFAICT the only way to do that is to call fs.readDir or fs.readDirSync and see if there is a matching file, something like

const fs = require('fs');
const path = require('path');

function getActualFilename(filename) {
  if(!fs.existsSync(filename)) {
    throw new Error(`${filename} does not exist`);
  }
  return getActualFilenameImpl(filename);
}

function getActualFilenameImpl(filename) {
  const lcFilename = path.basename(filename).toLowerCase();
  // handles passing in `c:\\`
  if (!lcFilename) {
    return filename.toUpperCase();
  }

  const dirname = path.dirname(filename);
  let filenames;
  try {
    filenames = fs.readdirSync(dirname);
  } catch (e) {
    // we already verified the path exists above so if this
    // happens it means the OS won't let use get a listing (UNC root on windows)
    // so it's the best we can do
    return filename;
  }
  const matches = filenames.filter(name => lcFilename === name.toLowerCase());
  if (!matches.length) {
    throw new Error(`${filename} does not exist`);
  }

  const realname = matches[0];
  if (dirname !== '.') {
    if (dirname.endsWith('/') || dirname.endsWith('\\')) {
      return path.join(dirname, realname);
    } else {
      return path.join(getActualFilenameImpl(dirname), realname);
    }
  } else {
    return realname;
  }
}

The code above is pretty hacky. Trying in on different things has made it clear there's lots of edge cases. On Windows in particular UNC paths fail since you can't call fs.readdirSync once you get to the network path root. I have no idea what functions to call to figure out where that path separates and then how to get the correct case path for that which is probably an entirely separate set of Windows API calls (like calling whatever functions net use uses to show shares) etc...

I did notice path.dirname stops removing the trailing slash when it gets to a UNC path so using that to try to figure out when stop trying.

Notes:

  • I get that for example on Linux (and optionally on Mac) the file system may be case sensitive and I'd have to check for that but I'm mostly concerned with Windows and standard macOS and will deal with case-sensitive issues later.

  • I also get that JavaScript's toLowerCase might not match the OSes concept of case insensitivity so if there is a solution that takes that into account that would also be great!

  • I get that I could cache results or directory listing for a speed up but was wondering if there is some other function to use that doesn't read the entire directory listing.

I'm actually trying to solve several problems and am open to other suggestions

Problem 1: What filename to store in an app specific database. It seems best to store the actual filename. See #3

Problem 2: Figuring out if 2 filenames reference the same file/folder. So if the user specifies SomeFolder/foobar.txt and somefolder/FOOBAR.txt I don't want that to appear as 2 separate files if they are actually the same file. I need my app to know they reference the same file. I think I can call fs.stat for this and check if the ino field matches?

Problem 3: Related to problem 1, reloading metadata related to the file. If the user specifies SomeFolder/foobar.txt at some point and my app generates metadata related to the file, then at some other point in time they specify somefolder/FOOBAR.txt I need to find the matching metadata. My current thinking is by looking up the actual filename and using that to match with this problem would be solved. Although I suppose if they rename the file from FooBar.txt to foobar.txt it would lose the metadata. Not sure I care about that situation though since if they rename from FooBar.txt to SomethingElse.txt I definitely do not care if I lose the metadata.

That said, maybe I should store the ino as the key in my DB? Not sure I'm comfortable with that idea yet but it's a possibility and would love to know if others do that. Some checking reveals that at least on macOS the ino stays the same across moves and rename on the same drive which would be a good thing for my use case. On the other hand I'd assume ino is only valid per file system so if I have 2 different drives mounted I could get clashing inos. I could use dev and ino as a key as in

const stat = fs.statSync(filename);
const key = `${stat.dev}:${stat.ino}`;

Though I have no idea if stat.dev is always the same with removable storage. I assume it's not. So it seems like filename as key is probably better?

KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
gman
  • 100,619
  • 31
  • 269
  • 393
  • Does [this](https://stackoverflow.com/questions/33086985/how-to-obtain-case-exact-path-of-a-file-in-node-js-on-windows) help? – AuxTaco Dec 28 '17 at 08:04
  • Unfortunately no as there are bugs. I filed them. Although given it uses `glob` it's likely doing the same thing as above, getting a folder listing. – gman Dec 28 '17 at 09:31
  • Possible duplicate of [How to obtain case-exact path of a file in Node.js on Windows?](https://stackoverflow.com/questions/33086985/how-to-obtain-case-exact-path-of-a-file-in-node-js-on-windows) – Joey Dec 28 '17 at 18:52
  • Not a dupe. This is not "Windows" question at all – gman Dec 29 '17 at 03:52

1 Answers1

0

As long as the filesystem doesn't keep a connection between files with the same names in different cases (and I don't know any such filesystem) there can't be a solution other than scanning the directory because there is simply no API provided for this at all at any level.

So you have to either scan manually as you already suggested or by using libraries like glob to find files while ignoring case.

But you say you also have the filenames in a database. So if you can make sure that the filenames in the DB are exactly matching the filenames in the filesystem then you should be able to find the files in different cases by doing case-insensitive DB queries. If it is an SQL database then it should already provide this functionality. If it is a more primitive data store you may add another filename property which is always lower-case so you can match against this to find the real file.

kayahr
  • 20,913
  • 29
  • 99
  • 147