From 4494bc66e4be7a46c28d331a7177e9f6cc24f721 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 26 Jun 2021 07:23:34 +0500 Subject: [PATCH] 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 --- .../java/com/termux/app/TermuxInstaller.java | 40 +- .../java/com/termux/app/utils/CrashUtils.java | 17 +- .../com/termux/shared/crash/CrashHandler.java | 9 +- .../com/termux/shared/file/FileUtils.java | 555 ++++++++---------- .../termux/shared/file/TermuxFileUtils.java | 123 ++++ .../shared/file/filesystem/FileTypes.java | 2 +- .../shared/file/tests/FileUtilsTests.java | 96 +-- .../termux/shared/models/errors/Errno.java | 89 +++ .../termux/shared/models/errors/Error.java | 249 ++++++++ .../shared/models/errors/FileUtilsErrno.java | 81 +++ .../shared/models/errors/FunctionErrno.java | 20 + .../com/termux/shared/shell/ShellUtils.java | 11 +- termux-shared/src/main/res/values/strings.xml | 47 +- 13 files changed, 905 insertions(+), 434 deletions(-) create mode 100644 termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java create mode 100644 termux-shared/src/main/java/com/termux/shared/models/errors/Errno.java create mode 100644 termux-shared/src/main/java/com/termux/shared/models/errors/Error.java create mode 100644 termux-shared/src/main/java/com/termux/shared/models/errors/FileUtilsErrno.java create mode 100644 termux-shared/src/main/java/com/termux/shared/models/errors/FunctionErrno.java diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index 7a1b5456..6b4782eb 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -14,6 +14,7 @@ import com.termux.R; import com.termux.shared.file.FileUtils; import com.termux.shared.interact.DialogUtils; import com.termux.shared.logger.Logger; +import com.termux.shared.models.errors.Error; import com.termux.shared.termux.TermuxConstants; import java.io.BufferedReader; @@ -88,21 +89,21 @@ final class TermuxInstaller { try { Logger.logInfo(LOG_TAG, "Installing " + TermuxConstants.TERMUX_APP_NAME + " bootstrap packages."); - String errmsg; + Error error; final String STAGING_PREFIX_PATH = TermuxConstants.TERMUX_STAGING_PREFIX_DIR_PATH; final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH); // Delete prefix staging directory or any file at its destination - errmsg = FileUtils.deleteFile(activity, "prefix staging directory", STAGING_PREFIX_PATH, true); - if (errmsg != null) { - throw new RuntimeException(errmsg); + error = FileUtils.deleteFile("prefix staging directory", STAGING_PREFIX_PATH, true); + if (error != null) { + throw new RuntimeException(error.toString()); } // Delete prefix directory or any file at its destination - errmsg = FileUtils.deleteFile(activity, "prefix directory", PREFIX_FILE_PATH, true); - if (errmsg != null) { - throw new RuntimeException(errmsg); + error = FileUtils.deleteFile("prefix directory", PREFIX_FILE_PATH, true); + if (error != null) { + throw new RuntimeException(error.toString()); } Logger.logInfo(LOG_TAG, "Extracting bootstrap zip to prefix staging directory \"" + STAGING_PREFIX_PATH + "\"."); @@ -125,14 +126,14 @@ final class TermuxInstaller { String newPath = STAGING_PREFIX_PATH + "/" + parts[1]; symlinks.add(Pair.create(oldPath, newPath)); - ensureDirectoryExists(activity, new File(newPath).getParentFile()); + ensureDirectoryExists(new File(newPath).getParentFile()); } } else { String zipEntryName = zipEntry.getName(); File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName); boolean isDirectory = zipEntry.isDirectory(); - ensureDirectoryExists(activity, isDirectory ? targetFile : targetFile.getParentFile()); + ensureDirectoryExists(isDirectory ? targetFile : targetFile.getParentFile()); if (!isDirectory) { try (FileOutputStream outStream = new FileOutputStream(targetFile)) { @@ -173,7 +174,7 @@ final class TermuxInstaller { activity.finish(); }).setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> { dialog.dismiss(); - FileUtils.deleteFile(activity, "prefix directory", PREFIX_FILE_PATH, true); + FileUtils.deleteFile("prefix directory", PREFIX_FILE_PATH, true); TermuxInstaller.setupBootstrapIfNeeded(activity, whenDone); }).show(); } catch (WindowManager.BadTokenException e1) { @@ -201,12 +202,13 @@ final class TermuxInstaller { new Thread() { public void run() { try { - String errmsg; + Error error; File storageDir = TermuxConstants.TERMUX_STORAGE_HOME_DIR; - errmsg = FileUtils.clearDirectory(context, "~/storage", storageDir.getAbsolutePath()); - if (errmsg != null) { - Logger.logErrorAndShowToast(context, LOG_TAG, errmsg); + error = FileUtils.clearDirectory("~/storage", storageDir.getAbsolutePath()); + if (error != null) { + Logger.logErrorAndShowToast(context, LOG_TAG, error.getMessage()); + Logger.logErrorExtended(LOG_TAG, error.toString()); return; } @@ -249,12 +251,12 @@ final class TermuxInstaller { }.start(); } - private static void ensureDirectoryExists(Context context, File directory) { - String errmsg; + private static void ensureDirectoryExists(File directory) { + Error error; - errmsg = FileUtils.createDirectoryFile(context, directory.getAbsolutePath()); - if (errmsg != null) { - throw new RuntimeException(errmsg); + error = FileUtils.createDirectoryFile(directory.getAbsolutePath()); + if (error != null) { + throw new RuntimeException(error.toString()); } } diff --git a/app/src/main/java/com/termux/app/utils/CrashUtils.java b/app/src/main/java/com/termux/app/utils/CrashUtils.java index 55a65ef4..63cd0356 100644 --- a/app/src/main/java/com/termux/app/utils/CrashUtils.java +++ b/app/src/main/java/com/termux/app/utils/CrashUtils.java @@ -10,6 +10,7 @@ import androidx.annotation.Nullable; import com.termux.R; import com.termux.shared.activities.ReportActivity; +import com.termux.shared.models.errors.Error; import com.termux.shared.notification.NotificationUtils; import com.termux.shared.file.FileUtils; import com.termux.shared.models.ReportInfo; @@ -62,25 +63,25 @@ public class CrashUtils { if (!FileUtils.regularFileExists(TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, false)) return; - String errmsg; + Error error; StringBuilder reportStringBuilder = new StringBuilder(); // Read report string from crash log file - errmsg = FileUtils.readStringFromFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportStringBuilder, false); - if (errmsg != null) { - Logger.logError(logTag, errmsg); + error = FileUtils.readStringFromFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportStringBuilder, false); + if (error != null) { + Logger.logErrorExtended(logTag, error.toString()); return; } // Move crash log file to backup location if it exists - FileUtils.moveRegularFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, TermuxConstants.TERMUX_CRASH_LOG_BACKUP_FILE_PATH, true); - if (errmsg != null) { - Logger.logError(logTag, errmsg); + error = FileUtils.moveRegularFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, TermuxConstants.TERMUX_CRASH_LOG_BACKUP_FILE_PATH, true); + if (error != null) { + Logger.logErrorExtended(logTag, error.toString()); } String reportString = reportStringBuilder.toString(); - if (reportString == null || reportString.isEmpty()) + if (reportString.isEmpty()) return; // Send a notification to show the crash log which when clicked will open the {@link ReportActivity} diff --git a/termux-shared/src/main/java/com/termux/shared/crash/CrashHandler.java b/termux-shared/src/main/java/com/termux/shared/crash/CrashHandler.java index 8cdbdf24..1f7c82ad 100644 --- a/termux-shared/src/main/java/com/termux/shared/crash/CrashHandler.java +++ b/termux-shared/src/main/java/com/termux/shared/crash/CrashHandler.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import com.termux.shared.file.FileUtils; import com.termux.shared.logger.Logger; import com.termux.shared.markdown.MarkdownUtils; +import com.termux.shared.models.errors.Error; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxUtils; @@ -57,7 +58,7 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler { reportString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Crash Thread", thread.toString(), "-")); reportString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Crash Timestamp", TermuxUtils.getCurrentTimeStamp(), "-")); - reportString.append("\n\n").append(Logger.getStackTracesMarkdownString("Stacktrace", Logger.getStackTraceStringArray(throwable))); + reportString.append("\n\n").append(Logger.getStackTracesMarkdownString("Stacktrace", Logger.getStackTracesStringArray(throwable))); reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(context, true)); reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context)); @@ -65,9 +66,9 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler { Logger.logError(reportString.toString()); // Write report string to crash log file - String errmsg = FileUtils.writeStringToFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportString.toString(), false); - if (errmsg != null) { - Logger.logError(LOG_TAG, errmsg); + Error error = FileUtils.writeStringToFile("crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportString.toString(), false); + if (error != null) { + Logger.logErrorExtended(LOG_TAG, error.toString()); } } diff --git a/termux-shared/src/main/java/com/termux/shared/file/FileUtils.java b/termux-shared/src/main/java/com/termux/shared/file/FileUtils.java index 71b1ae15..4bc81933 100644 --- a/termux-shared/src/main/java/com/termux/shared/file/FileUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/file/FileUtils.java @@ -1,18 +1,18 @@ package com.termux.shared.file; -import android.content.Context; import android.os.Build; import android.system.Os; import androidx.annotation.NonNull; import com.google.common.io.RecursiveDeleteOption; -import com.termux.shared.R; -import com.termux.shared.termux.TermuxConstants; import com.termux.shared.file.filesystem.FileType; import com.termux.shared.file.filesystem.FileTypes; import com.termux.shared.data.DataUtils; import com.termux.shared.logger.Logger; +import com.termux.shared.models.errors.Error; +import com.termux.shared.models.errors.FileUtilsErrno; +import com.termux.shared.models.errors.FunctionErrno; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -21,9 +21,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.nio.file.LinkOption; @@ -32,45 +30,18 @@ import java.util.regex.Pattern; public class FileUtils { + /** Required file permissions for the executable file for app usage. Executable file must have read and execute permissions */ + public static final String APP_EXECUTABLE_FILE_PERMISSIONS = "r-x"; // Default: "r-x" + /** Required file permissions for the working directory for app usage. Working directory must have read and write permissions. + * Execute permissions should be attempted to be set, but ignored if they are missing */ + public static final String APP_WORKING_DIRECTORY_PERMISSIONS = "rwx"; // Default: "rwx" + private static final String LOG_TAG = "FileUtils"; /** - * Replace "$PREFIX/" or "~/" prefix with termux absolute paths. + * Get canonical path. * - * @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; - } - - /** - * If {@code expandPath} is enabled, then input path is first attempted to be expanded by calling - * {@link #getExpandedTermuxPath(String)}. - * - * Then if path is already an absolute path, then it is used as is to get canonical path. + * If path is already an absolute path, then it is used as is to get canonical path. * If path is not an absolute path and {code prefixForNonAbsolutePath} is not {@code null}, then * {code prefixForNonAbsolutePath} + "/" is prefixed before path before getting canonical path. * If path is not an absolute path and {code prefixForNonAbsolutePath} is {@code null}, then @@ -85,12 +56,9 @@ public class FileUtils { * will automatically do this anyways. * @return Returns the {@code canonical path}. */ - public static String getCanonicalPath(String path, final String prefixForNonAbsolutePath, final boolean expandPath) { + public static String getCanonicalPath(String path, final String prefixForNonAbsolutePath) { if (path == null) path = ""; - if (expandPath) - path = getExpandedTermuxPath(path); - String absolutePath; // If path is already an absolute path @@ -160,7 +128,6 @@ public class FileUtils { } - /** * Checks whether a regular file exists at {@code filePath}. * @@ -215,7 +182,7 @@ public class FileUtils { * * This function is a wrapper for * {@link FileTypes#getFileType(String, boolean)} - * + * * @param filePath The {@code path} for file to check. * @param followLinks The {@code boolean} that decides if symlinks will be followed while * finding type. If set to {@code true}, then type of symlink target will @@ -236,7 +203,6 @@ public class FileUtils { * If the {@code parentDirPath} is not {@code null}, then setting of missing permissions will * only be done if {@code path} is under {@code parentDirPath}. * - * @param context The {@link Context} to get error string. * @param label The optional label for the regular file. This can optionally be {@code null}. * @param filePath The {@code path} for file to validate. Symlinks will not be followed. * @param parentDirPath The optional {@code parent directory path} to restrict operations to. @@ -249,21 +215,21 @@ public class FileUtils { * @param ignoreErrorsIfPathIsUnderParentDirPath The {@code boolean} that decides if permission * errors are to be ignored if path is under * {@code parentDirPath}. - * @return Returns the {@code errmsg} if path is not a regular file, or validating permissions + * @return Returns the {@code error} if path is not a regular file, or validating permissions * failed, otherwise {@code null}. */ - public static String validateRegularFileExistenceAndPermissions(@NonNull final Context context, String label, final String filePath, final String parentDirPath, + public static Error validateRegularFileExistenceAndPermissions(String label, final String filePath, final String parentDirPath, final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly, final boolean ignoreErrorsIfPathIsUnderParentDirPath) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "regular file path", "validateRegularFileExistenceAndPermissions"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "regular file path", "validateRegularFileExistenceAndPermissions"); try { FileType fileType = getFileType(filePath, false); // If file exists but not a regular file if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) { - return context.getString(R.string.error_non_regular_file_found, label + "file"); + return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file"); } boolean isPathUnderParentDirPath = false; @@ -286,7 +252,7 @@ public class FileUtils { // If path is not a regular file // Regular files cannot be automatically created so we do not ignore if missing if (fileType != FileType.REGULAR) { - return context.getString(R.string.error_no_regular_file_found, label + "file"); + return FileUtilsErrno.ERRNO_NO_REGULAR_FILE_FOUND.getError(label + "file"); } // If there is not parentDirPath restriction or path is not under parentDirPath or @@ -294,11 +260,11 @@ public class FileUtils { if (parentDirPath == null || !isPathUnderParentDirPath || !ignoreErrorsIfPathIsUnderParentDirPath) { if (permissionsToCheck != null) { // Check if permissions are missing - return checkMissingFilePermissions(context, label + "regular", filePath, permissionsToCheck, false); + return checkMissingFilePermissions(label + "regular", filePath, permissionsToCheck, false); } } } catch (Exception e) { - return context.getString(R.string.error_validate_file_existence_and_permissions_failed_with_exception, label + "file", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_VALIDATE_FILE_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage()); } return null; @@ -312,7 +278,6 @@ public class FileUtils { * setting of missing permissions will only be done if {@code path} is under * {@code parentDirPath} or equals {@code parentDirPath}. * - * @param context The {@link Context} to get error string. * @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 parentDirPath The optional {@code parent directory path} to restrict operations to. @@ -330,14 +295,14 @@ public class FileUtils { * @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 errmsg} if path is not a directory file, failed to create it, + * @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 String validateDirectoryFileExistenceAndPermissions(@NonNull final Context context, String label, final String filePath, final String parentDirPath, final boolean createDirectoryIfMissing, + public static Error validateDirectoryFileExistenceAndPermissions(String label, final String filePath, final String parentDirPath, final boolean createDirectoryIfMissing, final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly, final boolean ignoreErrorsIfPathIsInParentDirPath, final boolean ignoreIfNotExecutable) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "directory file path", "validateDirectoryExistenceAndPermissions"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "directory file path", "validateDirectoryExistenceAndPermissions"); try { File file = new File(filePath); @@ -345,7 +310,7 @@ public class FileUtils { // If file exists but not a directory file if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) { - return context.getString(R.string.error_non_directory_file_found, label + "directory"); + return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory"); } boolean isPathInParentDirPath = false; @@ -364,7 +329,7 @@ public class FileUtils { if (file.mkdirs()) fileType = getFileType(filePath, false); else - return context.getString(R.string.error_creating_file_failed, label + "directory file", filePath); + return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED.getError(label + "directory file", filePath); } // If setPermissions is enabled and path is a directory @@ -383,16 +348,16 @@ public class FileUtils { // If path is not a directory // Directories can be automatically created so we can ignore if missing with above check if (fileType != FileType.DIRECTORY) { - return context.getString(R.string.error_file_not_found_at_path, label + "directory", filePath); + return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label + "directory", filePath); } if (permissionsToCheck != null) { // Check if permissions are missing - return checkMissingFilePermissions(context, label + "directory", filePath, permissionsToCheck, ignoreIfNotExecutable); + return checkMissingFilePermissions(label + "directory", filePath, permissionsToCheck, ignoreIfNotExecutable); } } } catch (Exception e) { - return context.getString(R.string.error_validate_directory_existence_and_permissions_failed_with_exception, label + "directory file", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_VALIDATE_DIRECTORY_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION.getError(e, label + "directory file", filePath, e.getMessage()); } return null; @@ -404,31 +369,29 @@ public class FileUtils { * Create a regular file at path. * * This function is a wrapper for - * {@link #validateDirectoryFileExistenceAndPermissions(Context, String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. + * {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. * @param filePath The {@code path} for regular file to create. - * @return Returns the {@code errmsg} if path is not a regular file or failed to create it, + * @return Returns the {@code error} if path is not a regular file or failed to create it, * otherwise {@code null}. */ - public static String createRegularFile(@NonNull final Context context, final String filePath) { - return createRegularFile(context, null, filePath); + public static Error createRegularFile(final String filePath) { + return createRegularFile(null, filePath); } /** * Create a regular file at path. * * This function is a wrapper for - * {@link #validateDirectoryFileExistenceAndPermissions(Context, String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. + * {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for the regular file. This can optionally be {@code null}. * @param filePath The {@code path} for regular file to create. - * @return Returns the {@code errmsg} if path is not a regular file or failed to create it, + * @return Returns the {@code error} if path is not a regular file or failed to create it, * otherwise {@code null}. */ - public static String createRegularFile(@NonNull final Context context, final String label, final String filePath) { - return createRegularFile(context, label, filePath, + public static Error createRegularFile(final String label, final String filePath) { + return createRegularFile(label, filePath, null, false, false); } @@ -436,9 +399,8 @@ public class FileUtils { * Create a regular file at path. * * This function is a wrapper for - * {@link #validateRegularFileExistenceAndPermissions(Context, String, String, String, String, boolean, boolean, boolean)}. + * {@link #validateRegularFileExistenceAndPermissions(String, String, String, String, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for the regular file. This can optionally be {@code null}. * @param filePath The {@code path} for regular file to create. * @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order. @@ -446,22 +408,22 @@ public class FileUtils { * 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. - * @return Returns the {@code errmsg} if path is not a regular file, failed to create it, + * @return Returns the {@code error} if path is not a regular file, failed to create it, * or validating permissions failed, otherwise {@code null}. */ - public static String createRegularFile(@NonNull final Context context, String label, final String filePath, + public static Error createRegularFile(String label, final String filePath, final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "createRegularFile"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "createRegularFile"); - String errmsg; + Error error; File file = new File(filePath); FileType fileType = getFileType(filePath, false); // If file exists but not a regular file if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) { - return context.getString(R.string.error_non_regular_file_found, label + "file"); + return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file"); } // If regular file already exists @@ -470,20 +432,20 @@ public class FileUtils { } // Create the file parent directory - errmsg = createParentDirectoryFile(context, label + "regular file parent", filePath); - if (errmsg != null) - return errmsg; + error = createParentDirectoryFile(label + "regular file parent", filePath); + if (error != null) + return error; try { Logger.logVerbose(LOG_TAG, "Creating " + label + "regular file at path \"" + filePath + "\""); if (!file.createNewFile()) - return context.getString(R.string.error_creating_file_failed, label + "regular file", filePath); + return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED.getError(label + "regular file", filePath); } catch (Exception e) { - return context.getString(R.string.error_creating_file_failed_with_exception, label + "regular file", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "regular file", filePath, e.getMessage()); } - return validateRegularFileExistenceAndPermissions(context, label, filePath, + return validateRegularFileExistenceAndPermissions(label, filePath, null, permissionsToCheck, setPermissions, setMissingPermissionsOnly, false); @@ -495,23 +457,22 @@ public class FileUtils { * Create parent directory of file at path. * * This function is a wrapper for - * {@link #validateDirectoryFileExistenceAndPermissions(Context, String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. + * {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for the parent directory file. This can optionally be {@code null}. * @param filePath The {@code path} for file whose parent needs to be created. - * @return Returns the {@code errmsg} if parent path is not a directory file or failed to create it, + * @return Returns the {@code error} if parent path is not a directory file or failed to create it, * otherwise {@code null}. */ - public static String createParentDirectoryFile(@NonNull final Context context, final String label, final String filePath) { - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "createParentDirectoryFile"); + public static Error createParentDirectoryFile(final String label, final String filePath) { + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "createParentDirectoryFile"); File file = new File(filePath); String fileParentPath = file.getParent(); if (fileParentPath != null) - return createDirectoryFile(context, label, fileParentPath, - null, false, false); + return createDirectoryFile(label, fileParentPath, + null, false, false); else return null; } @@ -520,41 +481,38 @@ public class FileUtils { * Create a directory file at path. * * This function is a wrapper for - * {@link #validateDirectoryFileExistenceAndPermissions(Context, String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. + * {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. * @param filePath The {@code path} for directory file to create. - * @return Returns the {@code errmsg} if path is not a directory file or failed to create it, + * @return Returns the {@code error} if path is not a directory file or failed to create it, * otherwise {@code null}. */ - public static String createDirectoryFile(@NonNull final Context context, final String filePath) { - return createDirectoryFile(context, null, filePath); + public static Error createDirectoryFile(final String filePath) { + return createDirectoryFile(null, filePath); } /** * Create a directory file at path. * * This function is a wrapper for - * {@link #validateDirectoryFileExistenceAndPermissions(Context, String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. + * {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for the directory file. This can optionally be {@code null}. * @param filePath The {@code path} for directory file to create. - * @return Returns the {@code errmsg} if path is not a directory file or failed to create it, + * @return Returns the {@code error} if path is not a directory file or failed to create it, * otherwise {@code null}. */ - public static String createDirectoryFile(@NonNull final Context context, final String label, final String filePath) { - return createDirectoryFile(context, label, filePath, + public static Error createDirectoryFile(final String label, final String filePath) { + return createDirectoryFile(label, filePath, null, false, false); } /** * Create a directory file at path. - * - * This function is a wrapper for - * {@link #validateDirectoryFileExistenceAndPermissions(Context, String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. * - * @param context The {@link Context} to get error string. + * This function is a wrapper for + * {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}. + * * @param label The optional label for the directory file. This can optionally be {@code null}. * @param filePath The {@code path} for directory file to create. * @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order. @@ -562,12 +520,12 @@ public class FileUtils { * 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. - * @return Returns the {@code errmsg} if path is not a directory file, failed to create it, + * @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 String createDirectoryFile(@NonNull final Context context, final String label, final String filePath, + public static Error createDirectoryFile(final String label, final String filePath, final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly) { - return validateDirectoryFileExistenceAndPermissions(context, label, filePath, + return validateDirectoryFileExistenceAndPermissions(label, filePath, null, true, permissionsToCheck, setPermissions, setMissingPermissionsOnly, false, false); @@ -579,19 +537,18 @@ public class FileUtils { * Create a symlink file at path. * * This function is a wrapper for - * {@link #createSymlinkFile(Context, String, String, String, boolean, boolean, boolean)}. + * {@link #createSymlinkFile(String, String, String, boolean, boolean, boolean)}. * * Dangling symlinks will be allowed. * Symlink destination will be overwritten if it already exists but only if its a symlink. * - * @param context The {@link Context} to get error string. * @param targetFilePath The {@code path} TO which the symlink file will be created. * @param destFilePath The {@code path} AT which the symlink file will be created. - * @return Returns the {@code errmsg} if path is not a symlink file, failed to create it, + * @return Returns the {@code error} if path is not a symlink file, failed to create it, * otherwise {@code null}. */ - public static String createSymlinkFile(@NonNull final Context context, final String targetFilePath, final String destFilePath) { - return createSymlinkFile(context, null, targetFilePath, destFilePath, + public static Error createSymlinkFile(final String targetFilePath, final String destFilePath) { + return createSymlinkFile(null, targetFilePath, destFilePath, true, true, true); } @@ -599,27 +556,25 @@ public class FileUtils { * Create a symlink file at path. * * This function is a wrapper for - * {@link #createSymlinkFile(Context, String, String, String, boolean, boolean, boolean)}. + * {@link #createSymlinkFile(String, String, String, boolean, boolean, boolean)}. * * Dangling symlinks will be allowed. * Symlink destination will be overwritten if it already exists but only if its a symlink. * - * @param context The {@link Context} to get error string. * @param label The optional label for the symlink file. This can optionally be {@code null}. * @param targetFilePath The {@code path} TO which the symlink file will be created. * @param destFilePath The {@code path} AT which the symlink file will be created. - * @return Returns the {@code errmsg} if path is not a symlink file, failed to create it, + * @return Returns the {@code error} if path is not a symlink file, failed to create it, * otherwise {@code null}. */ - public static String createSymlinkFile(@NonNull final Context context, String label, final String targetFilePath, final String destFilePath) { - return createSymlinkFile(context, label, targetFilePath, destFilePath, + public static Error createSymlinkFile(String label, final String targetFilePath, final String destFilePath) { + return createSymlinkFile(label, targetFilePath, destFilePath, true, true, true); } /** * Create a symlink file at path. * - * @param context The {@link Context} to get error string. * @param label The optional label for the symlink file. This can optionally be {@code null}. * @param targetFilePath The {@code path} TO which the symlink file will be created. * @param destFilePath The {@code path} AT which the symlink file will be created. @@ -630,16 +585,16 @@ public class FileUtils { * deleted before symlink is created. * @param overwriteOnlyIfDestIsASymlink The {@code boolean} that decides if overwrite should * only be done if destination file is also a symlink. - * @return Returns the {@code errmsg} if path is not a symlink file, failed to create it, + * @return Returns the {@code error} if path is not a symlink file, failed to create it, * or validating permissions failed, otherwise {@code null}. */ - public static String createSymlinkFile(@NonNull final Context context, String label, final String targetFilePath, final String destFilePath, + public static Error createSymlinkFile(String label, final String targetFilePath, final String destFilePath, final boolean allowDangling, final boolean overwrite, final boolean overwriteOnlyIfDestIsASymlink) { label = (label == null ? "" : label + " "); - if (targetFilePath == null || targetFilePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "target file path", "createSymlinkFile"); - if (destFilePath == null || destFilePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "destination file path", "createSymlinkFile"); + if (targetFilePath == null || targetFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "target file path", "createSymlinkFile"); + if (destFilePath == null || destFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "destination file path", "createSymlinkFile"); - String errmsg; + Error error; try { File destFile = new File(destFilePath); @@ -659,7 +614,7 @@ public class FileUtils { if (targetFileType == FileType.NO_EXIST) { // If dangling symlink should not be allowed, then return with error if (!allowDangling) - return context.getString(R.string.error_file_not_found_at_path, label + "symlink target file", targetFileAbsolutePath); + return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label + "symlink target file", targetFileAbsolutePath); } // If destination exists @@ -671,24 +626,24 @@ public class FileUtils { // If overwriteOnlyIfDestIsASymlink is enabled but destination file is not a symlink if (overwriteOnlyIfDestIsASymlink && destFileType != FileType.SYMLINK) - return context.getString(R.string.error_cannot_overwrite_a_non_symlink_file_type, label + " file", destFilePath, targetFilePath, destFileType.getName()); + return FileUtilsErrno.ERRNO_CANNOT_OVERWRITE_A_NON_SYMLINK_FILE_TYPE.getError(label + " file", destFilePath, targetFilePath, destFileType.getName()); // Delete the destination file - errmsg = deleteFile(context, label + "symlink destination", destFilePath, true); - if (errmsg != null) - return errmsg; + error = deleteFile(label + "symlink destination", destFilePath, true); + if (error != null) + return error; } else { // Create the destination file parent directory - errmsg = createParentDirectoryFile(context, label + "symlink destination file parent", destFilePath); - if (errmsg != null) - return errmsg; + error = createParentDirectoryFile(label + "symlink destination file parent", destFilePath); + if (error != null) + return error; } // create a symlink at destFilePath to targetFilePath Logger.logVerbose(LOG_TAG, "Creating " + label + "symlink file at path \"" + destFilePath + "\" to \"" + targetFilePath + "\""); Os.symlink(targetFilePath, destFilePath); } catch (Exception e) { - return context.getString(R.string.error_creating_symlink_file_failed_with_exception, label + "symlink file", destFilePath, targetFilePath, e.getMessage()); + return FileUtilsErrno.ERRNO_CREATING_SYMLINK_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "symlink file", destFilePath, targetFilePath, e.getMessage()); } return null; @@ -700,21 +655,20 @@ public class FileUtils { * Copy a regular file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its a regular * file, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to copy. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to copy. * @param destFilePath The {@code destination path} for file to copy. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to copied doesn't exist. - * @return Returns the {@code errmsg} if copy was not successful, otherwise {@code null}. + * @return Returns the {@code error} if copy was not successful, otherwise {@code null}. */ - public static String copyRegularFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error copyRegularFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, false, ignoreNonExistentSrcFile, FileType.REGULAR.getValue(), true, true); } @@ -723,21 +677,20 @@ public class FileUtils { * Move a regular file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its a regular * file, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to move. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to move. * @param destFilePath The {@code destination path} for file to move. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to moved doesn't exist. - * @return Returns the {@code errmsg} if move was not successful, otherwise {@code null}. + * @return Returns the {@code error} if move was not successful, otherwise {@code null}. */ - public static String moveRegularFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error moveRegularFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, true, ignoreNonExistentSrcFile, FileType.REGULAR.getValue(), true, true); } @@ -746,21 +699,20 @@ public class FileUtils { * Copy a directory file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its a directory * file, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to copy. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to copy. * @param destFilePath The {@code destination path} for file to copy. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to copied doesn't exist. - * @return Returns the {@code errmsg} if copy was not successful, otherwise {@code null}. + * @return Returns the {@code error} if copy was not successful, otherwise {@code null}. */ - public static String copyDirectoryFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error copyDirectoryFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, false, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(), true, true); } @@ -769,22 +721,21 @@ public class FileUtils { * Move a directory file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its a directory * file, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to move. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to move. * @param destFilePath The {@code destination path} for file to move. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to moved doesn't exist. - * @return Returns the {@code errmsg} if move was not successful, otherwise {@code null}. + * @return Returns the {@code error} if move was not successful, otherwise {@code null}. */ - public static String moveDirectoryFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, - true, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(), + public static Error moveDirectoryFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, + true, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(), true, true); } @@ -792,21 +743,20 @@ public class FileUtils { * Copy a symlink file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its a symlink * file, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to copy. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to copy. * @param destFilePath The {@code destination path} for file to copy. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to copied doesn't exist. - * @return Returns the {@code errmsg} if copy was not successful, otherwise {@code null}. + * @return Returns the {@code error} if copy was not successful, otherwise {@code null}. */ - public static String copySymlinkFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error copySymlinkFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, false, ignoreNonExistentSrcFile, FileType.SYMLINK.getValue(), true, true); } @@ -815,21 +765,20 @@ public class FileUtils { * Move a symlink file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its a symlink * file, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to move. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to move. * @param destFilePath The {@code destination path} for file to move. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to moved doesn't exist. - * @return Returns the {@code errmsg} if move was not successful, otherwise {@code null}. + * @return Returns the {@code error} if move was not successful, otherwise {@code null}. */ - public static String moveSymlinkFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error moveSymlinkFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, true, ignoreNonExistentSrcFile, FileType.SYMLINK.getValue(), true, true); } @@ -838,21 +787,20 @@ public class FileUtils { * Copy a file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its the same file * type as the source, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to copy. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to copy. * @param destFilePath The {@code destination path} for file to copy. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to copied doesn't exist. - * @return Returns the {@code errmsg} if copy was not successful, otherwise {@code null}. + * @return Returns the {@code error} if copy was not successful, otherwise {@code null}. */ - public static String copyFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error copyFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, false, ignoreNonExistentSrcFile, FileTypes.FILE_TYPE_NORMAL_FLAGS, true, true); } @@ -861,25 +809,24 @@ public class FileUtils { * Move a file from {@code sourceFilePath} to {@code destFilePath}. * * This function is a wrapper for - * {@link #copyOrMoveFile(Context, String, String, String, boolean, boolean, int, boolean, boolean)}. + * {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}. * * If destination file already exists, then it will be overwritten, but only if its the same file * type as the source, otherwise an error will be returned. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to move. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to move. * @param destFilePath The {@code destination path} for file to move. * @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an * error if source file to moved doesn't exist. - * @return Returns the {@code errmsg} if move was not successful, otherwise {@code null}. + * @return Returns the {@code error} if move was not successful, otherwise {@code null}. */ - public static String moveFile(@NonNull final Context context, final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { - return copyOrMoveFile(context, label, srcFilePath, destFilePath, + public static Error moveFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) { + return copyOrMoveFile(label, srcFilePath, destFilePath, true, ignoreNonExistentSrcFile, FileTypes.FILE_TYPE_NORMAL_FLAGS, true, true); } - + /** * Copy or move a file from {@code sourceFilePath} to {@code destFilePath}. * @@ -890,7 +837,6 @@ public class FileUtils { * then any symlink files found under the directory will be deleted, but not their targets when * deleting source after move and deleting destination before copy/move. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to copy or move. This can optionally be {@code null}. * @param srcFilePath The {@code source path} for file to copy or move. * @param destFilePath The {@code destination path} for file to copy or move. @@ -910,23 +856,20 @@ public class FileUtils { * @param overwriteOnlyIfDestSameFileTypeAsSrc The {@code boolean} that decides if overwrite should * only be done if destination file is also the same file * type as the source file. - * @return Returns the {@code errmsg} if copy or move was not successful, otherwise {@code null}. + * @return Returns the {@code error} if copy or move was not successful, otherwise {@code null}. */ - public static String copyOrMoveFile(@NonNull final Context context, String label, final String srcFilePath, final String destFilePath, + public static Error copyOrMoveFile(String label, final String srcFilePath, final String destFilePath, final boolean moveFile, final boolean ignoreNonExistentSrcFile, int allowedFileTypeFlags, final boolean overwrite, final boolean overwriteOnlyIfDestSameFileTypeAsSrc) { label = (label == null ? "" : label + " "); - if (srcFilePath == null || srcFilePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "source file path", "copyOrMoveFile"); - if (destFilePath == null || destFilePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "destination file path", "copyOrMoveFile"); + if (srcFilePath == null || srcFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "source file path", "copyOrMoveFile"); + if (destFilePath == null || destFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "destination file path", "copyOrMoveFile"); String mode = (moveFile ? "Moving" : "Copying"); String modePast = (moveFile ? "moved" : "copied"); - String errmsg; + Error error; - InputStream inputStream = null; - OutputStream outputStream = null; - try { Logger.logVerbose(LOG_TAG, mode + " " + label + "source file from \"" + srcFilePath + "\" to destination \"" + destFilePath + "\""); @@ -944,18 +887,18 @@ public class FileUtils { // If copy or move is to be ignored if source file is not found if (ignoreNonExistentSrcFile) return null; - // Else return with error + // Else return with error else - return context.getString(R.string.error_file_not_found_at_path, label + "source file", srcFilePath); + return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label + "source file", srcFilePath); } // If the file type of the source file does not exist in the allowedFileTypeFlags, then return with error if ((allowedFileTypeFlags & srcFileType.getValue()) <= 0) - return context.getString(R.string.error_file_not_an_allowed_file_type, label + "source file meant to be " + modePast, srcFilePath, FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags)); + return FileUtilsErrno.ERRNO_FILE_NOT_AN_ALLOWED_FILE_TYPE.getError(label + "source file meant to be " + modePast, srcFilePath, FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags)); // If source and destination file path are the same if (srcFileCanonicalPath.equals(destFileCanonicalPath)) - return context.getString(R.string.error_copying_or_moving_file_to_same_path, mode + " " + label + "source file", srcFilePath, destFilePath); + return FileUtilsErrno.ERRNO_COPYING_OR_MOVING_FILE_TO_SAME_PATH.getError(mode + " " + label + "source file", srcFilePath, destFilePath); // If destination exists if (destFileType != FileType.NO_EXIST) { @@ -966,12 +909,12 @@ public class FileUtils { // If overwriteOnlyIfDestSameFileTypeAsSrc is enabled but destination file does not match source file type if (overwriteOnlyIfDestSameFileTypeAsSrc && destFileType != srcFileType) - return context.getString(R.string.error_cannot_overwrite_a_different_file_type, label + "source file", mode.toLowerCase(), srcFilePath, destFilePath, destFileType.getName(), srcFileType.getName()); + return FileUtilsErrno.ERRNO_CANNOT_OVERWRITE_A_DIFFERENT_FILE_TYPE.getError(label + "source file", mode.toLowerCase(), srcFilePath, destFilePath, destFileType.getName(), srcFileType.getName()); // Delete the destination file - errmsg = deleteFile(context, label + "destination file", destFilePath, true); - if (errmsg != null) - return errmsg; + error = deleteFile(label + "destination file", destFilePath, true); + if (error != null) + return error; } @@ -990,7 +933,7 @@ public class FileUtils { // If destination directory is a subdirectory of the source directory // Copying is still allowed by copyDirectory() by excluding destination directory files if (srcFileType == FileType.DIRECTORY && destFileCanonicalPath.startsWith(srcFileCanonicalPath + File.separator)) - return context.getString(R.string.error_cannot_move_directory_to_sub_directory_of_itself, label + "source directory", srcFilePath, destFilePath); + return FileUtilsErrno.ERRNO_CANNOT_MOVE_DIRECTORY_TO_SUB_DIRECTORY_OF_ITSELF.getError(label + "source directory", srcFilePath, destFilePath); // If rename failed, then we copy Logger.logVerbose(LOG_TAG, "Renaming " + label + "source file to destination failed, attempting to copy."); @@ -1003,9 +946,9 @@ public class FileUtils { Logger.logVerbose(LOG_TAG, "Attempting to copy source to destination."); // Create the dest file parent directory - errmsg = createParentDirectoryFile(context, label + "dest file parent", destFilePath); - if (errmsg != null) - return errmsg; + error = createParentDirectoryFile(label + "dest file parent", destFilePath); + if (error != null) + return error; if (srcFileType == FileType.DIRECTORY) { // Will give runtime exceptions on android < 8 due to missing classes like java.nio.file.Path if org.apache.commons.io version > 2.5 @@ -1016,9 +959,9 @@ public class FileUtils { } else { // read the target for the source file and create a symlink at dest // source file metadata will be lost - errmsg = createSymlinkFile(context, label + "dest file", Os.readlink(srcFilePath), destFilePath); - if (errmsg != null) - return errmsg; + error = createSymlinkFile(label + "dest file", Os.readlink(srcFilePath), destFilePath); + if (error != null) + return error; } } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -1033,18 +976,15 @@ public class FileUtils { // If source file had to be moved if (moveFile) { // Delete the source file since copying would have succeeded - errmsg = deleteFile(context, label + "source file", srcFilePath, true); - if (errmsg != null) - return errmsg; + error = deleteFile(label + "source file", srcFilePath, true); + if (error != null) + return error; } Logger.logVerbose(LOG_TAG, mode + " successful."); } catch (Exception e) { - return context.getString(R.string.error_copying_or_moving_file_failed_with_exception, mode + " " + label + "file", srcFilePath, destFilePath, e.getMessage()); - } finally { - closeCloseable(inputStream); - closeCloseable(outputStream); + return FileUtilsErrno.ERRNO_COPYING_OR_MOVING_FILE_FAILED_WITH_EXCEPTION.getError(e, mode + " " + label + "file", srcFilePath, destFilePath, e.getMessage()); } return null; @@ -1055,65 +995,61 @@ public class FileUtils { /** * Delete regular file at path. * - * This function is a wrapper for {@link #deleteFile(Context, String, String, boolean, int)}. + * This function is a wrapper for {@link #deleteFile(String, String, boolean, int)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to delete. This can optionally be {@code null}. * @param filePath The {@code path} for file to delete. * @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an * error if file to deleted doesn't exist. - * @return Returns the {@code errmsg} if deletion was not successful, otherwise {@code null}. + * @return Returns the {@code error} if deletion was not successful, otherwise {@code null}. */ - public static String deleteRegularFile(@NonNull final Context context, String label, final String filePath, final boolean ignoreNonExistentFile) { - return deleteFile(context, label, filePath, ignoreNonExistentFile, FileType.REGULAR.getValue()); + public static Error deleteRegularFile(String label, final String filePath, final boolean ignoreNonExistentFile) { + return deleteFile(label, filePath, ignoreNonExistentFile, FileType.REGULAR.getValue()); } /** * Delete directory file at path. * - * This function is a wrapper for {@link #deleteFile(Context, String, String, boolean, int)}. + * This function is a wrapper for {@link #deleteFile(String, String, boolean, int)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to delete. This can optionally be {@code null}. * @param filePath The {@code path} for file to delete. * @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an * error if file to deleted doesn't exist. - * @return Returns the {@code errmsg} if deletion was not successful, otherwise {@code null}. + * @return Returns the {@code error} if deletion was not successful, otherwise {@code null}. */ - public static String deleteDirectoryFile(@NonNull final Context context, String label, final String filePath, final boolean ignoreNonExistentFile) { - return deleteFile(context, label, filePath, ignoreNonExistentFile, FileType.DIRECTORY.getValue()); + public static Error deleteDirectoryFile(String label, final String filePath, final boolean ignoreNonExistentFile) { + return deleteFile(label, filePath, ignoreNonExistentFile, FileType.DIRECTORY.getValue()); } /** * Delete symlink file at path. * - * This function is a wrapper for {@link #deleteFile(Context, String, String, boolean, int)}. + * This function is a wrapper for {@link #deleteFile(String, String, boolean, int)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to delete. This can optionally be {@code null}. * @param filePath The {@code path} for file to delete. * @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an * error if file to deleted doesn't exist. - * @return Returns the {@code errmsg} if deletion was not successful, otherwise {@code null}. + * @return Returns the {@code error} if deletion was not successful, otherwise {@code null}. */ - public static String deleteSymlinkFile(@NonNull final Context context, String label, final String filePath, final boolean ignoreNonExistentFile) { - return deleteFile(context, label, filePath, ignoreNonExistentFile, FileType.SYMLINK.getValue()); + public static Error deleteSymlinkFile(String label, final String filePath, final boolean ignoreNonExistentFile) { + return deleteFile(label, filePath, ignoreNonExistentFile, FileType.SYMLINK.getValue()); } /** * Delete regular, directory or symlink file at path. * - * This function is a wrapper for {@link #deleteFile(Context, String, String, boolean, int)}. + * This function is a wrapper for {@link #deleteFile(String, String, boolean, int)}. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to delete. This can optionally be {@code null}. * @param filePath The {@code path} for file to delete. * @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an * error if file to deleted doesn't exist. - * @return Returns the {@code errmsg} if deletion was not successful, otherwise {@code null}. + * @return Returns the {@code error} if deletion was not successful, otherwise {@code null}. */ - public static String deleteFile(@NonNull final Context context, String label, final String filePath, final boolean ignoreNonExistentFile) { - return deleteFile(context, label, filePath, ignoreNonExistentFile, FileTypes.FILE_TYPE_NORMAL_FLAGS); + public static Error deleteFile(String label, final String filePath, final boolean ignoreNonExistentFile) { + return deleteFile(label, filePath, ignoreNonExistentFile, FileTypes.FILE_TYPE_NORMAL_FLAGS); } /** @@ -1124,7 +1060,6 @@ public class FileUtils { * If the {@code filePath} is a canonical path to a directory, then any symlink files found under * the directory will be deleted, but not their targets. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to delete. This can optionally be {@code null}. * @param filePath The {@code path} for file to delete. * @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an @@ -1134,12 +1069,12 @@ public class FileUtils { * prevent accidental deletion of the wrong type of file, like a * directory instead of a regular file. You can pass * {@link FileTypes#FILE_TYPE_ANY_FLAGS} to allow deletion of any file type. - * @return Returns the {@code errmsg} if deletion was not successful, otherwise {@code null}. + * @return Returns the {@code error} if deletion was not successful, otherwise {@code null}. */ - public static String deleteFile(@NonNull final Context context, String label, final String filePath, final boolean ignoreNonExistentFile, int allowedFileTypeFlags) { + public static Error deleteFile(String label, final String filePath, final boolean ignoreNonExistentFile, int allowedFileTypeFlags) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "deleteFile"); - + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "deleteFile"); + try { Logger.logVerbose(LOG_TAG, "Deleting " + label + "file at path \"" + filePath + "\""); @@ -1151,25 +1086,34 @@ public class FileUtils { // If delete is to be ignored if file does not exist if (ignoreNonExistentFile) return null; - // Else return with error + // Else return with error else - return context.getString(R.string.error_file_not_found_at_path, label + "file meant to be deleted", filePath); + return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label + "file meant to be deleted", filePath); } // If the file type of the file does not exist in the allowedFileTypeFlags, then return with error if ((allowedFileTypeFlags & fileType.getValue()) <= 0) - return context.getString(R.string.error_file_not_an_allowed_file_type, label + "file meant to be deleted", filePath, FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags)); + return FileUtilsErrno.ERRNO_FILE_NOT_AN_ALLOWED_FILE_TYPE.getError(label + "file meant to be deleted", filePath, FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - /* Try to use {@link SecureDirectoryStream} if available for safer directory - deletion, it should be available for android >= 8.0 - * https://guava.dev/releases/24.1-jre/api/docs/com/google/common/io/MoreFiles.html#deleteRecursively-java.nio.file.Path-com.google.common.io.RecursiveDeleteOption...- - * https://github.com/google/guava/issues/365 - * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/sun/nio/fs/UnixSecureDirectoryStream.java - * - * MoreUtils is marked with the @Beta annotation so the API may be removed in - * future but has been there for a few years now - */ + /* + * Try to use {@link SecureDirectoryStream} if available for safer directory + * deletion, it should be available for android >= 8.0 + * https://guava.dev/releases/24.1-jre/api/docs/com/google/common/io/MoreFiles.html#deleteRecursively-java.nio.file.Path-com.google.common.io.RecursiveDeleteOption...- + * https://github.com/google/guava/issues/365 + * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/sun/nio/fs/UnixSecureDirectoryStream.java + * + * MoreUtils is marked with the @Beta annotation so the API may be removed in + * future but has been there for a few years now. + * + * If an exception is thrown, the exception message might not contain the full errors. + * Individual failures get added to suppressed throwables which can be extracted + * from the exception object by calling `Throwable[] getSuppressed()`. So just logging + * the exception message and stacktrace may not be enough, the suppressed throwables + * need to be logged as well, which the Logger class does if they are found in the + * exception added to the Error that's returned by this function. + * https://github.com/google/guava/blob/v30.1.1/guava/src/com/google/common/io/MoreFiles.java#L775 + */ //noinspection UnstableApiUsage com.google.common.io.MoreFiles.deleteRecursively(file.toPath(), RecursiveDeleteOption.ALLOW_INSECURE); } else { @@ -1186,10 +1130,10 @@ public class FileUtils { // If file still exists after deleting it fileType = getFileType(filePath, false); if (fileType != FileType.NO_EXIST) - return context.getString(R.string.error_file_still_exists_after_deleting, label + "file meant to be deleted", filePath); + return FileUtilsErrno.ERRNO_FILE_STILL_EXISTS_AFTER_DELETING.getError(label + "file meant to be deleted", filePath); } catch (Exception e) { - return context.getString(R.string.error_deleting_file_failed_with_exception, label + "file", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_DELETING_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage()); } return null; @@ -1202,14 +1146,13 @@ public class FileUtils { * it will be created automatically. * * This function is a wrapper for - * {@link #clearDirectory(Context, String, String)}. + * {@link #clearDirectory(String, String)}. * - * @param context The {@link Context} to get error string. * @param filePath The {@code path} for directory to clear. - * @return Returns the {@code errmsg} if clearing was not successful, otherwise {@code null}. + * @return Returns the {@code error} if clearing was not successful, otherwise {@code null}. */ - public static String clearDirectory(Context context, String filePath) { - return clearDirectory(context, null, filePath); + public static Error clearDirectory(String filePath) { + return clearDirectory(null, filePath); } /** @@ -1219,16 +1162,15 @@ public class FileUtils { * The {@code filePath} must be the canonical path to a directory since symlinks will not be followed. * Any symlink files found under the directory will be deleted, but not their targets. * - * @param context The {@link Context} to get error string. * @param label The optional label for directory to clear. This can optionally be {@code null}. * @param filePath The {@code path} for directory to clear. - * @return Returns the {@code errmsg} if clearing was not successful, otherwise {@code null}. + * @return Returns the {@code error} if clearing was not successful, otherwise {@code null}. */ - public static String clearDirectory(@NonNull final Context context, String label, final String filePath) { + public static Error clearDirectory(String label, final String filePath) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "clearDirectory"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "clearDirectory"); - String errmsg; + Error error; try { Logger.logVerbose(LOG_TAG, "Clearing " + label + "directory at path \"" + filePath + "\""); @@ -1238,12 +1180,14 @@ public class FileUtils { // If file exists but not a directory file if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) { - return context.getString(R.string.error_non_directory_file_found, label + "directory"); + return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory"); } // If directory exists, clear its contents if (fileType == FileType.DIRECTORY) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + /* If an exception is thrown, the exception message might not contain the full errors. + * Individual failures get added to suppressed throwables. */ //noinspection UnstableApiUsage com.google.common.io.MoreFiles.deleteDirectoryContents(file.toPath(), RecursiveDeleteOption.ALLOW_INSECURE); } else { @@ -1253,12 +1197,12 @@ public class FileUtils { } // Else create it else { - errmsg = createDirectoryFile(context, label, filePath); - if (errmsg != null) - return errmsg; + error = createDirectoryFile(label, filePath); + if (error != null) + return error; } } catch (Exception e) { - return context.getString(R.string.error_clearing_directory_failed_with_exception, label + "directory", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_CLEARING_DIRECTORY_FAILED_WITH_EXCEPTION.getError(e, label + "directory", filePath, e.getMessage()); } return null; @@ -1269,7 +1213,6 @@ public class FileUtils { /** * Read a {@link String} from file at path with a specific {@link Charset} into {@code dataString}. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to read. This can optionally be {@code null}. * @param filePath The {@code path} for file to read. * @param charset The {@link Charset} of the file. If this is {@code null}, @@ -1277,21 +1220,21 @@ public class FileUtils { * @param dataStringBuilder The {@code StringBuilder} to read data into. * @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an * error if file to read doesn't exist. - * @return Returns the {@code errmsg} if reading was not successful, otherwise {@code null}. + * @return Returns the {@code error} if reading was not successful, otherwise {@code null}. */ - public static String readStringFromFile(@NonNull final Context context, String label, final String filePath, Charset charset, @NonNull final StringBuilder dataStringBuilder, final boolean ignoreNonExistentFile) { + public static Error readStringFromFile(String label, final String filePath, Charset charset, @NonNull final StringBuilder dataStringBuilder, final boolean ignoreNonExistentFile) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "readStringFromFile"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "readStringFromFile"); Logger.logVerbose(LOG_TAG, "Reading string from " + label + "file at path \"" + filePath + "\""); - String errmsg; + Error error; FileType fileType = getFileType(filePath, false); // If file exists but not a regular file if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) { - return context.getString(R.string.error_non_regular_file_found, label + "file"); + return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file"); } // If file does not exist @@ -1299,17 +1242,17 @@ public class FileUtils { // If reading is to be ignored if file does not exist if (ignoreNonExistentFile) return null; - // Else return with error + // Else return with error else - return context.getString(R.string.error_file_not_found_at_path, label + "file meant to be read", filePath); + return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label + "file meant to be read", filePath); } if (charset == null) charset = Charset.defaultCharset(); // Check if charset is supported - errmsg = isCharsetSupported(context, charset); - if (errmsg != null) - return errmsg; + error = isCharsetSupported(charset); + if (error != null) + return error; FileInputStream fileInputStream = null; BufferedReader bufferedReader = null; @@ -1326,9 +1269,9 @@ public class FileUtils { dataStringBuilder.append(receiveString); } - Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("String", DataUtils.getTruncatedCommandOutput(dataStringBuilder.toString(), Logger.LOGGER_ENTRY_SIZE_LIMIT_IN_BYTES, true, false, true), "-")); + Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("String", DataUtils.getTruncatedCommandOutput(dataStringBuilder.toString(), Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD, true, false, true), "-")); } catch (Exception e) { - return context.getString(R.string.error_reading_string_to_file_failed_with_exception, label + "file", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_READING_STRING_TO_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage()); } finally { closeCloseable(fileInputStream); closeCloseable(bufferedReader); @@ -1340,40 +1283,39 @@ public class FileUtils { /** * Write the {@link String} {@code dataString} with a specific {@link Charset} to file at path. * - * @param context The {@link Context} to get error string. * @param label The optional label for file to write. This can optionally be {@code null}. * @param filePath The {@code path} for file to write. * @param charset The {@link Charset} of the {@code dataString}. If this is {@code null}, * then default {@link Charset} will be used. * @param append The {@code boolean} that decides if file should be appended to or not. - * @return Returns the {@code errmsg} if writing was not successful, otherwise {@code null}. + * @return Returns the {@code error} if writing was not successful, otherwise {@code null}. */ - public static String writeStringToFile(@NonNull final Context context, String label, final String filePath, Charset charset, final String dataString, final boolean append) { + public static Error writeStringToFile(String label, final String filePath, Charset charset, final String dataString, final boolean append) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "writeStringToFile"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "writeStringToFile"); - Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("Writing string to " + label + "file at path \"" + filePath + "\"", DataUtils.getTruncatedCommandOutput(dataString, Logger.LOGGER_ENTRY_SIZE_LIMIT_IN_BYTES, true, false, true), "-")); + Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("Writing string to " + label + "file at path \"" + filePath + "\"", DataUtils.getTruncatedCommandOutput(dataString, Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD, true, false, true), "-")); - String errmsg; + Error error; FileType fileType = getFileType(filePath, false); // If file exists but not a regular file if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) { - return context.getString(R.string.error_non_regular_file_found, label + "file"); + return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file"); } // Create the file parent directory - errmsg = createParentDirectoryFile(context, label + "file parent", filePath); - if (errmsg != null) - return errmsg; + error = createParentDirectoryFile(label + "file parent", filePath); + if (error != null) + return error; if (charset == null) charset = Charset.defaultCharset(); // Check if charset is supported - errmsg = isCharsetSupported(context, charset); - if (errmsg != null) - return errmsg; + error = isCharsetSupported(charset); + if (error != null) + return error; FileOutputStream fileOutputStream = null; BufferedWriter bufferedWriter = null; @@ -1385,7 +1327,7 @@ public class FileUtils { bufferedWriter.write(dataString); bufferedWriter.flush(); } catch (Exception e) { - return context.getString(R.string.error_writing_string_to_file_failed_with_exception, label + "file", filePath, e.getMessage()); + return FileUtilsErrno.ERRNO_WRITING_STRING_TO_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage()); } finally { closeCloseable(fileOutputStream); closeCloseable(bufferedWriter); @@ -1399,19 +1341,18 @@ public class FileUtils { /** * Check if a specific {@link Charset} is supported. * - * @param context The {@link Context} to get error string. * @param charset The {@link Charset} to check. - * @return Returns the {@code errmsg} if charset is not supported or failed to check it, otherwise {@code null}. + * @return Returns the {@code error} if charset is not supported or failed to check it, otherwise {@code null}. */ - public static String isCharsetSupported(@NonNull final Context context, final Charset charset) { - if (charset == null) return context.getString(R.string.error_null_or_empty_parameter, "charset", "isCharsetSupported"); + public static Error isCharsetSupported(final Charset charset) { + if (charset == null) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError("charset", "isCharsetSupported"); try { if (!Charset.isSupported(charset.name())) { - return context.getString(R.string.error_unsupported_charset, charset.name()); + return FileUtilsErrno.ERRNO_UNSUPPORTED_CHARSET.getError(charset.name()); } } catch (Exception e) { - return context.getString(R.string.error_checking_if_charset_supported_failed, charset.name(), e.getMessage()); + return FileUtilsErrno.ERRNO_CHECKING_IF_CHARSET_SUPPORTED_FAILED.getError(e, charset.name(), e.getMessage()); } return null; @@ -1559,52 +1500,50 @@ public class FileUtils { /** * Checking missing permissions for file at path. * - * @param context The {@link Context} to get error string. * @param filePath The {@code path} for file to check permissions for. * @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order. * @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission * error is to be ignored. - * @return Returns the {@code errmsg} if validating permissions failed, otherwise {@code null}. + * @return Returns the {@code error} if validating permissions failed, otherwise {@code null}. */ - public static String checkMissingFilePermissions(@NonNull final Context context, final String filePath, final String permissionsToCheck, final boolean ignoreIfNotExecutable) { - return checkMissingFilePermissions(context, null, filePath, permissionsToCheck, ignoreIfNotExecutable); + public static Error checkMissingFilePermissions(final String filePath, final String permissionsToCheck, final boolean ignoreIfNotExecutable) { + return checkMissingFilePermissions(null, filePath, permissionsToCheck, ignoreIfNotExecutable); } /** * Checking missing permissions for file at path. * - * @param context The {@link Context} to get error string. * @param label The optional label for the file. This can optionally be {@code null}. * @param filePath The {@code path} for file to check permissions for. * @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order. * @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission * error is to be ignored. - * @return Returns the {@code errmsg} if validating permissions failed, otherwise {@code null}. + * @return Returns the {@code error} if validating permissions failed, otherwise {@code null}. */ - public static String checkMissingFilePermissions(@NonNull final Context context, String label, final String filePath, final String permissionsToCheck, final boolean ignoreIfNotExecutable) { + public static Error checkMissingFilePermissions(String label, final String filePath, final String permissionsToCheck, final boolean ignoreIfNotExecutable) { label = (label == null ? "" : label + " "); - if (filePath == null || filePath.isEmpty()) return context.getString(R.string.error_null_or_empty_parameter, label + "file path", "checkMissingFilePermissions"); + if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "checkMissingFilePermissions"); if (!isValidPermissionString(permissionsToCheck)) { Logger.logError(LOG_TAG, "Invalid permissionsToCheck passed to checkMissingFilePermissions: \"" + permissionsToCheck + "\""); - return context.getString(R.string.error_invalid_file_permissions_string_to_check); + return FileUtilsErrno.ERRNO_INVALID_FILE_PERMISSIONS_STRING_TO_CHECK.getError(); } File file = new File(filePath); // If file is not readable if (permissionsToCheck.contains("r") && !file.canRead()) { - return context.getString(R.string.error_file_not_readable, label + "file"); + return FileUtilsErrno.ERRNO_FILE_NOT_READABLE.getError(label + "file"); } // If file is not writable if (permissionsToCheck.contains("w") && !file.canWrite()) { - return context.getString(R.string.error_file_not_writable, label + "file"); + return FileUtilsErrno.ERRNO_FILE_NOT_WRITABLE.getError(label + "file"); } // If file is not executable // This canExecute() will give "avc: granted { execute }" warnings for target sdk 29 else if (permissionsToCheck.contains("x") && !file.canExecute() && !ignoreIfNotExecutable) { - return context.getString(R.string.error_file_not_executable, label + "file"); + return FileUtilsErrno.ERRNO_FILE_NOT_EXECUTABLE.getError(label + "file"); } return null; @@ -1623,5 +1562,5 @@ public class FileUtils { if (string == null || string.isEmpty()) return false; return Pattern.compile("^([r-])[w-][x-]$", 0).matcher(string).matches(); } - + } diff --git a/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java b/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java new file mode 100644 index 00000000..7748a6bc --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java @@ -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); + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/file/filesystem/FileTypes.java b/termux-shared/src/main/java/com/termux/shared/file/filesystem/FileTypes.java index c6a78d7a..cf599cd2 100644 --- a/termux-shared/src/main/java/com/termux/shared/file/filesystem/FileTypes.java +++ b/termux-shared/src/main/java/com/termux/shared/file/filesystem/FileTypes.java @@ -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; } diff --git a/termux-shared/src/main/java/com/termux/shared/file/tests/FileUtilsTests.java b/termux-shared/src/main/java/com/termux/shared/file/tests/FileUtilsTests.java index 47f4e86b..82205317 100644 --- a/termux-shared/src/main/java/com/termux/shared/file/tests/FileUtilsTests.java +++ b/termux-shared/src/main/java/com/termux/shared/file/tests/FileUtilsTests.java @@ -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 + "\""); diff --git a/termux-shared/src/main/java/com/termux/shared/models/errors/Errno.java b/termux-shared/src/main/java/com/termux/shared/models/errors/Errno.java new file mode 100644 index 00000000..0c21f02b --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/models/errors/Errno.java @@ -0,0 +1,89 @@ +package com.termux.shared.models.errors; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import com.termux.shared.logger.Logger; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** The {@link Class} that defines error messages and codes. */ +public class Errno { + + public static final String TYPE = "Error"; + + + public static final Errno ERRNO_SUCCESS = new Errno(TYPE, Activity.RESULT_OK, "Success"); + public static final Errno ERRNO_MINOR_FAILURES = new Errno(TYPE, Activity.RESULT_FIRST_USER, "Minor failure"); + public static final Errno ERRNO_FAILED = new Errno(TYPE, Activity.RESULT_FIRST_USER + 1, "Failed"); + public static final Errno ERRNO_CANCELED = new Errno(TYPE, Activity.RESULT_FIRST_USER + 2, "Cancelled"); + + /** The errno type. */ + protected String type; + /** The errno code. */ + protected final int code; + /** The errno message. */ + protected final String message; + + private static final String LOG_TAG = "Errno"; + + + public Errno(final String type, final int code, final String message) { + this.type = type; + this.code = code; + this.message = message; + } + + @NonNull + @Override + public String toString() { + return "type=" + type + ", code=" + code + ", message=\"" + message + "\""; + } + + + public String getType() { + return type; + } + + public String getMessage() { + return message; + } + + public int getCode() { + return code; + } + + + + public Error getError() { + return new Error(getType(), getCode(), getMessage()); + } + + public Error getError(Object... args) { + try { + return new Error(getType(), getCode(), String.format(getMessage(), args)); + } catch (Exception e) { + Logger.logWarn(LOG_TAG, "Exception raised while calling String.format() for error message of errno " + this + " with args" + Arrays.toString(args) + "\n" + e.getMessage()); + // Return unformatted message as a backup + return new Error(getType(), getCode(), getMessage() + ": " + Arrays.toString(args)); + } + } + + public Error getError(Throwable throwable, Object... args) { + return getError(Collections.singletonList(throwable), args); + } + + public Error getError(List throwablesList, Object... args) { + try { + return new Error(getType(), getCode(), String.format(getMessage(), args), throwablesList); + } catch (Exception e) { + Logger.logWarn(LOG_TAG, "Exception raised while calling String.format() for error message of errno " + this + " with args" + Arrays.toString(args) + "\n" + e.getMessage()); + // Return unformatted message as a backup + return new Error(getType(), getCode(), getMessage() + ": " + Arrays.toString(args), throwablesList); + } + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/models/errors/Error.java b/termux-shared/src/main/java/com/termux/shared/models/errors/Error.java new file mode 100644 index 00000000..b9dba33f --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/models/errors/Error.java @@ -0,0 +1,249 @@ +package com.termux.shared.models.errors; + +import androidx.annotation.NonNull; + +import com.termux.shared.logger.Logger; +import com.termux.shared.markdown.MarkdownUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Error implements Serializable { + + /** The error type. */ + private String type; + /** The error code. */ + private int code; + /** The error message. */ + private String message; + /** The error exceptions. */ + private List throwablesList = new ArrayList<>(); + + private static final String LOG_TAG = "Error"; + + + public Error() { + InitError(null, null, null, null); + } + + public Error(String type, Integer code, String message, List throwablesList) { + InitError(type, code, message, throwablesList); + } + + public Error(String type, Integer code, String message, Throwable throwable) { + InitError(type, code, message, Collections.singletonList(throwable)); + } + + public Error(String type, Integer code, String message) { + InitError(type, code, message, null); + } + + public Error(Integer code, String message, List throwablesList) { + InitError(null, code, message, throwablesList); + } + + public Error(Integer code, String message, Throwable throwable) { + InitError(null, code, message, Collections.singletonList(throwable)); + } + + public Error(Integer code, String message) { + InitError(null, code, message, null); + } + + public Error(String message, Throwable throwable) { + InitError(null, null, message, Collections.singletonList(throwable)); + } + + public Error(String message, List throwablesList) { + InitError(null, null, message, throwablesList); + } + + public Error(String message) { + InitError(null, null, message, null); + } + + private void InitError(String type, Integer code, String message, List throwablesList) { + if (type != null && !type.isEmpty()) + this.type = type; + else + this.type = Errno.TYPE; + + if (code != null && code > Errno.ERRNO_SUCCESS.getCode()) + this.code = code; + else + this.code = Errno.ERRNO_SUCCESS.getCode(); + + this.message = message; + this.throwablesList = throwablesList; + } + + + public String getType() { + return type; + } + + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public void prependMessage(String message) { + if (message != null && isStateFailed()) + this.message = message + this.message; + } + + public void appendMessage(String message) { + if (message != null && isStateFailed()) + this.message = this.message + message; + } + + public List getThrowablesList() { + return Collections.unmodifiableList(throwablesList); + } + + + public synchronized boolean setStateFailed(@NonNull Error error) { + return setStateFailed(error.getType(), error.getCode(), error.getMessage(), null); + } + + public synchronized boolean setStateFailed(@NonNull Error error, Throwable throwable) { + return setStateFailed(error.getType(), error.getCode(), error.getMessage(), Collections.singletonList(throwable)); + } + public synchronized boolean setStateFailed(@NonNull Error error, List throwablesList) { + return setStateFailed(error.getType(), error.getCode(), error.getMessage(), throwablesList); + } + + public synchronized boolean setStateFailed(int code, String message) { + return setStateFailed(this.type, code, message, null); + } + + public synchronized boolean setStateFailed(int code, String message, Throwable throwable) { + return setStateFailed(this.type, code, message, Collections.singletonList(throwable)); + } + + public synchronized boolean setStateFailed(int code, String message, List throwablesList) { + return setStateFailed(this.type, code, message, throwablesList); + } + + public synchronized boolean setStateFailed(String type, int code, String message, List throwablesList) { + this.message = message; + this.throwablesList = throwablesList; + + if (type != null && !type.isEmpty()) + this.type = type; + + if (code > Errno.ERRNO_SUCCESS.getCode()) { + this.code = code; + return true; + } else { + Logger.logWarn(LOG_TAG, "Ignoring invalid error code value \"" + code + "\". Force setting it to RESULT_CODE_FAILED \"" + Errno.ERRNO_FAILED.getCode() + "\""); + this.code = Errno.ERRNO_FAILED.getCode(); + return false; + } + } + + public boolean isStateFailed() { + return code > Errno.ERRNO_SUCCESS.getCode(); + } + + + @NonNull + @Override + public String toString() { + return getErrorLogString(this); + } + + /** + * Get a log friendly {@link String} for {@link Error} error parameters. + * + * @param error The {@link Error} to convert. + * @return Returns the log friendly {@link String}. + */ + public static String getErrorLogString(final Error error) { + if (error == null) return "null"; + + StringBuilder logString = new StringBuilder(); + + logString.append(error.getCodeString()); + logString.append("\n").append(error.getTypeAndMessageLogString()); + if (error.throwablesList != null) + logString.append("\n").append(error.geStackTracesLogString()); + + return logString.toString(); + } + + /** + * Get a minimal log friendly {@link String} for {@link Error} error parameters. + * + * @param error The {@link Error} to convert. + * @return Returns the log friendly {@link String}. + */ + public static String getMinimalErrorLogString(final Error error) { + if (error == null) return "null"; + + StringBuilder logString = new StringBuilder(); + + logString.append(error.getCodeString()); + logString.append(error.getTypeAndMessageLogString()); + + return logString.toString(); + } + + /** + * Get a minimal {@link String} for {@link Error} error parameters. + * + * @param error The {@link Error} to convert. + * @return Returns the {@link String}. + */ + public static String getMinimalErrorString(final Error error) { + if (error == null) return "null"; + + StringBuilder logString = new StringBuilder(); + + logString.append("(").append(error.getCode()).append(") "); + logString.append(error.getType()).append(": ").append(error.getMessage()); + + return logString.toString(); + } + + /** + * Get a markdown {@link String} for {@link Error}. + * + * @param error The {@link Error} to convert. + * @return Returns the markdown {@link String}. + */ + public static String getErrorMarkdownString(final Error error) { + if (error == null) return "null"; + + StringBuilder markdownString = new StringBuilder(); + + markdownString.append(MarkdownUtils.getSingleLineMarkdownStringEntry("Error Code", error.getCode(), "-")); + markdownString.append("\n").append(MarkdownUtils.getMultiLineMarkdownStringEntry((Errno.TYPE.equals(error.getType()) ? "Error Message" : "Error Message (" + error.getType() + ")"), error.message, "-")); + markdownString.append("\n\n").append(error.geStackTracesMarkdownString()); + + return markdownString.toString(); + } + + + public String getCodeString() { + return Logger.getSingleLineLogStringEntry("Error Code", code, "-"); + } + + public String getTypeAndMessageLogString() { + return Logger.getMultiLineLogStringEntry(Errno.TYPE.equals(type) ? "Error Message" : "Error Message (" + type + ")", message, "-"); + } + + public String geStackTracesLogString() { + return Logger.getStackTracesString("StackTraces:", Logger.getStackTracesStringArray(throwablesList)); + } + + public String geStackTracesMarkdownString() { + return Logger.getStackTracesMarkdownString("StackTraces", Logger.getStackTracesStringArray(throwablesList)); + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/models/errors/FileUtilsErrno.java b/termux-shared/src/main/java/com/termux/shared/models/errors/FileUtilsErrno.java new file mode 100644 index 00000000..2935d7a4 --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/models/errors/FileUtilsErrno.java @@ -0,0 +1,81 @@ +package com.termux.shared.models.errors; + +/** The {@link Class} that defines FileUtils error messages and codes. */ +public class FileUtilsErrno extends Errno { + + public static final String TYPE = "FileUtils Error"; + + + /* Errors for null or empty paths (100-150) */ + public static final Errno ERRNO_EXECUTABLE_REQUIRED = new Errno(TYPE, 100, "Executable required."); + public static final Errno ERRNO_NULL_OR_EMPTY_REGULAR_FILE_PATH = new Errno(TYPE, 101, "The regular file path is null or empty."); + public static final Errno ERRNO_NULL_OR_EMPTY_REGULAR_FILE = new Errno(TYPE, 102, "The regular file is null or empty."); + public static final Errno ERRNO_NULL_OR_EMPTY_EXECUTABLE_FILE_PATH = new Errno(TYPE, 103, "The executable file path is null or empty."); + public static final Errno ERRNO_NULL_OR_EMPTY_EXECUTABLE_FILE = new Errno(TYPE, 104, "The executable file is null or empty."); + public static final Errno ERRNO_NULL_OR_EMPTY_DIRECTORY_FILE_PATH = new Errno(TYPE, 105, "The directory file path is null or empty."); + public static final Errno ERRNO_NULL_OR_EMPTY_DIRECTORY_FILE = new Errno(TYPE, 106, "The directory file is null or empty."); + + + + /* Errors for invalid or not found files at path (150-200) */ + public static final Errno ERRNO_FILE_NOT_FOUND_AT_PATH = new Errno(TYPE, 150, "The %1$s is not found at path \"%2$s\"."); + + public static final Errno ERRNO_NO_REGULAR_FILE_FOUND = new Errno(TYPE, 151, "Regular file not found at %1$s path."); + public static final Errno ERRNO_NOT_A_REGULAR_FILE = new Errno(TYPE, 152, "The %1$s at path \"%2$s\" is not a regular file."); + + public static final Errno ERRNO_NON_REGULAR_FILE_FOUND = new Errno(TYPE, 153, "Non-regular file found at %1$s path."); + public static final Errno ERRNO_NON_DIRECTORY_FILE_FOUND = new Errno(TYPE, 154, "Non-directory file found at %1$s path."); + public static final Errno ERRNO_NON_SYMLINK_FILE_FOUND = new Errno(TYPE, 155, "Non-symlink file found at %1$s path."); + + public static final Errno ERRNO_FILE_NOT_AN_ALLOWED_FILE_TYPE = new Errno(TYPE, 156, "The %1$s found at path \"%2$s\" is not one of allowed file types \"%3$s\"."); + + public static final Errno ERRNO_VALIDATE_FILE_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION = new Errno(TYPE, 157, "Validating file existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s"); + public static final Errno ERRNO_VALIDATE_DIRECTORY_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION = new Errno(TYPE, 158, "Validating directory existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s"); + + + + /* Errors for file creation (200-250) */ + public static final Errno ERRNO_CREATING_FILE_FAILED = new Errno(TYPE, 200, "Creating %1$s at path \"%2$s\" failed."); + public static final Errno ERRNO_CREATING_FILE_FAILED_WITH_EXCEPTION = new Errno(TYPE, 201, "Creating %1$s at path \"%2$s\" failed.\nException: %3$s"); + + public static final Errno ERRNO_CANNOT_OVERWRITE_A_NON_SYMLINK_FILE_TYPE = new Errno(TYPE, 202, "Cannot overwrite %1$s while creating symlink at \"%2$s\" to \"%3$s\" since destination file type \"%4$s\" is not a symlink."); + public static final Errno ERRNO_CREATING_SYMLINK_FILE_FAILED_WITH_EXCEPTION = new Errno(TYPE, 203, "Creating %1$s at path \"%2$s\" to \"%3$s\" failed.\nException: %4$s"); + + + + /* Errors for file copying and moving (250-300) */ + public static final Errno ERRNO_COPYING_OR_MOVING_FILE_FAILED_WITH_EXCEPTION = new Errno(TYPE, 250, "%1$s from \"%2$s\" to \"%3$s\" failed.\nException: %4$s"); + public static final Errno ERRNO_COPYING_OR_MOVING_FILE_TO_SAME_PATH = new Errno(TYPE, 251, "%1$s from \"%2$s\" to \"%3$s\" cannot be done since they point to the same path."); + public static final Errno ERRNO_CANNOT_OVERWRITE_A_DIFFERENT_FILE_TYPE = new Errno(TYPE, 252, "Cannot overwrite %1$s while %2$s it from \"%3$s\" to \"%4$s\" since destination file type \"%5$s\" is different from source file type \"%6$s\"."); + public static final Errno ERRNO_CANNOT_MOVE_DIRECTORY_TO_SUB_DIRECTORY_OF_ITSELF = new Errno(TYPE, 253, "Cannot move %1$s from \"%2$s\" to \"%3$s\" since destination is a subdirectory of the source."); + + + + /* Errors for file deletion (300-350) */ + public static final Errno ERRNO_DELETING_FILE_FAILED = new Errno(TYPE, 300, "Deleting %1$s at path \"%2$s\" failed."); + public static final Errno ERRNO_DELETING_FILE_FAILED_WITH_EXCEPTION = new Errno(TYPE, 301, "Deleting %1$s at path \"%2$s\" failed.\nException: %3$s"); + public static final Errno ERRNO_CLEARING_DIRECTORY_FAILED_WITH_EXCEPTION = new Errno(TYPE, 302, "Clearing %1$s at path \"%2$s\" failed.\nException: %3$s"); + public static final Errno ERRNO_FILE_STILL_EXISTS_AFTER_DELETING = new Errno(TYPE, 303, "The %1$s still exists after deleting it from \"%2$s\"."); + + + + /* Errors for file reading and writing (350-400) */ + public static final Errno ERRNO_READING_STRING_TO_FILE_FAILED_WITH_EXCEPTION = new Errno(TYPE, 350, "Reading string from %1$s at path \"%2$s\" failed.\nException: %3$s"); + public static final Errno ERRNO_WRITING_STRING_TO_FILE_FAILED_WITH_EXCEPTION = new Errno(TYPE, 351, "Writing string to %1$s at path \"%2$s\" failed.\nException: %3$s"); + public static final Errno ERRNO_UNSUPPORTED_CHARSET = new Errno(TYPE, 352, "Unsupported charset \"%1$s\""); + public static final Errno ERRNO_CHECKING_IF_CHARSET_SUPPORTED_FAILED = new Errno(TYPE, 353, "Checking if charset \"%1$s\" is supported failed.\nException: %2$s"); + + + + /* Errors for invalid file permissions (400-450) */ + public static final Errno ERRNO_INVALID_FILE_PERMISSIONS_STRING_TO_CHECK = new Errno(TYPE, 400, "The file permission string to check is invalid."); + public static final Errno ERRNO_FILE_NOT_READABLE = new Errno(TYPE, 401, "The %1$s at path is not readable. Permission Denied."); + public static final Errno ERRNO_FILE_NOT_WRITABLE = new Errno(TYPE, 402, "The %1$s at path is not writable. Permission Denied."); + public static final Errno ERRNO_FILE_NOT_EXECUTABLE = new Errno(TYPE, 403, "The %1$s at path is not executable. Permission Denied."); + + + FileUtilsErrno(final String type, final int code, final String message) { + super(type, code, message); + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/models/errors/FunctionErrno.java b/termux-shared/src/main/java/com/termux/shared/models/errors/FunctionErrno.java new file mode 100644 index 00000000..8d099b34 --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/models/errors/FunctionErrno.java @@ -0,0 +1,20 @@ +package com.termux.shared.models.errors; + +/** The {@link Class} that defines function error messages and codes. */ +public class FunctionErrno extends Errno { + + public static final String TYPE = "Function Error"; + + + /* Errors for null or empty parameters (100-150) */ + public static final Errno ERRNO_NULL_OR_EMPTY_PARAMETER = new Errno(TYPE, 100, "The %1$s parameter passed to \"%2$s\" is null or empty."); + public static final Errno ERRNO_NULL_OR_EMPTY_PARAMETERS = new Errno(TYPE, 101, "The %1$s parameters passed to \"%2$s\" are null or empty."); + public static final Errno ERRNO_UNSET_PARAMETER = new Errno(TYPE, 102, "The %1$s parameter passed to \"%2$s\" must be set."); + public static final Errno ERRNO_UNSET_PARAMETERS = new Errno(TYPE, 103, "The %1$s parameters passed to \"%2$s\" must be set."); + + + FunctionErrno(final String type, final int code, final String message) { + super(type, code, message); + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java b/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java index fe1efc8d..89c66f3c 100644 --- a/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java @@ -4,6 +4,7 @@ import android.content.Context; import androidx.annotation.NonNull; +import com.termux.shared.models.errors.Error; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.file.FileUtils; import com.termux.shared.logger.Logger; @@ -150,14 +151,14 @@ public class ShellUtils { return (lastSlash == -1) ? executable : executable.substring(lastSlash + 1); } - public static void clearTermuxTMPDIR(Context context, boolean onlyIfExists) { + public static void clearTermuxTMPDIR(boolean onlyIfExists) { if(onlyIfExists && !FileUtils.directoryFileExists(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, false)) return; - String errmsg; - errmsg = FileUtils.clearDirectory(context, "$TMPDIR", FileUtils.getCanonicalPath(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, null, false)); - if (errmsg != null) { - Logger.logError(errmsg); + Error error; + error = FileUtils.clearDirectory("$TMPDIR", FileUtils.getCanonicalPath(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, null)); + if (error != null) { + Logger.logErrorExtended(error.toString()); } } diff --git a/termux-shared/src/main/res/values/strings.xml b/termux-shared/src/main/res/values/strings.xml index b1049f8e..21a85046 100644 --- a/termux-shared/src/main/res/values/strings.xml +++ b/termux-shared/src/main/res/values/strings.xml @@ -13,52 +13,7 @@ - - Executable required. - The %1$s is to \"%2$s\" null or empty. - The regular file path is null or empty. - The regular file is null or empty. - The executable file path is null or empty. - The executable file is null or empty. - The directory file path is null or empty. - The directory file is null or empty. - - The %1$s is not found at path \"%2$s\". - Regular file not found at %1$s path. - The %1$s at path \"%2$s\" is not a regular file. - Non-regular file found at %1$s path. - Non-directory file found at %1$s path. - Non-symlink file found at %1$s path. - The %1$s found at path \"%2$s\" is not one of allowed file types \"%3$s\". - - Validating file existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s - Validating directory existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s - - Creating %1$s at path \"%2$s\" failed. - Creating %1$s at path \"%2$s\" failed.\nException: %3$s - - Cannot overwrite %1$s while creating symlink at \"%2$s\" to \"%3$s\" since destination file type \"%4$s\" is not a symlink. - Creating %1$s at path \"%2$s\" to \"%3$s\" failed.\nException: %4$s - - %1$s from \"%2$s\" to \"%3$s\" failed.\nException: %4$s - %1$s from \"%2$s\" to \"%3$s\" cannot be done since they point to the same path. - Cannot overwrite %1$s while %2$s it from \"%3$s\" to \"%4$s\" since destination file type \"%5$s\" is different from source file type \"%6$s\". - Cannot move %1$s from \"%2$s\" to \"%3$s\" since destination is a subdirectory of the source. - - The %1$s still exists after deleting it from \"%2$s\". - Deleting %1$s at path \"%2$s\" failed. - Deleting %1$s at path \"%2$s\" failed.\nException: %3$s - Clearing %1$s at path \"%2$s\" failed.\nException: %3$s - - Reading string from %1$s at path \"%2$s\" failed.\nException: %3$s - Writing string to %1$s at path \"%2$s\" failed.\nException: %3$s - Unsupported charset \"%1$s\" - Checking if charset \"%1$s\" is suppoted failed.\nException: %2$s - - The file permission string to check is invalid. - The %1$s at path is not readable. Permission Denied. - The %1$s at path is not writable. Permission Denied. - The %1$s at path is not executable. Permission Denied. + %1$s Directory Absolute Path: \"%2$s\"