Skip to content
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

BASE: Ensure folder path when file path set in command line #5605

Merged
merged 4 commits into from Jan 20, 2024

Conversation

antoniou79
Copy link
Contributor

The "soundfont" option is excluded from this, since that is expected to be a file path.

The PR is submitted for consideration as an extension to the command line to allow more leniency with the path arguments, ie. when a file path is set for a folder path argument in command line, it is converted to the appropriate folder path for the file, so that scummvm won't throw an error and exit.

The motivation for the PR was the discussion in this forum thread: https://forums.scummvm.org/viewtopic.php?t=17015

soundfont option is excluded for this, since that is expected to be a file path
Comment on lines 919 to 924
} else if (!path.isDirectory()
&& path.getParent().isDirectory()
&& path.getParent().isReadable()) {
// The path.getParent().isDirectory() check is redundant but included for clarifying
// the purpose of this case, which is to get the folder path part from a file path
settings["iconspath"] = path.getParent().getPath().toString(Common::Path::kNativeSeparator);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this code is repeated, could we wrap it into a reusable function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't come up with something pretty for a reusable function.

I did a few iterations on implementing one, but the different cases per path option (isReadable() check or isWriteable check), the custom usage() messages (which should stay outside the function) and the updating of the settings[] value for the option, made the re-usable method complex and ultimately reduced readability of the code, rather than helped.

Any suggestions are of course welcome.

For the record, my monstrosity looked like this:
// Assumes that path.exists() check is true
bool ensureAccessibleDirectoryForPathOption(Common::StringMap &settings, const Common::String optionKeyStr, const Common::FSNode &path, bool ensureWriteable) {
if (path.isDirectory()) {
if ((ensureWriteable && path.isWritable())
|| (!ensureWriteable && path.isReadable())) {
return true;
}
} else if (path.getParent().isDirectory()
&& ((ensureWriteable && path.getParent().isWritable())
|| (!ensureWriteable && path.getParent().isReadable())) ) {
// The path.getParent().isDirectory() check is redundant but included for clarifying
// the purpose of this case, which is to get the folder path part from a file path
settings[optionKeyStr] = path.getParent().getPath().toString(Common::Path::kNativeSeparator);
return true;
}
return false;
}

So that the parsing for eg. the screenshotpath would look like:
DO_LONG_OPTION("screenshotpath")
Common::FSNode path(option);
if (!path.exists()) {
usage("Non-existent screenshot path '%s'", option);
} else if (!ensureAccessibleDirectoryForPathOption(settings, "screenshotpath", path, true)) {
usage("Non-writable screenshot path '%s'", option);
}
END_OPTION

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this code is repeated, could we wrap it into a reusable function?

I went ahead an committed the method above, with some comments to clarify its purpose

Comment on lines 501 to 503
} else if (path.getParent().isDirectory()
&& ((ensureWriteable && path.getParent().isWritable())
|| (!ensureWriteable && path.getParent().isReadable())) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (path.getParent().isDirectory()
&& ((ensureWriteable && path.getParent().isWritable())
|| (!ensureWriteable && path.getParent().isReadable())) ) {
} else if (path.getParent().isDirectory()
&& ensureAccessibleDirectoryForPathOption(settings, optionKeyStr, path, ensureWritable)) {

I am not sure this makes the code more readable, but you could do a recursive call here to avoid duplicating the same condition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I am wondering if accepting files as argument makes sense for all options. In particular this seems a strange behaviour for writable paths (e.g. screenshots).

So you could have something like

bool ensureAccessibleDirectoryForPathOption(Common::StringMap &settings, const Common::String optionKeyStr, const Common::FSNode &path, bool ensureWriteable, bool acceptFile) {
	if (path.isDirectory()) {
		...
	} else if (acceptFile && ensureAccessibleDirectoryForPathOption(settings, optionKeyStr, path.getParent(), ensureWriteable, false)) {
		...
	}
	return false;
``

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I am wondering if FSNode::isDirectory() work as expected in the case of a symbolic link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I am wondering if FSNode::isDirectory() work as expected in the case of a symbolic link?

I did a few tests on Linux (Ubuntu 22.04, ln and ln -s) and Windows 10 (mklink, mklink /D, mklink /H, mklink /J), with the current version of the code for the PR (I did not implement the recursive version yet).

For links to folders (symbolic or hard) the code works, and isDirectoty() works as expected (returns true). This is on both Linux and Windows.

For links to files (again symbolic or hard) the isDirectory() returns false, and the code enters the second clause. In both OSes it then returns false, but for different reasons. On Linux, it seems that getParent() returns the same FSNode (or a FSNode for the same path as the symbolic link) and the isDirectory() check returns false (as it did before we attempted getParent()). On Windows, getParent() returns an FSNode with empty path, for which isDirectory() is true, but it fails readability and writeability checks, so false is returned.

So the code does (still) support symbolic links to folder paths, but for symbolic links to files, it won't get a valid parent folder path.

This seems acceptable to me (of course more tests/checks by anyone else are welcome), but I should probably add an extra note in the comments that it does not work (does not get a valid parent path) for symbolic links to files and will return false.

Comment on lines 497 to 498
if ((ensureWriteable && path.isWritable())
|| (!ensureWriteable && path.isReadable())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory a path could be writable and not readable. In the case of screenshots we only need it to be writable, but in the case of savegames we need it to be both writable and readable. I know we do not currently check it is readable, but we could use this change to also add that check.

Also made the ensureAccessibleDirectoryForPathOption() method use a recursive call to reduce complexity

The following decisions are made for commandline path options accessibility checks:
"screenshotpath" option is required to be writeable (not checked for readable)
"path" option is required to be readable (not checked for writeable)
"savepath" option is required to be readable AND writeable
"extrapath" option is required to be readable (not checked for writeable)
"iconspath" option is required to be readable AND writeable
"themepath" option is required to be readable (not checked for writeable)
@sev-
Copy link
Member

sev- commented Jan 20, 2024

Thank you!

@sev- sev- merged commit 75bfee7 into scummvm:master Jan 20, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants