mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-10 04:24:05 +08:00
Implement Errno system
This commit adds onto 679e0de0
If an exception is thrown, the exception message might not contain the full errors. Individual failures may get added to suppressed throwables. FileUtils functions previously just returned the exception message as errmsg which did not contain full error info.
Now `Error` class has been implemented which will used to return errors, including suppressed throwables. Each `Error` object will have an error type, code, message and a list of throwables in case multiple throwables need to returned, in addition to the suppressed throwables list in each throwable.
A supportive `Errno` base class has been implemented as well which other errno classes can inherit of which some have been added. Each `Errno` object will have an error type, code and message and can be converted to an `Error` object if needed.
Requirement for `Context` object has been removed from FileUtils so that they can be called from anywhere in code instead of having to pass around `Context` objects. Previously, `string.xml` was used to store error messages in case multi language support had to be added in future since error messages are displayed to users and not just for dev usage. However, now this will have to handled in java code if needed, based on locale.
The termux related file utils have also been moved from FileUtils to TermuxFileUtils
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
||||
package com.termux.shared.file;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import com.termux.shared.models.errors.Error;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TermuxFileUtils {
|
||||
/**
|
||||
* Replace "$PREFIX/" or "~/" prefix with termux absolute paths.
|
||||
*
|
||||
* @param path The {@code path} to expand.
|
||||
* @return Returns the {@code expand path}.
|
||||
*/
|
||||
public static String getExpandedTermuxPath(String path) {
|
||||
if (path != null && !path.isEmpty()) {
|
||||
path = path.replaceAll("^\\$PREFIX$", TermuxConstants.TERMUX_PREFIX_DIR_PATH);
|
||||
path = path.replaceAll("^\\$PREFIX/", TermuxConstants.TERMUX_PREFIX_DIR_PATH + "/");
|
||||
path = path.replaceAll("^~/$", TermuxConstants.TERMUX_HOME_DIR_PATH);
|
||||
path = path.replaceAll("^~/", TermuxConstants.TERMUX_HOME_DIR_PATH + "/");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace termux absolute paths with "$PREFIX/" or "~/" prefix.
|
||||
*
|
||||
* @param path The {@code path} to unexpand.
|
||||
* @return Returns the {@code unexpand path}.
|
||||
*/
|
||||
public static String getUnExpandedTermuxPath(String path) {
|
||||
if (path != null && !path.isEmpty()) {
|
||||
path = path.replaceAll("^" + Pattern.quote(TermuxConstants.TERMUX_PREFIX_DIR_PATH) + "/", "\\$PREFIX/");
|
||||
path = path.replaceAll("^" + Pattern.quote(TermuxConstants.TERMUX_HOME_DIR_PATH) + "/", "~/");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get canonical path.
|
||||
*
|
||||
* @param path The {@code path} to convert.
|
||||
* @param prefixForNonAbsolutePath Optional prefix path to prefix before non-absolute paths. This
|
||||
* can be set to {@code null} if non-absolute paths should
|
||||
* be prefixed with "/". The call to {@link File#getCanonicalPath()}
|
||||
* will automatically do this anyways.
|
||||
* @param expandPath The {@code boolean} that decides if input path is first attempted to be expanded by calling
|
||||
* {@link TermuxFileUtils#getExpandedTermuxPath(String)} before its passed to
|
||||
* {@link FileUtils#getCanonicalPath(String, String)}.
|
||||
|
||||
* @return Returns the {@code canonical path}.
|
||||
*/
|
||||
public static String getCanonicalPath(String path, final String prefixForNonAbsolutePath, final boolean expandPath) {
|
||||
if (path == null) path = "";
|
||||
|
||||
if (expandPath)
|
||||
path = getExpandedTermuxPath(path);
|
||||
|
||||
return FileUtils.getCanonicalPath(path, prefixForNonAbsolutePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if {@code path} is under the allowed termux working directory paths. If it is, then
|
||||
* allowed parent path is returned.
|
||||
*
|
||||
* @param path The {@code path} to check.
|
||||
* @return Returns the allowed path if it {@code path} is under it, otherwise {@link TermuxConstants#TERMUX_FILES_DIR_PATH}.
|
||||
*/
|
||||
public static String getMatchedAllowedTermuxWorkingDirectoryParentPathForPath(String path) {
|
||||
if (path == null || path.isEmpty()) return TermuxConstants.TERMUX_FILES_DIR_PATH;
|
||||
|
||||
if (path.startsWith(TermuxConstants.TERMUX_STORAGE_HOME_DIR_PATH + "/")) {
|
||||
return TermuxConstants.TERMUX_STORAGE_HOME_DIR_PATH;
|
||||
} if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath() + "/")) {
|
||||
return Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
} else if (path.startsWith("/sdcard/")) {
|
||||
return "/sdcard";
|
||||
} else {
|
||||
return TermuxConstants.TERMUX_FILES_DIR_PATH;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the existence and permissions of directory file at path as a working directory for
|
||||
* termux app.
|
||||
*
|
||||
* The creation of missing directory and setting of missing permissions will only be done if
|
||||
* {@code path} is under paths returned by {@link #getMatchedAllowedTermuxWorkingDirectoryParentPathForPath(String)}.
|
||||
*
|
||||
* The permissions set to directory will be {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS}.
|
||||
*
|
||||
* @param label The optional label for the directory file. This can optionally be {@code null}.
|
||||
* @param filePath The {@code path} for file to validate or create. Symlinks will not be followed.
|
||||
* @param createDirectoryIfMissing The {@code boolean} that decides if directory file
|
||||
* should be created if its missing.
|
||||
* @param setPermissions The {@code boolean} that decides if permissions are to be
|
||||
* automatically set defined by {@code permissionsToCheck}.
|
||||
* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
|
||||
* are to be set or if they should be overridden.
|
||||
* @param ignoreErrorsIfPathIsInParentDirPath The {@code boolean} that decides if existence
|
||||
* and permission errors are to be ignored if path is
|
||||
* in {@code parentDirPath}.
|
||||
* @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission
|
||||
* error is to be ignored. This allows making an attempt to set
|
||||
* executable permissions, but ignoring if it fails.
|
||||
* @return Returns the {@code error} if path is not a directory file, failed to create it,
|
||||
* or validating permissions failed, otherwise {@code null}.
|
||||
*/
|
||||
public static Error validateDirectoryFileExistenceAndPermissions(String label, final String filePath, final boolean createDirectoryIfMissing,
|
||||
final boolean setPermissions, final boolean setMissingPermissionsOnly,
|
||||
final boolean ignoreErrorsIfPathIsInParentDirPath, final boolean ignoreIfNotExecutable) {
|
||||
return FileUtils.validateDirectoryFileExistenceAndPermissions(label, filePath,
|
||||
TermuxFileUtils.getMatchedAllowedTermuxWorkingDirectoryParentPathForPath(filePath), createDirectoryIfMissing,
|
||||
FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, setPermissions, setMissingPermissionsOnly,
|
||||
ignoreErrorsIfPathIsInParentDirPath, ignoreIfNotExecutable);
|
||||
}
|
||||
|
||||
}
|
@@ -90,7 +90,7 @@ public class FileTypes {
|
||||
return getFileType(fileAttributes);
|
||||
} catch (Exception e) {
|
||||
// If not a ENOENT (No such file or directory) exception
|
||||
if (!e.getMessage().contains("ENOENT"))
|
||||
if (e.getMessage() != null && !e.getMessage().contains("ENOENT"))
|
||||
Logger.logError("Failed to get file type for file at path \"" + filePath + "\": " + e.getMessage());
|
||||
return FileType.NO_EXIST;
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.file.FileUtils;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.models.errors.Error;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -31,18 +32,19 @@ public class FileUtilsTests {
|
||||
Logger.logInfo(LOG_TAG, "Running tests");
|
||||
Logger.logInfo(LOG_TAG, "testRootDirectoryPath: \"" + testRootDirectoryPath + "\"");
|
||||
|
||||
String fileUtilsTestsDirectoryCanonicalPath = FileUtils.getCanonicalPath(testRootDirectoryPath, null, false);
|
||||
String fileUtilsTestsDirectoryCanonicalPath = FileUtils.getCanonicalPath(testRootDirectoryPath, null);
|
||||
assertEqual("FileUtilsTests directory path is not a canonical path", testRootDirectoryPath, fileUtilsTestsDirectoryCanonicalPath);
|
||||
|
||||
runTestsInner(context, testRootDirectoryPath);
|
||||
runTestsInner(testRootDirectoryPath);
|
||||
Logger.logInfo(LOG_TAG, "All tests successful");
|
||||
} catch (Exception e) {
|
||||
Logger.logErrorAndShowToast(context, LOG_TAG, e.getMessage());
|
||||
Logger.logErrorExtended(LOG_TAG, e.getMessage());
|
||||
Logger.showToast(context, e.getMessage() != null ? e.getMessage().replaceAll("(?s)\nFull Error:\n.*", "") : null, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void runTestsInner(@NonNull final Context context, @NonNull final String testRootDirectoryPath) throws Exception {
|
||||
String errmsg;
|
||||
private static void runTestsInner(@NonNull final String testRootDirectoryPath) throws Exception {
|
||||
Error error;
|
||||
String label;
|
||||
String path;
|
||||
|
||||
@@ -101,20 +103,20 @@ public class FileUtilsTests {
|
||||
|
||||
// Create or clear test root directory file
|
||||
label = "testRootDirectoryPath";
|
||||
errmsg = FileUtils.clearDirectory(context, label, testRootDirectoryPath);
|
||||
assertEqual("Failed to create " + label + " directory file", null, errmsg);
|
||||
error = FileUtils.clearDirectory(label, testRootDirectoryPath);
|
||||
assertEqual("Failed to create " + label + " directory file", null, error);
|
||||
|
||||
if (!FileUtils.directoryFileExists(testRootDirectoryPath, false))
|
||||
throwException("The " + label + " directory file does not exist as expected after creation");
|
||||
|
||||
|
||||
// Create dir1 directory file
|
||||
errmsg = FileUtils.createDirectoryFile(context, dir1_label, dir1_path);
|
||||
assertEqual("Failed to create " + dir1_label + " directory file", null, errmsg);
|
||||
error = FileUtils.createDirectoryFile(dir1_label, dir1_path);
|
||||
assertEqual("Failed to create " + dir1_label + " directory file", null, error);
|
||||
|
||||
// Create dir2 directory file
|
||||
errmsg = FileUtils.createDirectoryFile(context, dir2_label, dir2_path);
|
||||
assertEqual("Failed to create " + dir2_label + " directory file", null, errmsg);
|
||||
error = FileUtils.createDirectoryFile(dir2_label, dir2_path);
|
||||
assertEqual("Failed to create " + dir2_label + " directory file", null, error);
|
||||
|
||||
|
||||
|
||||
@@ -122,29 +124,29 @@ public class FileUtilsTests {
|
||||
|
||||
// Create dir1/sub_dir1 directory file
|
||||
label = dir1__sub_dir1_label; path = dir1__sub_dir1_path;
|
||||
errmsg = FileUtils.createDirectoryFile(context, label, path);
|
||||
assertEqual("Failed to create " + label + " directory file", null, errmsg);
|
||||
error = FileUtils.createDirectoryFile(label, path);
|
||||
assertEqual("Failed to create " + label + " directory file", null, error);
|
||||
if (!FileUtils.directoryFileExists(path, false))
|
||||
throwException("The " + label + " directory file does not exist as expected after creation");
|
||||
|
||||
// Create dir1/sub_reg1 regular file
|
||||
label = dir1__sub_reg1_label; path = dir1__sub_reg1_path;
|
||||
errmsg = FileUtils.createRegularFile(context, label, path);
|
||||
assertEqual("Failed to create " + label + " regular file", null, errmsg);
|
||||
error = FileUtils.createRegularFile(label, path);
|
||||
assertEqual("Failed to create " + label + " regular file", null, error);
|
||||
if (!FileUtils.regularFileExists(path, false))
|
||||
throwException("The " + label + " regular file does not exist as expected after creation");
|
||||
|
||||
// Create dir1/sub_sym1 -> dir2 absolute symlink file
|
||||
label = dir1__sub_sym1_label; path = dir1__sub_sym1_path;
|
||||
errmsg = FileUtils.createSymlinkFile(context, label, dir2_path, path);
|
||||
assertEqual("Failed to create " + label + " symlink file", null, errmsg);
|
||||
error = FileUtils.createSymlinkFile(label, dir2_path, path);
|
||||
assertEqual("Failed to create " + label + " symlink file", null, error);
|
||||
if (!FileUtils.symlinkFileExists(path))
|
||||
throwException("The " + label + " symlink file does not exist as expected after creation");
|
||||
|
||||
// Copy dir1/sub_sym1 symlink file to dir1/sub_sym2
|
||||
label = dir1__sub_sym2_label; path = dir1__sub_sym2_path;
|
||||
errmsg = FileUtils.copySymlinkFile(context, label, dir1__sub_sym1_path, path, false);
|
||||
assertEqual("Failed to copy " + dir1__sub_sym1_label + " symlink file to " + label, null, errmsg);
|
||||
error = FileUtils.copySymlinkFile(label, dir1__sub_sym1_path, path, false);
|
||||
assertEqual("Failed to copy " + dir1__sub_sym1_label + " symlink file to " + label, null, error);
|
||||
if (!FileUtils.symlinkFileExists(path))
|
||||
throwException("The " + label + " symlink file does not exist as expected after copying it from " + dir1__sub_sym1_label);
|
||||
if (!new File(path).getCanonicalPath().equals(dir2_path))
|
||||
@@ -156,25 +158,25 @@ public class FileUtilsTests {
|
||||
|
||||
// Write "line1" to dir2/sub_reg1 regular file
|
||||
label = dir2__sub_reg1_label; path = dir2__sub_reg1_path;
|
||||
errmsg = FileUtils.writeStringToFile(context, label, path, Charset.defaultCharset(), "line1", false);
|
||||
assertEqual("Failed to write string to " + label + " file with append mode false", null, errmsg);
|
||||
error = FileUtils.writeStringToFile(label, path, Charset.defaultCharset(), "line1", false);
|
||||
assertEqual("Failed to write string to " + label + " file with append mode false", null, error);
|
||||
if (!FileUtils.regularFileExists(path, false))
|
||||
throwException("The " + label + " file does not exist as expected after writing to it with append mode false");
|
||||
|
||||
// Write "line2" to dir2/sub_reg1 regular file
|
||||
errmsg = FileUtils.writeStringToFile(context, label, path, Charset.defaultCharset(), "\nline2", true);
|
||||
assertEqual("Failed to write string to " + label + " file with append mode true", null, errmsg);
|
||||
error = FileUtils.writeStringToFile(label, path, Charset.defaultCharset(), "\nline2", true);
|
||||
assertEqual("Failed to write string to " + label + " file with append mode true", null, error);
|
||||
|
||||
// Read dir2/sub_reg1 regular file
|
||||
StringBuilder dataStringBuilder = new StringBuilder();
|
||||
errmsg = FileUtils.readStringFromFile(context, label, path, Charset.defaultCharset(), dataStringBuilder, false);
|
||||
assertEqual("Failed to read from " + label + " file", null, errmsg);
|
||||
error = FileUtils.readStringFromFile(label, path, Charset.defaultCharset(), dataStringBuilder, false);
|
||||
assertEqual("Failed to read from " + label + " file", null, error);
|
||||
assertEqual("The data read from " + label + " file in not as expected", "line1\nline2", dataStringBuilder.toString());
|
||||
|
||||
// Copy dir2/sub_reg1 regular file to dir2/sub_reg2 file
|
||||
label = dir2__sub_reg2_label; path = dir2__sub_reg2_path;
|
||||
errmsg = FileUtils.copyRegularFile(context, label, dir2__sub_reg1_path, path, false);
|
||||
assertEqual("Failed to copy " + dir2__sub_reg1_label + " regular file to " + label, null, errmsg);
|
||||
error = FileUtils.copyRegularFile(label, dir2__sub_reg1_path, path, false);
|
||||
assertEqual("Failed to copy " + dir2__sub_reg1_label + " regular file to " + label, null, error);
|
||||
if (!FileUtils.regularFileExists(path, false))
|
||||
throwException("The " + label + " regular file does not exist as expected after copying it from " + dir2__sub_reg1_label);
|
||||
|
||||
@@ -184,22 +186,22 @@ public class FileUtilsTests {
|
||||
|
||||
// Copy dir1 directory file to dir3
|
||||
label = dir3_label; path = dir3_path;
|
||||
errmsg = FileUtils.copyDirectoryFile(context, label, dir2_path, path, false);
|
||||
assertEqual("Failed to copy " + dir2_label + " directory file to " + label, null, errmsg);
|
||||
error = FileUtils.copyDirectoryFile(label, dir2_path, path, false);
|
||||
assertEqual("Failed to copy " + dir2_label + " directory file to " + label, null, error);
|
||||
if (!FileUtils.directoryFileExists(path, false))
|
||||
throwException("The " + label + " directory file does not exist as expected after copying it from " + dir2_label);
|
||||
|
||||
// Copy dir1 directory file to dir3 again to test overwrite
|
||||
label = dir3_label; path = dir3_path;
|
||||
errmsg = FileUtils.copyDirectoryFile(context, label, dir2_path, path, false);
|
||||
assertEqual("Failed to copy " + dir2_label + " directory file to " + label, null, errmsg);
|
||||
error = FileUtils.copyDirectoryFile(label, dir2_path, path, false);
|
||||
assertEqual("Failed to copy " + dir2_label + " directory file to " + label, null, error);
|
||||
if (!FileUtils.directoryFileExists(path, false))
|
||||
throwException("The " + label + " directory file does not exist as expected after copying it from " + dir2_label);
|
||||
|
||||
// Move dir3 directory file to dir4
|
||||
label = dir4_label; path = dir4_path;
|
||||
errmsg = FileUtils.moveDirectoryFile(context, label, dir3_path, path, false);
|
||||
assertEqual("Failed to move " + dir3_label + " directory file to " + label, null, errmsg);
|
||||
error = FileUtils.moveDirectoryFile(label, dir3_path, path, false);
|
||||
assertEqual("Failed to move " + dir3_label + " directory file to " + label, null, error);
|
||||
if (!FileUtils.directoryFileExists(path, false))
|
||||
throwException("The " + label + " directory file does not exist as expected after copying it from " + dir3_label);
|
||||
|
||||
@@ -209,16 +211,16 @@ public class FileUtilsTests {
|
||||
|
||||
// Create dir1/sub_sym3 -> dir4 relative symlink file
|
||||
label = dir1__sub_sym3_label; path = dir1__sub_sym3_path;
|
||||
errmsg = FileUtils.createSymlinkFile(context, label, "../dir4", path);
|
||||
assertEqual("Failed to create " + label + " symlink file", null, errmsg);
|
||||
error = FileUtils.createSymlinkFile(label, "../dir4", path);
|
||||
assertEqual("Failed to create " + label + " symlink file", null, error);
|
||||
if (!FileUtils.symlinkFileExists(path))
|
||||
throwException("The " + label + " symlink file does not exist as expected after creation");
|
||||
|
||||
// Create dir1/sub_sym3 -> dirX relative dangling symlink file
|
||||
// This is to ensure that symlinkFileExists returns true if a symlink file exists but is dangling
|
||||
label = dir1__sub_sym3_label; path = dir1__sub_sym3_path;
|
||||
errmsg = FileUtils.createSymlinkFile(context, label, "../dirX", path);
|
||||
assertEqual("Failed to create " + label + " symlink file", null, errmsg);
|
||||
error = FileUtils.createSymlinkFile(label, "../dirX", path);
|
||||
assertEqual("Failed to create " + label + " symlink file", null, error);
|
||||
if (!FileUtils.symlinkFileExists(path))
|
||||
throwException("The " + label + " dangling symlink file does not exist as expected after creation");
|
||||
|
||||
@@ -228,8 +230,8 @@ public class FileUtilsTests {
|
||||
|
||||
// Delete dir1/sub_sym2 symlink file
|
||||
label = dir1__sub_sym2_label; path = dir1__sub_sym2_path;
|
||||
errmsg = FileUtils.deleteSymlinkFile(context, label, path, false);
|
||||
assertEqual("Failed to delete " + label + " symlink file", null, errmsg);
|
||||
error = FileUtils.deleteSymlinkFile(label, path, false);
|
||||
assertEqual("Failed to delete " + label + " symlink file", null, error);
|
||||
if (FileUtils.fileExists(path, false))
|
||||
throwException("The " + label + " symlink file still exist after deletion");
|
||||
|
||||
@@ -245,8 +247,8 @@ public class FileUtilsTests {
|
||||
|
||||
// Delete dir1 directory file
|
||||
label = dir1_label; path = dir1_path;
|
||||
errmsg = FileUtils.deleteDirectoryFile(context, label, path, false);
|
||||
assertEqual("Failed to delete " + label + " directory file", null, errmsg);
|
||||
error = FileUtils.deleteDirectoryFile(label, path, false);
|
||||
assertEqual("Failed to delete " + label + " directory file", null, error);
|
||||
if (FileUtils.fileExists(path, false))
|
||||
throwException("The " + label + " directory file still exist after deletion");
|
||||
|
||||
@@ -267,8 +269,8 @@ public class FileUtilsTests {
|
||||
|
||||
// Delete dir2/sub_reg1 regular file
|
||||
label = dir2__sub_reg1_label; path = dir2__sub_reg1_path;
|
||||
errmsg = FileUtils.deleteRegularFile(context, label, path, false);
|
||||
assertEqual("Failed to delete " + label + " regular file", null, errmsg);
|
||||
error = FileUtils.deleteRegularFile(label, path, false);
|
||||
assertEqual("Failed to delete " + label + " regular file", null, error);
|
||||
if (FileUtils.fileExists(path, false))
|
||||
throwException("The " + label + " regular file still exist after deletion");
|
||||
|
||||
@@ -276,6 +278,14 @@ public class FileUtilsTests {
|
||||
FileUtils.getFileType("/dev/null", false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void assertEqual(@NonNull final String message, final String expected, final Error actual) throws Exception {
|
||||
String actualString = actual != null ? actual.getMessage() : null;
|
||||
if (!equalsRegardingNull(expected, actualString))
|
||||
throwException(message + "\nexpected: \"" + expected + "\"\nactual: \"" + actualString + "\"\nFull Error:\n" + (actual != null ? actual.toString() : ""));
|
||||
}
|
||||
|
||||
public static void assertEqual(@NonNull final String message, final String expected, final String actual) throws Exception {
|
||||
if (!equalsRegardingNull(expected, actual))
|
||||
throwException(message + "\nexpected: \"" + expected + "\"\nactual: \"" + actual + "\"");
|
||||
|
Reference in New Issue
Block a user