/*
* Copyright (C) 2012 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cyanogenmod.filemanager.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
import com.cyanogenmod.filemanager.commands.java.Program;
import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
import com.cyanogenmod.filemanager.commands.shell.ResolveLinkCommand;
import com.cyanogenmod.filemanager.console.CancelledOperationException;
import com.cyanogenmod.filemanager.console.CommandNotFoundException;
import com.cyanogenmod.filemanager.console.Console;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ExecutionException;
import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
import com.cyanogenmod.filemanager.console.OperationTimeoutException;
import com.cyanogenmod.filemanager.console.java.JavaConsole;
import com.cyanogenmod.filemanager.model.AID;
import com.cyanogenmod.filemanager.model.BlockDevice;
import com.cyanogenmod.filemanager.model.CharacterDevice;
import com.cyanogenmod.filemanager.model.Directory;
import com.cyanogenmod.filemanager.model.DomainSocket;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.Group;
import com.cyanogenmod.filemanager.model.Identity;
import com.cyanogenmod.filemanager.model.NamedPipe;
import com.cyanogenmod.filemanager.model.ParentDirectory;
import com.cyanogenmod.filemanager.model.Permissions;
import com.cyanogenmod.filemanager.model.RegularFile;
import com.cyanogenmod.filemanager.model.Symlink;
import com.cyanogenmod.filemanager.model.SystemFile;
import com.cyanogenmod.filemanager.model.User;
import com.cyanogenmod.filemanager.preferences.DisplayRestrictions;
import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
import com.cyanogenmod.filemanager.preferences.FileTimeFormatMode;
import com.cyanogenmod.filemanager.preferences.NavigationSortMode;
import com.cyanogenmod.filemanager.preferences.ObjectIdentifier;
import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier;
import com.cyanogenmod.filemanager.preferences.Preferences;
import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* A helper class with useful methods for deal with files.
*/
public final class FileHelper {
private static final String TAG = "FileHelper"; //$NON-NLS-1$
// Scheme for file and directory picking
public static final String FILE_URI_SCHEME = "file"; //$NON-NLS-1$
public static final String FOLDER_URI_SCHEME = "folder"; //$NON-NLS-1$
public static final String DIRECTORY_URI_SCHEME = "directory"; //$NON-NLS-1$
/**
* Special extension for compressed tar files
*/
private static final String[] COMPRESSED_TAR =
{
"tar.gz", "tar.bz2", "tar.lzma" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
};
/**
* The root directory.
* @hide
*/
public static final String ROOT_DIRECTORY = "/"; //$NON-NLS-1$
/**
* The parent directory string.
* @hide
*/
public static final String PARENT_DIRECTORY = ".."; //$NON-NLS-1$
/**
* The current directory string.
* @hide
*/
public static final String CURRENT_DIRECTORY = "."; //$NON-NLS-1$
/**
* The administrator user.
* @hide
*/
public static final String USER_ROOT = "root"; //$NON-NLS-1$
/**
* The newline string.
* @hide
*/
public static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
/**
* The size of chunks that we will copy with nio during a copy operation.
*
* Set to 1MiB.
*/
public final static long NIO_COPY_CHUNK_SIZE = 1024000L;
// The date/time formats objects
/**
* @hide
*/
public final static Object DATETIME_SYNC = new Object();
/**
* @hide
*/
public static boolean sReloadDateTimeFormats = true;
private static String sDateTimeFormatOrder = null;
private static FileTimeFormatMode sFiletimeFormatMode = null;
private static DateFormat sDateFormat = null;
private static DateFormat sTimeFormat = null;
/**
* Constructor of FileHelper.
*/
private FileHelper() {
super();
}
/**
* Method that check if a file is a symbolic link.
*
* @param file File to check
* @return boolean If file is a symbolic link
* @throws IOException If real file couldn't be checked
*/
public static boolean isSymlink(File file) throws IOException {
return file.getAbsolutePath().compareTo(file.getCanonicalPath()) != 0;
}
/**
* Method that resolves a symbolic link to the real file or directory.
*
* @param file File to check
* @return File The real file or directory
* @throws IOException If real file couldn't be resolved
*/
public static File resolveSymlink(File file) throws IOException {
return file.getCanonicalFile();
}
/**
* Method that returns a more human readable of the size
* of a file system object.
*
* @param fso File system object
* @return String The human readable size (void if fso don't supports size)
*/
public static String getHumanReadableSize(FileSystemObject fso) {
//Only if has size
if (fso instanceof Directory) {
return ""; //$NON-NLS-1$
}
if (hasSymlinkRef(fso)) {
if (isSymlinkRefDirectory(fso)) {
return ""; //$NON-NLS-1$
}
return getHumanReadableSize(((Symlink)fso).getLinkRef().getSize());
}
return getHumanReadableSize(fso.getSize());
}
/**
* Method that returns a more human readable of a size in bytes.
*
* @param size The size in bytes
* @return String The human readable size
*/
public static String getHumanReadableSize(long size) {
Resources res = FileManagerApplication.getInstance().getResources();
final int[] magnitude = {
R.string.size_bytes,
R.string.size_kilobytes,
R.string.size_megabytes,
R.string.size_gigabytes
};
double aux = size;
int cc = magnitude.length;
for (int i = 0; i < cc; i++) {
if (aux < 1024) {
double cleanSize = Math.round(aux * 100);
return Double.toString(cleanSize / 100) +
" " + res.getString(magnitude[i]); //$NON-NLS-1$
} else {
aux = aux / 1024;
}
}
double cleanSize = Math.round(aux * 100);
return Double.toString(cleanSize / 100) +
" " + res.getString(magnitude[cc - 1]); //$NON-NLS-1$
}
/**
* Method that returns if the file system object if the root directory.
*
* @param fso The file system object to check
* @return boolean if the file system object if the root directory
*/
public static boolean isRootDirectory(FileSystemObject fso) {
if (fso.getName() == null) return true;
return fso.getName().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
}
/**
* Method that returns if the folder if the root directory.
*
* @param folder The folder
* @return boolean if the folder if the root directory
*/
public static boolean isRootDirectory(String folder) {
if (folder == null) return true;
return isRootDirectory(new File(folder));
}
/**
* Method that returns if the folder if the root directory.
*
* @param folder The folder
* @return boolean if the folder if the root directory
*/
public static boolean isRootDirectory(File folder) {
if (folder.getPath() == null) return true;
return folder.getPath().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
}
/**
* Method that returns if the parent file system object if the root directory.
*
* @param fso The parent file system object to check
* @return boolean if the parent file system object if the root directory
*/
public static boolean isParentRootDirectory(FileSystemObject fso) {
if (fso.getParent() == null) return true;
return fso.getParent().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
}
/**
* Method that returns the name without the extension of a file system object.
*
* @param fso The file system object
* @return The name without the extension of the file system object.
*/
public static String getName(FileSystemObject fso) {
return getName(fso.getName());
}
/**
* Method that returns the name without the extension of a file system object.
*
* @param name The name of file system object
* @return The name without the extension of the file system object.
*/
public static String getName(String name) {
String ext = getExtension(name);
if (ext == null) return name;
return name.substring(0, name.length() - ext.length() - 1);
}
/**
* Method that returns the extension of a file system object.
*
* @param fso The file system object
* @return The extension of the file system object, or null
* if fso has no extension.
*/
public static String getExtension(FileSystemObject fso) {
return getExtension(fso.getName());
}
/**
* Method that returns the extension of a file system object.
*
* @param name The name of file system object
* @return The extension of the file system object, or null
* if fso has no extension.
*/
public static String getExtension(String name) {
final char dot = '.';
int pos = name.lastIndexOf(dot);
if (pos == -1 || pos == 0) { // Hidden files doesn't have extensions
return null;
}
// Exceptions to the general extraction method
int cc = COMPRESSED_TAR.length;
for (int i = 0; i < cc; i++) {
if (name.endsWith("." + COMPRESSED_TAR[i])) { //$NON-NLS-1$
return COMPRESSED_TAR[i];
}
}
// General extraction method
return name.substring(pos + 1);
}
/**
* Method that returns the parent directory of a file/folder
*
* @param path The file/folder
* @return String The parent directory
*/
public static String getParentDir(String path) {
return getParentDir(new File(path));
}
/**
* Method that returns the parent directory of a file/folder
*
* @param path The file/folder
* @return String The parent directory
*/
public static String getParentDir(File path) {
String parent = path.getParent();
if (parent == null && path.getAbsolutePath().compareTo(FileHelper.ROOT_DIRECTORY) != 0) {
parent = FileHelper.ROOT_DIRECTORY;
}
return parent;
}
/**
* Method that evaluates if a path is relative.
*
* @param src The path to check
* @return boolean If a path is relative
*/
public static boolean isRelativePath(String src) {
if (src.startsWith(CURRENT_DIRECTORY + File.separator)) {
return true;
}
if (src.startsWith(PARENT_DIRECTORY + File.separator)) {
return true;
}
if (src.indexOf(File.separator + CURRENT_DIRECTORY + File.separator) != -1) {
return true;
}
if (src.indexOf(File.separator + PARENT_DIRECTORY + File.separator) != -1) {
return true;
}
if (!src.startsWith(ROOT_DIRECTORY)) {
return true;
}
return false;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* has a link reference.
*
* @param fso The file system object to check
* @return boolean If file system object the has a link reference
*/
public static boolean hasSymlinkRef(FileSystemObject fso) {
if (fso instanceof Symlink) {
return ((Symlink)fso).getLinkRef() != null;
}
return false;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* the link reference is a directory.
*
* @param fso The file system object to check
* @return boolean If file system object the link reference is a directory
*/
public static boolean isSymlinkRefDirectory(FileSystemObject fso) {
if (!hasSymlinkRef(fso)) {
return false;
}
return ((Symlink)fso).getLinkRef() instanceof Directory;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* the link reference is a system file.
*
* @param fso The file system object to check
* @return boolean If file system object the link reference is a system file
*/
public static boolean isSymlinkRefSystemFile(FileSystemObject fso) {
if (!hasSymlinkRef(fso)) {
return false;
}
return ((Symlink)fso).getLinkRef() instanceof SystemFile;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* the link reference is a block device.
*
* @param fso The file system object to check
* @return boolean If file system object the link reference is a block device
*/
public static boolean isSymlinkRefBlockDevice(FileSystemObject fso) {
if (!hasSymlinkRef(fso)) {
return false;
}
return ((Symlink)fso).getLinkRef() instanceof BlockDevice;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* the link reference is a character device.
*
* @param fso The file system object to check
* @return boolean If file system object the link reference is a character device
*/
public static boolean isSymlinkRefCharacterDevice(FileSystemObject fso) {
if (!hasSymlinkRef(fso)) {
return false;
}
return ((Symlink)fso).getLinkRef() instanceof CharacterDevice;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* the link reference is a named pipe.
*
* @param fso The file system object to check
* @return boolean If file system object the link reference is a named pipe
*/
public static boolean isSymlinkRefNamedPipe(FileSystemObject fso) {
if (!hasSymlinkRef(fso)) {
return false;
}
return ((Symlink)fso).getLinkRef() instanceof NamedPipe;
}
/**
* Method that check if the file system object is a {@link Symlink} and
* the link reference is a domain socket.
*
* @param fso The file system object to check
* @return boolean If file system object the link reference is a domain socket
*/
public static boolean isSymlinkRefDomainSocket(FileSystemObject fso) {
if (!hasSymlinkRef(fso)) {
return false;
}
return ((Symlink)fso).getLinkRef() instanceof DomainSocket;
}
/**
* Method that checks if a file system object is a directory (real o symlink).
*
* @param fso The file system object to check
* @return boolean If file system object is a directory
*/
public static boolean isDirectory(FileSystemObject fso) {
if (fso instanceof Directory) {
return true;
}
if (isSymlinkRefDirectory(fso)) {
return true;
}
return false;
}
/**
* Method that checks if a file system object is a system file (real o symlink).
*
* @param fso The file system object to check
* @return boolean If file system object is a system file
*/
public static boolean isSystemFile(FileSystemObject fso) {
if (fso instanceof SystemFile) {
return true;
}
if (isSymlinkRefSystemFile(fso)) {
return true;
}
return false;
}
/**
* Method that returns the real reference of a file system object
* (the reference file system object if the file system object is a symlink.
* Otherwise the same reference).
*
* @param fso The file system object to check
* @return FileSystemObject The real file system object reference
*/
public static FileSystemObject getReference(FileSystemObject fso) {
if (hasSymlinkRef(fso)) {
return ((Symlink)fso).getLinkRef();
}
return fso;
}
/**
* Method that applies the configuration modes to the listed files
* (sort mode, hidden files, ...).
*
* @param files The listed files
* @param restrictions The restrictions to apply when displaying files
* @param chRooted If app run with no privileges
* @return List The applied mode listed files
*/
public static List applyUserPreferences(
List files, Map restrictions, boolean chRooted) {
return applyUserPreferences(files, restrictions, false, chRooted);
}
/**
* Method that applies the configuration modes to the listed files
* (sort mode, hidden files, ...).
*
* @param files The listed files
* @param restrictions The restrictions to apply when displaying files
* @param noSort If sort must be applied
* @param chRooted If app run with no privileges
* @return List The applied mode listed files
*/
public static List applyUserPreferences(
List files, Map restrictions,
boolean noSort, boolean chRooted) {
//Retrieve user preferences
SharedPreferences prefs = Preferences.getSharedPreferences();
FileManagerSettings sortModePref = FileManagerSettings.SETTINGS_SORT_MODE;
FileManagerSettings showDirsFirstPref = FileManagerSettings.SETTINGS_SHOW_DIRS_FIRST;
FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN;
FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM;
FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS;
//Remove all unnecessary files (no required by the user)
int cc = files.size();
for (int i = cc - 1; i >= 0; i--) {
FileSystemObject file = files.get(i);
//Hidden files
if (!prefs.getBoolean(
showHiddenPref.getId(),
((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
if (file.isHidden()) {
files.remove(i);
continue;
}
}
//System files
if (!prefs.getBoolean(
showSystemPref.getId(),
((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
if (file instanceof SystemFile) {
files.remove(i);
continue;
}
}
//Symlinks files
if (!prefs.getBoolean(
showSymlinksPref.getId(),
((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
if (file instanceof Symlink) {
files.remove(i);
continue;
}
}
// Restrictions (only apply to files)
if (restrictions != null) {
if (!isDirectory(file)) {
if (!isDisplayAllowed(file, restrictions)) {
files.remove(i);
continue;
}
}
}
}
//Apply sort mode
if (!noSort) {
final boolean showDirsFirst =
prefs.getBoolean(
showDirsFirstPref.getId(),
((Boolean)showDirsFirstPref.getDefaultValue()).booleanValue());
final NavigationSortMode sortMode =
NavigationSortMode.fromId(
prefs.getInt(sortModePref.getId(),
((ObjectIdentifier)sortModePref.getDefaultValue()).getId()));
Collections.sort(files, new Comparator() {
@Override
public int compare(FileSystemObject lhs, FileSystemObject rhs) {
//Parent directory always goes first
boolean isLhsParentDirectory = lhs instanceof ParentDirectory;
boolean isRhsParentDirectory = rhs instanceof ParentDirectory;
if (isLhsParentDirectory || isRhsParentDirectory) {
if (isLhsParentDirectory && isRhsParentDirectory) {
return 0;
}
return (isLhsParentDirectory) ? -1 : 1;
}
//Need to sort directory first?
if (showDirsFirst) {
boolean isLhsDirectory = FileHelper.isDirectory(lhs);
boolean isRhsDirectory = FileHelper.isDirectory(rhs);
if (isLhsDirectory || isRhsDirectory) {
if (isLhsDirectory && isRhsDirectory) {
//Apply sort mode
return FileHelper.doCompare(lhs, rhs, sortMode);
}
return (isLhsDirectory) ? -1 : 1;
}
}
//Apply sort mode
return FileHelper.doCompare(lhs, rhs, sortMode);
}
});
}
//Return the files
return files;
}
/**
* Determines if a file system object complies w/ a user's display preferences implying that
* the user is interested in this file
* (sort mode, hidden files, ...).
*
* @param fso The file
* @param restrictions The restrictions to apply when displaying files
* @param chRooted If app run with no privileges
* @return boolean indicating user's interest
*/
public static boolean compliesWithDisplayPreferences(
FileSystemObject fso, Map restrictions, boolean chRooted) {
//Retrieve user preferences
SharedPreferences prefs = Preferences.getSharedPreferences();
FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN;
FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM;
FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS;
//Hidden files
if (!prefs.getBoolean(
showHiddenPref.getId(),
((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
if (fso.isHidden()) {
return false;
}
}
//System files
if (!prefs.getBoolean(
showSystemPref.getId(),
((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
if (fso instanceof SystemFile) {
return false;
}
}
//Symlinks files
if (!prefs.getBoolean(
showSymlinksPref.getId(),
((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
if (fso instanceof Symlink) {
return false;
}
}
// Restrictions (only apply to files)
if (restrictions != null) {
if (!isDirectory(fso)) {
if (!isDisplayAllowed(fso, restrictions)) {
return false;
}
}
}
// all checks passed
return true;
}
/**
* Method that check if a file should be displayed according to the restrictions
*
* @param fso The file system object to check
* @param restrictions The restrictions map
* @return boolean If the file should be displayed
*/
private static boolean isDisplayAllowed(
FileSystemObject fso, Map restrictions) {
Iterator it = restrictions.keySet().iterator();
while (it.hasNext()) {
DisplayRestrictions restriction = it.next();
Object value = restrictions.get(restriction);
if (value == null) {
continue;
}
switch (restriction) {
case CATEGORY_TYPE_RESTRICTION:
if (value instanceof MimeTypeCategory) {
MimeTypeCategory cat1 = (MimeTypeCategory)value;
// NOTE: We don't need the context here, because mime-type
// database should be loaded prior to this call
MimeTypeCategory cat2 = MimeTypeHelper.getCategory(null, fso);
if (cat1.compareTo(cat2) != 0) {
return false;
}
}
break;
case MIME_TYPE_RESTRICTION:
String[] mimeTypes = null;
if (value instanceof String) {
mimeTypes = new String[] {(String) value};
} else if (value instanceof String[]) {
mimeTypes = (String[]) value;
}
if (mimeTypes != null) {
boolean matches = false;
for (String mimeType : mimeTypes) {
if (mimeType.compareTo(MimeTypeHelper.ALL_MIME_TYPES) == 0) {
matches = true;
break;
}
// NOTE: We don't need the context here, because mime-type
// database should be loaded prior to this call
if (MimeTypeHelper.matchesMimeType(null, fso, mimeType)) {
matches = true;
break;
}
}
if (!matches) {
return false;
}
}
break;
case SIZE_RESTRICTION:
if (value instanceof Long) {
Long maxSize = (Long)value;
if (fso.getSize() > maxSize.longValue()) {
return false;
}
}
break;
case DIRECTORY_ONLY_RESTRICTION:
if (value instanceof Boolean) {
Boolean directoryOnly = (Boolean) value;
if (directoryOnly.booleanValue() && !FileHelper.isDirectory(fso)) {
return false;
}
}
break;
case LOCAL_FILESYSTEM_ONLY_RESTRICTION:
if (value instanceof Boolean) {
Boolean localOnly = (Boolean)value;
if (localOnly.booleanValue()) {
/** TODO Needed when CMFM gets networking **/
}
}
break;
default:
break;
}
}
return true;
}
/**
* Method that resolve the symbolic links of the list of files passed as argument.
* This method invokes the {@link ResolveLinkCommand} in those files that have a valid
* symlink reference
*
* @param context The current context
* @param files The listed files
*/
public static void resolveSymlinks(Context context, List files) {
int cc = files.size();
for (int i = 0; i < cc; i++) {
FileSystemObject fso = files.get(i);
resolveSymlink(context, fso);
}
}
/**
* Method that resolves the symbolic link of a file passed in as argument.
* This method invokes the {@link ResolveLinkCommand} on the file that has a valid
* symlink reference
*
* @param context The current context
* @param fso FileSystemObject to resolve symlink
*/
public static void resolveSymlink(Context context, FileSystemObject fso) {
if (fso instanceof Symlink && ((Symlink)fso).getLinkRef() == null) {
try {
FileSystemObject symlink =
CommandHelper.resolveSymlink(context, fso.getFullPath(), null);
((Symlink)fso).setLinkRef(symlink);
} catch (Throwable ex) {/**NON BLOCK**/}
}
}
/**
* Method that do a comparison between 2 file system objects.
*
* @param fso1 The first file system objects
* @param fso2 The second file system objects
* @param mode The sort mode
* @return int a negative integer if {@code fso1} is less than {@code fso2};
* a positive integer if {@code fso1} is greater than {@code fso2};
* 0 if {@code fso1} has the same order as {@code fso2}.
*/
public static int doCompare(
final FileSystemObject fso1,
final FileSystemObject fso2,
final NavigationSortMode mode) {
// Retrieve the user preference for case sensitive sort
boolean caseSensitive =
Preferences.getSharedPreferences().
getBoolean(
FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.getId(),
((Boolean)FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.
getDefaultValue()).booleanValue());
//Name (ascending)
if (mode.getId() == NavigationSortMode.NAME_ASC.getId()) {
if (!caseSensitive) {
return fso1.getName().compareToIgnoreCase(fso2.getName());
}
return fso1.getName().compareTo(fso2.getName());
}
//Name (descending)
if (mode.getId() == NavigationSortMode.NAME_DESC.getId()) {
if (!caseSensitive) {
return fso1.getName().compareToIgnoreCase(fso2.getName()) * -1;
}
return fso1.getName().compareTo(fso2.getName()) * -1;
}
//Date (ascending)
if (mode.getId() == NavigationSortMode.DATE_ASC.getId()) {
return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime());
}
//Date (descending)
if (mode.getId() == NavigationSortMode.DATE_DESC.getId()) {
return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime()) * -1;
}
//Size (ascending)
if (mode.getId() == NavigationSortMode.SIZE_ASC.getId()) {
return Long.compare(fso1.getSize(), fso2.getSize());
}
//Size (descending)
if (mode.getId() == NavigationSortMode.SIZE_DESC.getId()) {
return Long.compare(fso1.getSize(), fso2.getSize()) * -1;
}
//Type (ascending)
if (mode.getId() == NavigationSortMode.TYPE_ASC.getId()) {
// Shouldn't need context here, mimetypes should be loaded
return MimeTypeHelper.compareFSO(null, fso1, fso2);
}
//Type (descending)
if (mode.getId() == NavigationSortMode.TYPE_DESC.getId()) {
// Shouldn't need context here, mimetypes should be loaded
return MimeTypeHelper.compareFSO(null, fso1, fso2) * -1;
}
//Comparison between files directly
return fso1.compareTo(fso2);
}
/**
* Method that add to the path the trailing slash
*
* @param path The path
* @return String The path with the trailing slash
*/
public static String addTrailingSlash(String path) {
if (path == null) return null;
return path.endsWith(File.separator) ? path : path + File.separator;
}
/**
* Method that cleans the path and removes the trailing slash
*
* @param path The path to clean
* @return String The path without the trailing slash
*/
public static String removeTrailingSlash(String path) {
if (path == null) return null;
if (path.trim().compareTo(ROOT_DIRECTORY) == 0) return path;
if (path.endsWith(File.separator)) {
return path.substring(0, path.length()-1);
}
return path;
}
/**
* Method that creates a new name based on the name of the {@link FileSystemObject}
* that is not current used by the filesystem.
*
* @param ctx The current context
* @param parentDir The directory in which we want to make the file
* @param attemptedName The attempted name
* @param regexp The resource of the regular expression to create the new name
* @return String The new non-existing name
*/
public static String createNonExistingName(
final Context ctx, final String parentDir,
final String attemptedName, int regexp) {
// Find a non-exiting name
String newName = attemptedName;
if (!isNameExists(ctx, parentDir, newName)) return newName;
do {
String name = FileHelper.getName(newName);
String ext = FileHelper.getExtension(newName);
if (ext == null) {
ext = ""; //$NON-NLS-1$
} else {
ext = "." + ext; //$NON-NLS-1$
}
newName = ctx.getString(regexp, name, ext);
} while (isNameExists(ctx, parentDir, newName));
return newName;
}
/**
* Method that checks if a name exists in the current directory.
*
* @param context The application context
* @param parentDir The full path to the parent directory
* @param name The name to check
* @return boolean Indicate if the name exists in the current directory
*/
public static boolean isNameExists(Context context, String parentDir, String name) {
if (parentDir == null || parentDir.equals(ROOT_DIRECTORY)) {
parentDir = "";
}
//Verify if the name exists in the current file list
try {
return CommandHelper.getFileInfo(context, parentDir + "/" + name, null) != null;
} catch (Exception e) {
// This is a slight misreporting, however, I don't want to do a bunch of refactoring
Log.i(TAG, "Failed to get file info: " + e.getMessage());
return false;
}
}
/**
* Method that returns is a {@link FileSystemObject} can be handled by this application
* allowing the uncompression of the file
*
* @param fso The file system object to verify
* @return boolean If the file is supported
*/
@SuppressWarnings("nls")
public static boolean isSupportedUncompressedFile(FileSystemObject fso) {
// Valid uncompressed formats are:
final String[] VALID =
{
"tar", "tgz", "tar.gz", "tar.bz2", "tar.lzma",
"zip", "gz", "bz2", "lzma", "xz", "Z", "rar"
};
// Null values for required commands
final String[] OPT_KEYS =
{
null, null, null, null, null,
"unzip", null, null, "unlzma", "unxz", "uncompress", "unrar"
};
// Check that have a valid file
if (fso == null) return false;
// Only regular files
if (isDirectory(fso) || fso instanceof Symlink) {
return false;
}
// No in virtual filesystems
if (fso.isSecure() || fso.isRemote()) {
return false;
}
String ext = getExtension(fso);
if (ext != null) {
int cc = VALID.length;
for (int i = 0; i < cc; i++) {
if (VALID[i].compareToIgnoreCase(ext) == 0) {
// Is the command present
if (OPT_KEYS[i] != null &&
FileManagerApplication.hasOptionalCommand(OPT_KEYS[i])) {
return true;
}
return false;
}
}
}
return false;
}
/**
* Method that converts an absolute path to a relative path
*
* @param path The absolute path to convert
* @param relativeTo The absolute path from which make path relative to (a folder)
* @return String The relative path
*/
public static String toRelativePath(String path, String relativeTo) {
// Normalize the paths
File f1 = new File(path);
File f2 = new File(relativeTo);
String s1 = f1.getAbsolutePath();
String s2 = f2.getAbsolutePath();
if (!s2.endsWith(File.separator)) {
s2 = s2 + File.separator;
}
// If s2 contains s1 then the relative is replace of the start of the path
if (s1.startsWith(s2)) {
return s1.substring(s2.length());
}
StringBuffer relative = new StringBuffer();
do {
File f3 = new File(s2);
relative.append(String.format("..%s", File.separator)); //$NON-NLS-1$
s2 = f3.getParent() + File.separator;
} while (!s1.startsWith(s2) && !s1.startsWith(new File(s2).getAbsolutePath()));
s2 = new File(s2).getAbsolutePath();
return relative.toString() + s1.substring(s2.length());
}
/**
* Method that creates a {@link FileSystemObject} from a {@link File}
*
* @param file The file or folder reference
* @return FileSystemObject The file system object reference
*/
public static FileSystemObject createFileSystemObject(File file) {
try {
// The user and group name of the files. Use the defaults one for sdcards
final String USER = "root"; //$NON-NLS-1$
final String GROUP = "sdcard_r"; //$NON-NLS-1$
// The user and group name of the files. In ChRoot, aosp give restrict access to
// this user and group. This applies for permission also. This has no really much
// interest if we not allow to change the permissions
AID userAID = AIDHelper.getAIDFromName(USER);
AID groupAID = AIDHelper.getAIDFromName(GROUP);
User user = new User(userAID.getId(), userAID.getName());
Group group = new Group(groupAID.getId(), groupAID.getName());
Permissions perm = file.isDirectory()
? Permissions.createDefaultFolderPermissions()
: Permissions.createDefaultFilePermissions();
// Build a directory?
Date lastModified = new Date(file.lastModified());
if (file.isDirectory()) {
return
new Directory(
file.getName(),
file.getParent(),
user, group, perm,
lastModified, lastModified, lastModified); // The only date we have
}
// Build a regular file
return
new RegularFile(
file.getName(),
file.getParent(),
user, group, perm,
file.length(),
lastModified, lastModified, lastModified); // The only date we have
} catch (Exception e) {
Log.e(TAG, "Exception retrieving the fso", e); //$NON-NLS-1$
}
return null;
}
/**
* Method that copies recursively to the destination
*
* @param src The source file or folder
* @param dst The destination file or folder
* @return boolean If the operation complete successfully
* @throws ExecutionException If a problem was detected in the operation
*/
public static boolean copyRecursive(
final File src, final File dst, Program program)
throws ExecutionException, CancelledOperationException {
if (src.isDirectory()) {
// Create the directory
if (dst.exists() && !dst.isDirectory()) {
Log.e(TAG,
String.format("Failed to check destionation dir: %s", dst)); //$NON-NLS-1$
throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$
}
if (!dst.exists()) {
if (!dst.mkdir()) {
Log.e(TAG, String.format("Failed to create directory: %s", dst)); //$NON-NLS-1$
return false;
}
}
File[] files = src.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
// Short circuit if we've been cancelled. Show's over :(
if (program.isCancelled()) {
throw new CancelledOperationException();
}
if (!copyRecursive(files[i], new File(dst, files[i].getName()),
program)) {
return false;
}
}
}
} else {
// Copy the directory
if (!copyFileWithNio(src, dst, program)) {
return false;
}
}
return true;
}
/**
* Method that copies a file, using FileChannel.transferFrom from
* the nio package.
*
* The file is chunked into chunks of size {@link #NIO_COPY_CHUNK_SIZE}
* and each chunk is transferred until the file is completely copied. This
* allows us to cancel the file transfer at any time.
*
* @param src The source file
* @param dst The destination file
* @return boolean Whether the operation completed successfully
*/
public static boolean copyFileWithNio(final File src, final File dst,
Program program) throws CancelledOperationException, ExecutionException {
FileChannel inputChannel = null;
FileChannel outputChannel = null;
long currentPosition = 0;
long count = NIO_COPY_CHUNK_SIZE;
try {
inputChannel = new FileInputStream(src).getChannel();
outputChannel = new FileOutputStream(dst).getChannel();
while (currentPosition < inputChannel.size()) {
// Short circuit if we've been cancelled. Show's over :(
if (program.isCancelled()) {
throw new CancelledOperationException();
}
if ((currentPosition + count) > inputChannel.size()) {
count = (inputChannel.size() - currentPosition);
}
outputChannel.transferFrom(inputChannel, currentPosition, count);
currentPosition = currentPosition + count;
}
} catch (Throwable e) {
Log.e(TAG,
String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$
try {
// Delete the destination file upon failure
if (!dst.delete()) {
Log.e(TAG, "Failed to delete the dest file: " + dst);
}
} catch (Throwable t) {/**NON BLOCK**/}
// Check if this error is an out of space exception and throw that specifically.
// ENOSPC -> Error No Space
if (e.getCause() instanceof ErrnoException
&& ((ErrnoException)e.getCause()).errno == OsConstants.ENOSPC) {
throw new ExecutionException(R.string.msgs_no_disk_space);
} else if (e instanceof CancelledOperationException) {
// If the user cancelled this operation, let it through.
throw (CancelledOperationException)e;
} else if (e instanceof ClosedByInterruptException) {
// The thread running this operation was interrupted.
// This is likely because the user cancelled the operation,
// which cancelled the AsyncTask.
throw new CancelledOperationException();
}
return false;
} finally {
try {
if (inputChannel != null) {
inputChannel.close();
}
} catch (IOException e) {
Log.e(TAG, "Error while closing input channel during copyFileWithNio");
}
try {
if (outputChannel != null) {
outputChannel.close();
}
} catch (IOException e) {
Log.e(TAG, "Error while closing output channel during copyFileWithNio");
}
}
return true;
}
/**
* Method that deletes a folder recursively
*
* @param folder The folder to delete
* @return boolean If the folder was deleted
*/
public static boolean deleteFolder(File folder) {
File[] files = folder.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
if (!deleteFolder(files[i])) {
return false;
}
} else {
if (!files[i].delete()) {
return false;
}
}
}
}
return folder.delete();
}
/**
* Method that returns the canonical/absolute path of the path.
* This method performs path resolution
*
* @param path The path to convert
* @return String The canonical/absolute path
*/
public static String getAbsPath(String path) {
try {
return new File(path).getCanonicalPath();
} catch (Exception e) {
return new File(path).getAbsolutePath();
}
}
/**
* Method that returns the .nomedia file
*
* @param fso The folder that contains the .nomedia file
* @return File The .nomedia file
*/
public static File getNoMediaFile(FileSystemObject fso) {
File file = null;
try {
file = new File(fso.getFullPath()).getCanonicalFile();
} catch (Exception e) {
file = new File(fso.getFullPath()).getAbsoluteFile();
}
return new File(file, ".nomedia").getAbsoluteFile(); //$NON-NLS-1$
}
/**
* Method that ensures that the actual console has access to read the
* {@link FileSystemObject} passed.
*
* @param console The console
* @param fso The {@link FileSystemObject} to check
* @param executable The executable to associate to the {@link InsufficientPermissionsException}
* @throws InsufficientPermissionsException If the console doesn't have enough rights
*/
public static void ensureReadAccess(
Console console, FileSystemObject fso, SyncResultExecutable executable)
throws InsufficientPermissionsException {
try {
if (console.isPrivileged()) {
// Should have access
return;
}
if (console instanceof JavaConsole &&
StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
// Java console runs in chrooted environment, and sdcard are always readable
return;
}
Identity identity = console.getIdentity();
if (identity == null) {
throw new InsufficientPermissionsException(executable);
}
Permissions permissions = fso.getPermissions();
User user = fso.getUser();
Group group = fso.getGroup();
List groups = identity.getGroups();
if ( permissions == null || user == null || group == null) {
throw new InsufficientPermissionsException(executable);
}
// Check others
if (permissions.getOthers().isRead()) {
return;
}
// Check user
if (user.getId() == identity.getUser().getId() && permissions.getUser().isRead()) {
return;
}
// Check group
if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isRead()) {
return;
}
// Check groups
int cc = groups.size();
for (int i = 0; i < cc; i++) {
Group g = groups.get(i);
if (group.getId() == g.getId() && permissions.getGroup().isRead()) {
return;
}
}
} catch (Exception e) {
Log.e(TAG, "Failed to check fso read permission,", e); //$NON-NLS-1$
}
throw new InsufficientPermissionsException(executable);
}
/**
* Method that ensures that the actual console has access to write the
* {@link FileSystemObject} passed.
*
* @param console The console
* @param fso The {@link FileSystemObject} to check
* @param executable The executable to associate to the {@link InsufficientPermissionsException}
* @throws InsufficientPermissionsException If the console doesn't have enough rights
*/
public static void ensureWriteAccess(
Console console, FileSystemObject fso, SyncResultExecutable executable)
throws InsufficientPermissionsException {
try {
if (console.isPrivileged()) {
// Should have access
return;
}
if (console instanceof JavaConsole &&
StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
// Java console runs in chrooted environment, and sdcard are always writeable
return;
}
Identity identity = console.getIdentity();
if (identity == null) {
throw new InsufficientPermissionsException(executable);
}
Permissions permissions = fso.getPermissions();
User user = fso.getUser();
Group group = fso.getGroup();
List groups = identity.getGroups();
if ( permissions == null || user == null || group == null) {
throw new InsufficientPermissionsException(executable);
}
// Check others
if (permissions.getOthers().isWrite()) {
return;
}
// Check user
if (user.getId() == identity.getUser().getId() && permissions.getUser().isWrite()) {
return;
}
// Check group
if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isWrite()) {
return;
}
// Check groups
int cc = groups.size();
for (int i = 0; i < cc; i++) {
Group g = groups.get(i);
if (group.getId() == g.getId() && permissions.getGroup().isWrite()) {
return;
}
}
} catch (Exception e) {
Log.e(TAG, "Failed to check fso write permission,", e); //$NON-NLS-1$
}
throw new InsufficientPermissionsException(executable);
}
/**
* Method that ensures that the actual console has access to execute the
* {@link FileSystemObject} passed.
*
* @param console The console
* @param fso The {@link FileSystemObject} to check
* @param executable The executable to associate to the {@link InsufficientPermissionsException}
* @throws InsufficientPermissionsException If the console doesn't have enough rights
*/
public static void ensureExecuteAccess(
Console console, FileSystemObject fso, SyncResultExecutable executable)
throws InsufficientPermissionsException {
try {
if (console.isPrivileged()) {
// Should have access
return;
}
if (console instanceof JavaConsole &&
StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
// Java console runs in chrooted environment, and sdcard are never executable
throw new InsufficientPermissionsException(executable);
}
Identity identity = console.getIdentity();
if (identity == null) {
throw new InsufficientPermissionsException(executable);
}
Permissions permissions = fso.getPermissions();
User user = fso.getUser();
Group group = fso.getGroup();
List groups = identity.getGroups();
if ( permissions == null || user == null || group == null) {
throw new InsufficientPermissionsException(executable);
}
// Check others
if (permissions.getOthers().isExecute()) {
return;
}
// Check user
if (user.getId() == identity.getUser().getId() && permissions.getUser().isExecute()) {
return;
}
// Check group
if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isExecute()) {
return;
}
// Check groups
int cc = groups.size();
for (int i = 0; i < cc; i++) {
Group g = groups.get(i);
if (group.getId() == g.getId() && permissions.getGroup().isExecute()) {
return;
}
}
} catch (Exception e) {
Log.e(TAG, "Failed to check fso execute permission,", e); //$NON-NLS-1$
}
throw new InsufficientPermissionsException(executable);
}
/**
* Method that formats a filetime date with the specific system settings
*
* @param ctx The current context
* @param filetime The filetime date
* @return String The filetime date formatted
*/
public static String formatFileTime(Context ctx, Date filetime) {
synchronized (DATETIME_SYNC) {
if (sReloadDateTimeFormats) {
String defaultValue =
((ObjectStringIdentifier)FileManagerSettings.
SETTINGS_FILETIME_FORMAT_MODE.getDefaultValue()).getId();
String id = FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE.getId();
sFiletimeFormatMode =
FileTimeFormatMode.fromId(
Preferences.getSharedPreferences().getString(id, defaultValue));
if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) {
sDateTimeFormatOrder = ctx.getString(R.string.datetime_format_order);
sDateFormat = android.text.format.DateFormat.getDateFormat(ctx);
sTimeFormat = android.text.format.DateFormat.getTimeFormat(ctx);
} else if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.LOCALE) == 0) {
sDateFormat =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
} else {
sDateFormat = new SimpleDateFormat(sFiletimeFormatMode.getFormat());
}
sReloadDateTimeFormats = false;
}
}
// Apply the user settings
if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) {
String date = sDateFormat.format(filetime);
String time = sTimeFormat.format(filetime);
return String.format(sDateTimeFormatOrder, date, time);
} else {
return sDateFormat.format(filetime);
}
}
/**
* Method that create a new temporary filename
*
* @param external If the file should be created in the external or the internal cache dir
*/
public static synchronized File createTempFilename(Context context, boolean external) {
File tempDirectory = external ? context.getExternalCacheDir() : context.getCacheDir();
File tempFile;
do {
UUID uuid = UUID.randomUUID();
tempFile = new File(tempDirectory, uuid.toString());
} while (tempFile.exists());
return tempFile;
}
/**
* Method that delete a file or a folder
*
* @param src The file or folder to delete
* @return boolean If the operation was successfully
*/
public static boolean deleteFileOrFolder(File src) {
if (src.isDirectory()) {
return FileHelper.deleteFolder(src);
}
return src.delete();
}
/**
* Method that checks if the source file passed belongs to (is under) the directory passed
*
* @param src The file to check
* @param dir The parent file to check
* @return boolean If the source belongs to the directory
*/
public static boolean belongsToDirectory(File src, File dir) {
if (dir.isFile()) {
return false;
}
return src.getAbsolutePath().startsWith(dir.getAbsolutePath());
}
/**
* Method that checks if both path are the same (by checking sensitive cases).
*
* @param src The source path
* @param dst The destination path
* @return boolean If both are the same path
*/
public static boolean isSamePath(String src, String dst) {
// This is only true if both are exactly the same path or the same file in insensitive
// file systems
File o1 = new File(src);
File o2 = new File(dst);
return o1.equals(o2);
}
}