2 * Copyright (C) 2012 The CyanogenMod Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.cyanogenmod.filemanager.util;
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.res.Resources;
22 import android.system.ErrnoException;
23 import android.system.OsConstants;
24 import android.util.Log;
26 import com.cyanogenmod.filemanager.FileManagerApplication;
27 import com.cyanogenmod.filemanager.R;
28 import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
29 import com.cyanogenmod.filemanager.commands.java.Program;
30 import com.cyanogenmod.filemanager.commands.shell.ResolveLinkCommand;
31 import com.cyanogenmod.filemanager.console.CancelledOperationException;
32 import com.cyanogenmod.filemanager.console.Console;
33 import com.cyanogenmod.filemanager.console.ExecutionException;
34 import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
35 import com.cyanogenmod.filemanager.console.java.JavaConsole;
36 import com.cyanogenmod.filemanager.model.AID;
37 import com.cyanogenmod.filemanager.model.BlockDevice;
38 import com.cyanogenmod.filemanager.model.CharacterDevice;
39 import com.cyanogenmod.filemanager.model.Directory;
40 import com.cyanogenmod.filemanager.model.DomainSocket;
41 import com.cyanogenmod.filemanager.model.FileSystemObject;
42 import com.cyanogenmod.filemanager.model.Group;
43 import com.cyanogenmod.filemanager.model.Identity;
44 import com.cyanogenmod.filemanager.model.NamedPipe;
45 import com.cyanogenmod.filemanager.model.ParentDirectory;
46 import com.cyanogenmod.filemanager.model.Permissions;
47 import com.cyanogenmod.filemanager.model.RegularFile;
48 import com.cyanogenmod.filemanager.model.Symlink;
49 import com.cyanogenmod.filemanager.model.SystemFile;
50 import com.cyanogenmod.filemanager.model.User;
51 import com.cyanogenmod.filemanager.preferences.DisplayRestrictions;
52 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
53 import com.cyanogenmod.filemanager.preferences.FileTimeFormatMode;
54 import com.cyanogenmod.filemanager.preferences.NavigationSortMode;
55 import com.cyanogenmod.filemanager.preferences.ObjectIdentifier;
56 import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier;
57 import com.cyanogenmod.filemanager.preferences.Preferences;
58 import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
60 import java.io.BufferedInputStream;
61 import java.io.BufferedOutputStream;
63 import java.io.FileInputStream;
64 import java.io.FileOutputStream;
65 import java.io.IOException;
66 import java.text.DateFormat;
67 import java.text.SimpleDateFormat;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.Date;
71 import java.util.Iterator;
72 import java.util.List;
74 import java.util.UUID;
77 * A helper class with useful methods for deal with files.
79 public final class FileHelper {
81 private static final String TAG = "FileHelper"; //$NON-NLS-1$
83 // Scheme for file and directory picking
84 public static final String FILE_URI_SCHEME = "file"; //$NON-NLS-1$
85 public static final String FOLDER_URI_SCHEME = "folder"; //$NON-NLS-1$
86 public static final String DIRECTORY_URI_SCHEME = "directory"; //$NON-NLS-1$
89 * Special extension for compressed tar files
91 private static final String[] COMPRESSED_TAR =
93 "tar.gz", "tar.bz2", "tar.lzma" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
100 public static final String ROOT_DIRECTORY = "/"; //$NON-NLS-1$
103 * The parent directory string.
106 public static final String PARENT_DIRECTORY = ".."; //$NON-NLS-1$
109 * The current directory string.
112 public static final String CURRENT_DIRECTORY = "."; //$NON-NLS-1$
115 * The administrator user.
118 public static final String USER_ROOT = "root"; //$NON-NLS-1$
121 * The newline string.
124 public static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
126 // The date/time formats objects
130 public final static Object DATETIME_SYNC = new Object();
134 public static boolean sReloadDateTimeFormats = true;
135 private static String sDateTimeFormatOrder = null;
136 private static FileTimeFormatMode sFiletimeFormatMode = null;
137 private static DateFormat sDateFormat = null;
138 private static DateFormat sTimeFormat = null;
141 * Constructor of <code>FileHelper</code>.
143 private FileHelper() {
148 * Method that check if a file is a symbolic link.
150 * @param file File to check
151 * @return boolean If file is a symbolic link
152 * @throws IOException If real file couldn't be checked
154 public static boolean isSymlink(File file) throws IOException {
155 return file.getAbsolutePath().compareTo(file.getCanonicalPath()) != 0;
159 * Method that resolves a symbolic link to the real file or directory.
161 * @param file File to check
162 * @return File The real file or directory
163 * @throws IOException If real file couldn't be resolved
165 public static File resolveSymlink(File file) throws IOException {
166 return file.getCanonicalFile();
170 * Method that returns a more human readable of the size
171 * of a file system object.
173 * @param fso File system object
174 * @return String The human readable size (void if fso don't supports size)
176 public static String getHumanReadableSize(FileSystemObject fso) {
178 if (fso instanceof Directory) {
179 return ""; //$NON-NLS-1$
181 if (hasSymlinkRef(fso)) {
182 if (isSymlinkRefDirectory(fso)) {
183 return ""; //$NON-NLS-1$
185 return getHumanReadableSize(((Symlink)fso).getLinkRef().getSize());
187 return getHumanReadableSize(fso.getSize());
191 * Method that returns a more human readable of a size in bytes.
193 * @param size The size in bytes
194 * @return String The human readable size
196 public static String getHumanReadableSize(long size) {
197 Resources res = FileManagerApplication.getInstance().getResources();
198 final int[] magnitude = {
200 R.string.size_kilobytes,
201 R.string.size_megabytes,
202 R.string.size_gigabytes
206 int cc = magnitude.length;
207 for (int i = 0; i < cc; i++) {
210 return Long.toString(aux) + " " + res.getString(magnitude[i]); //$NON-NLS-1$
214 return Long.toString(aux) + " " + res.getString(magnitude[cc - 1]); //$NON-NLS-1$
218 * Method that returns if the file system object if the root directory.
220 * @param fso The file system object to check
221 * @return boolean if the file system object if the root directory
223 public static boolean isRootDirectory(FileSystemObject fso) {
224 if (fso.getName() == null) return true;
225 return fso.getName().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
229 * Method that returns if the folder if the root directory.
231 * @param folder The folder
232 * @return boolean if the folder if the root directory
234 public static boolean isRootDirectory(String folder) {
235 if (folder == null) return true;
236 return isRootDirectory(new File(folder));
240 * Method that returns if the folder if the root directory.
242 * @param folder The folder
243 * @return boolean if the folder if the root directory
245 public static boolean isRootDirectory(File folder) {
246 if (folder.getPath() == null) return true;
247 return folder.getPath().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
251 * Method that returns if the parent file system object if the root directory.
253 * @param fso The parent file system object to check
254 * @return boolean if the parent file system object if the root directory
256 public static boolean isParentRootDirectory(FileSystemObject fso) {
257 if (fso.getParent() == null) return true;
258 return fso.getParent().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
262 * Method that returns the name without the extension of a file system object.
264 * @param fso The file system object
265 * @return The name without the extension of the file system object.
267 public static String getName(FileSystemObject fso) {
268 return getName(fso.getName());
272 * Method that returns the name without the extension of a file system object.
274 * @param name The name of file system object
275 * @return The name without the extension of the file system object.
277 public static String getName(String name) {
278 String ext = getExtension(name);
279 if (ext == null) return name;
280 return name.substring(0, name.length() - ext.length() - 1);
284 * Method that returns the extension of a file system object.
286 * @param fso The file system object
287 * @return The extension of the file system object, or <code>null</code>
288 * if <code>fso</code> has no extension.
290 public static String getExtension(FileSystemObject fso) {
291 return getExtension(fso.getName());
295 * Method that returns the extension of a file system object.
297 * @param name The name of file system object
298 * @return The extension of the file system object, or <code>null</code>
299 * if <code>fso</code> has no extension.
301 public static String getExtension(String name) {
302 final char dot = '.';
303 int pos = name.lastIndexOf(dot);
304 if (pos == -1 || pos == 0) { // Hidden files doesn't have extensions
308 // Exceptions to the general extraction method
309 int cc = COMPRESSED_TAR.length;
310 for (int i = 0; i < cc; i++) {
311 if (name.endsWith("." + COMPRESSED_TAR[i])) { //$NON-NLS-1$
312 return COMPRESSED_TAR[i];
316 // General extraction method
317 return name.substring(pos + 1);
321 * Method that returns the parent directory of a file/folder
323 * @param path The file/folder
324 * @return String The parent directory
326 public static String getParentDir(String path) {
327 return getParentDir(new File(path));
331 * Method that returns the parent directory of a file/folder
333 * @param path The file/folder
334 * @return String The parent directory
336 public static String getParentDir(File path) {
337 String parent = path.getParent();
338 if (parent == null && path.getAbsolutePath().compareTo(FileHelper.ROOT_DIRECTORY) != 0) {
339 parent = FileHelper.ROOT_DIRECTORY;
345 * Method that evaluates if a path is relative.
347 * @param src The path to check
348 * @return boolean If a path is relative
350 public static boolean isRelativePath(String src) {
351 if (src.startsWith(CURRENT_DIRECTORY + File.separator)) {
354 if (src.startsWith(PARENT_DIRECTORY + File.separator)) {
357 if (src.indexOf(File.separator + CURRENT_DIRECTORY + File.separator) != -1) {
360 if (src.indexOf(File.separator + PARENT_DIRECTORY + File.separator) != -1) {
363 if (!src.startsWith(ROOT_DIRECTORY)) {
370 * Method that check if the file system object is a {@link Symlink} and
371 * has a link reference.
373 * @param fso The file system object to check
374 * @return boolean If file system object the has a link reference
376 public static boolean hasSymlinkRef(FileSystemObject fso) {
377 if (fso instanceof Symlink) {
378 return ((Symlink)fso).getLinkRef() != null;
384 * Method that check if the file system object is a {@link Symlink} and
385 * the link reference is a directory.
387 * @param fso The file system object to check
388 * @return boolean If file system object the link reference is a directory
390 public static boolean isSymlinkRefDirectory(FileSystemObject fso) {
391 if (!hasSymlinkRef(fso)) {
394 return ((Symlink)fso).getLinkRef() instanceof Directory;
398 * Method that check if the file system object is a {@link Symlink} and
399 * the link reference is a system file.
401 * @param fso The file system object to check
402 * @return boolean If file system object the link reference is a system file
404 public static boolean isSymlinkRefSystemFile(FileSystemObject fso) {
405 if (!hasSymlinkRef(fso)) {
408 return ((Symlink)fso).getLinkRef() instanceof SystemFile;
412 * Method that check if the file system object is a {@link Symlink} and
413 * the link reference is a block device.
415 * @param fso The file system object to check
416 * @return boolean If file system object the link reference is a block device
418 public static boolean isSymlinkRefBlockDevice(FileSystemObject fso) {
419 if (!hasSymlinkRef(fso)) {
422 return ((Symlink)fso).getLinkRef() instanceof BlockDevice;
426 * Method that check if the file system object is a {@link Symlink} and
427 * the link reference is a character device.
429 * @param fso The file system object to check
430 * @return boolean If file system object the link reference is a character device
432 public static boolean isSymlinkRefCharacterDevice(FileSystemObject fso) {
433 if (!hasSymlinkRef(fso)) {
436 return ((Symlink)fso).getLinkRef() instanceof CharacterDevice;
440 * Method that check if the file system object is a {@link Symlink} and
441 * the link reference is a named pipe.
443 * @param fso The file system object to check
444 * @return boolean If file system object the link reference is a named pipe
446 public static boolean isSymlinkRefNamedPipe(FileSystemObject fso) {
447 if (!hasSymlinkRef(fso)) {
450 return ((Symlink)fso).getLinkRef() instanceof NamedPipe;
454 * Method that check if the file system object is a {@link Symlink} and
455 * the link reference is a domain socket.
457 * @param fso The file system object to check
458 * @return boolean If file system object the link reference is a domain socket
460 public static boolean isSymlinkRefDomainSocket(FileSystemObject fso) {
461 if (!hasSymlinkRef(fso)) {
464 return ((Symlink)fso).getLinkRef() instanceof DomainSocket;
468 * Method that checks if a file system object is a directory (real o symlink).
470 * @param fso The file system object to check
471 * @return boolean If file system object is a directory
473 public static boolean isDirectory(FileSystemObject fso) {
474 if (fso instanceof Directory) {
477 if (isSymlinkRefDirectory(fso)) {
484 * Method that checks if a file system object is a system file (real o symlink).
486 * @param fso The file system object to check
487 * @return boolean If file system object is a system file
489 public static boolean isSystemFile(FileSystemObject fso) {
490 if (fso instanceof SystemFile) {
493 if (isSymlinkRefSystemFile(fso)) {
500 * Method that returns the real reference of a file system object
501 * (the reference file system object if the file system object is a symlink.
502 * Otherwise the same reference).
504 * @param fso The file system object to check
505 * @return FileSystemObject The real file system object reference
507 public static FileSystemObject getReference(FileSystemObject fso) {
508 if (hasSymlinkRef(fso)) {
509 return ((Symlink)fso).getLinkRef();
515 * Method that applies the configuration modes to the listed files
516 * (sort mode, hidden files, ...).
518 * @param files The listed files
519 * @param restrictions The restrictions to apply when displaying files
520 * @param chRooted If app run with no privileges
521 * @return List<FileSystemObject> The applied mode listed files
523 public static List<FileSystemObject> applyUserPreferences(
524 List<FileSystemObject> files, Map<DisplayRestrictions,
525 Object> restrictions, boolean chRooted) {
526 return applyUserPreferences(files, restrictions, false, chRooted);
530 * Method that applies the configuration modes to the listed files
531 * (sort mode, hidden files, ...).
533 * @param files The listed files
534 * @param restrictions The restrictions to apply when displaying files
535 * @param noSort If sort must be applied
536 * @param chRooted If app run with no privileges
537 * @return List<FileSystemObject> The applied mode listed files
539 public static List<FileSystemObject> applyUserPreferences(
540 List<FileSystemObject> files, Map<DisplayRestrictions, Object> restrictions,
541 boolean noSort, boolean chRooted) {
542 //Retrieve user preferences
543 SharedPreferences prefs = Preferences.getSharedPreferences();
544 FileManagerSettings sortModePref = FileManagerSettings.SETTINGS_SORT_MODE;
545 FileManagerSettings showDirsFirstPref = FileManagerSettings.SETTINGS_SHOW_DIRS_FIRST;
546 FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN;
547 FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM;
548 FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS;
550 //Remove all unnecessary files (no required by the user)
551 int cc = files.size();
552 for (int i = cc - 1; i >= 0; i--) {
553 FileSystemObject file = files.get(i);
556 if (!prefs.getBoolean(
557 showHiddenPref.getId(),
558 ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
559 if (file.isHidden()) {
566 if (!prefs.getBoolean(
567 showSystemPref.getId(),
568 ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
569 if (file instanceof SystemFile) {
576 if (!prefs.getBoolean(
577 showSymlinksPref.getId(),
578 ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
579 if (file instanceof Symlink) {
585 // Restrictions (only apply to files)
586 if (restrictions != null) {
587 if (!isDirectory(file)) {
588 if (!isDisplayAllowed(file, restrictions)) {
598 final boolean showDirsFirst =
600 showDirsFirstPref.getId(),
601 ((Boolean)showDirsFirstPref.getDefaultValue()).booleanValue());
602 final NavigationSortMode sortMode =
603 NavigationSortMode.fromId(
604 prefs.getInt(sortModePref.getId(),
605 ((ObjectIdentifier)sortModePref.getDefaultValue()).getId()));
606 Collections.sort(files, new Comparator<FileSystemObject>() {
608 public int compare(FileSystemObject lhs, FileSystemObject rhs) {
609 //Parent directory always goes first
610 boolean isLhsParentDirectory = lhs instanceof ParentDirectory;
611 boolean isRhsParentDirectory = rhs instanceof ParentDirectory;
612 if (isLhsParentDirectory || isRhsParentDirectory) {
613 if (isLhsParentDirectory && isRhsParentDirectory) {
616 return (isLhsParentDirectory) ? -1 : 1;
619 //Need to sort directory first?
621 boolean isLhsDirectory = FileHelper.isDirectory(lhs);
622 boolean isRhsDirectory = FileHelper.isDirectory(rhs);
623 if (isLhsDirectory || isRhsDirectory) {
624 if (isLhsDirectory && isRhsDirectory) {
626 return FileHelper.doCompare(lhs, rhs, sortMode);
628 return (isLhsDirectory) ? -1 : 1;
633 return FileHelper.doCompare(lhs, rhs, sortMode);
644 * Determines if a file system object complies w/ a user's display preferences implying that
645 * the user is interested in this file
646 * (sort mode, hidden files, ...).
648 * @param fso The file
649 * @param restrictions The restrictions to apply when displaying files
650 * @param chRooted If app run with no privileges
651 * @return boolean indicating user's interest
653 public static boolean compliesWithDisplayPreferences(
654 FileSystemObject fso, Map<DisplayRestrictions, Object> restrictions, boolean chRooted) {
655 //Retrieve user preferences
656 SharedPreferences prefs = Preferences.getSharedPreferences();
657 FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN;
658 FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM;
659 FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS;
662 if (!prefs.getBoolean(
663 showHiddenPref.getId(),
664 ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
665 if (fso.isHidden()) {
671 if (!prefs.getBoolean(
672 showSystemPref.getId(),
673 ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
674 if (fso instanceof SystemFile) {
680 if (!prefs.getBoolean(
681 showSymlinksPref.getId(),
682 ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
683 if (fso instanceof Symlink) {
688 // Restrictions (only apply to files)
689 if (restrictions != null) {
690 if (!isDirectory(fso)) {
691 if (!isDisplayAllowed(fso, restrictions)) {
702 * Method that check if a file should be displayed according to the restrictions
704 * @param fso The file system object to check
705 * @param restrictions The restrictions map
706 * @return boolean If the file should be displayed
708 private static boolean isDisplayAllowed(
709 FileSystemObject fso, Map<DisplayRestrictions, Object> restrictions) {
710 Iterator<DisplayRestrictions> it = restrictions.keySet().iterator();
711 while (it.hasNext()) {
712 DisplayRestrictions restriction = it.next();
713 Object value = restrictions.get(restriction);
717 switch (restriction) {
718 case CATEGORY_TYPE_RESTRICTION:
719 if (value instanceof MimeTypeCategory) {
720 MimeTypeCategory cat1 = (MimeTypeCategory)value;
721 // NOTE: We don't need the context here, because mime-type
722 // database should be loaded prior to this call
723 MimeTypeCategory cat2 = MimeTypeHelper.getCategory(null, fso);
724 if (cat1.compareTo(cat2) != 0) {
730 case MIME_TYPE_RESTRICTION:
731 String[] mimeTypes = null;
732 if (value instanceof String) {
733 mimeTypes = new String[] {(String) value};
734 } else if (value instanceof String[]) {
735 mimeTypes = (String[]) value;
737 if (mimeTypes != null) {
738 boolean matches = false;
739 for (String mimeType : mimeTypes) {
740 if (mimeType.compareTo(MimeTypeHelper.ALL_MIME_TYPES) == 0) {
744 // NOTE: We don't need the context here, because mime-type
745 // database should be loaded prior to this call
746 if (MimeTypeHelper.matchesMimeType(null, fso, mimeType)) {
757 case SIZE_RESTRICTION:
758 if (value instanceof Long) {
759 Long maxSize = (Long)value;
760 if (fso.getSize() > maxSize.longValue()) {
766 case DIRECTORY_ONLY_RESTRICTION:
767 if (value instanceof Boolean) {
768 Boolean directoryOnly = (Boolean) value;
769 if (directoryOnly.booleanValue() && !FileHelper.isDirectory(fso)) {
775 case LOCAL_FILESYSTEM_ONLY_RESTRICTION:
776 if (value instanceof Boolean) {
777 Boolean localOnly = (Boolean)value;
778 if (localOnly.booleanValue()) {
779 /** TODO Needed when CMFM gets networking **/
792 * Method that resolve the symbolic links of the list of files passed as argument.<br />
793 * This method invokes the {@link ResolveLinkCommand} in those files that have a valid
796 * @param context The current context
797 * @param files The listed files
799 public static void resolveSymlinks(Context context, List<FileSystemObject> files) {
800 int cc = files.size();
801 for (int i = 0; i < cc; i++) {
802 FileSystemObject fso = files.get(i);
803 resolveSymlink(context, fso);
808 * Method that resolves the symbolic link of a file passed in as argument.<br />
809 * This method invokes the {@link ResolveLinkCommand} on the file that has a valid
812 * @param context The current context
813 * @param fso FileSystemObject to resolve symlink
815 public static void resolveSymlink(Context context, FileSystemObject fso) {
816 if (fso instanceof Symlink && ((Symlink)fso).getLinkRef() == null) {
818 FileSystemObject symlink =
819 CommandHelper.resolveSymlink(context, fso.getFullPath(), null);
820 ((Symlink)fso).setLinkRef(symlink);
821 } catch (Throwable ex) {/**NON BLOCK**/}
826 * Method that do a comparison between 2 file system objects.
828 * @param fso1 The first file system objects
829 * @param fso2 The second file system objects
830 * @param mode The sort mode
831 * @return int a negative integer if {@code fso1} is less than {@code fso2};
832 * a positive integer if {@code fso1} is greater than {@code fso2};
833 * 0 if {@code fso1} has the same order as {@code fso2}.
835 public static int doCompare(
836 final FileSystemObject fso1,
837 final FileSystemObject fso2,
838 final NavigationSortMode mode) {
840 // Retrieve the user preference for case sensitive sort
841 boolean caseSensitive =
842 Preferences.getSharedPreferences().
844 FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.getId(),
845 ((Boolean)FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.
846 getDefaultValue()).booleanValue());
849 if (mode.getId() == NavigationSortMode.NAME_ASC.getId()) {
850 if (!caseSensitive) {
851 return fso1.getName().compareToIgnoreCase(fso2.getName());
853 return fso1.getName().compareTo(fso2.getName());
856 if (mode.getId() == NavigationSortMode.NAME_DESC.getId()) {
857 if (!caseSensitive) {
858 return fso1.getName().compareToIgnoreCase(fso2.getName()) * -1;
860 return fso1.getName().compareTo(fso2.getName()) * -1;
864 if (mode.getId() == NavigationSortMode.DATE_ASC.getId()) {
865 return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime());
868 if (mode.getId() == NavigationSortMode.DATE_DESC.getId()) {
869 return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime()) * -1;
873 if (mode.getId() == NavigationSortMode.SIZE_ASC.getId()) {
874 return Long.compare(fso1.getSize(), fso2.getSize());
877 if (mode.getId() == NavigationSortMode.SIZE_DESC.getId()) {
878 return Long.compare(fso1.getSize(), fso2.getSize()) * -1;
882 if (mode.getId() == NavigationSortMode.TYPE_ASC.getId()) {
883 // Shouldn't need context here, mimetypes should be loaded
884 return MimeTypeHelper.compareFSO(null, fso1, fso2);
887 if (mode.getId() == NavigationSortMode.TYPE_DESC.getId()) {
888 // Shouldn't need context here, mimetypes should be loaded
889 return MimeTypeHelper.compareFSO(null, fso1, fso2) * -1;
892 //Comparison between files directly
893 return fso1.compareTo(fso2);
897 * Method that add to the path the trailing slash
899 * @param path The path
900 * @return String The path with the trailing slash
902 public static String addTrailingSlash(String path) {
903 if (path == null) return null;
904 return path.endsWith(File.separator) ? path : path + File.separator;
908 * Method that cleans the path and removes the trailing slash
910 * @param path The path to clean
911 * @return String The path without the trailing slash
913 public static String removeTrailingSlash(String path) {
914 if (path == null) return null;
915 if (path.trim().compareTo(ROOT_DIRECTORY) == 0) return path;
916 if (path.endsWith(File.separator)) {
917 return path.substring(0, path.length()-1);
923 * Method that creates a new name based on the name of the {@link FileSystemObject}
924 * that is not current used by the filesystem.
926 * @param ctx The current context
927 * @param files The list of files of the current directory
928 * @param attemptedName The attempted name
929 * @param regexp The resource of the regular expression to create the new name
930 * @return String The new non-existing name
932 public static String createNonExistingName(
933 final Context ctx, final List<FileSystemObject> files,
934 final String attemptedName, int regexp) {
935 // Find a non-exiting name
936 String newName = attemptedName;
937 if (!isNameExists(files, newName)) return newName;
939 String name = FileHelper.getName(newName);
940 String ext = FileHelper.getExtension(newName);
942 ext = ""; //$NON-NLS-1$
944 ext = "." + ext; //$NON-NLS-1$
946 newName = ctx.getString(regexp, name, ext);
947 } while (isNameExists(files, newName));
952 * Method that checks if a name exists in the current directory.
954 * @param files The list of files of the current directory
955 * @param name The name to check
956 * @return boolean Indicate if the name exists in the current directory
958 public static boolean isNameExists(List<FileSystemObject> files, String name) {
959 //Verify if the name exists in the current file list
960 int cc = files.size();
961 for (int i = 0; i < cc; i++) {
962 FileSystemObject fso = files.get(i);
963 if (fso.getName().compareTo(name) == 0) {
971 * Method that returns is a {@link FileSystemObject} can be handled by this application
972 * allowing the uncompression of the file
974 * @param fso The file system object to verify
975 * @return boolean If the file is supported
977 @SuppressWarnings("nls")
978 public static boolean isSupportedUncompressedFile(FileSystemObject fso) {
979 // Valid uncompressed formats are:
980 final String[] VALID =
982 "tar", "tgz", "tar.gz", "tar.bz2", "tar.lzma",
983 "zip", "gz", "bz2", "lzma", "xz", "Z", "rar"
985 // Null values for required commands
986 final String[] OPT_KEYS =
988 null, null, null, null, null,
989 "unzip", null, null, "unlzma", "unxz", "uncompress", "unrar"
992 // Check that have a valid file
993 if (fso == null) return false;
995 // Only regular files
996 if (isDirectory(fso) || fso instanceof Symlink) {
999 // No in virtual filesystems
1000 if (fso.isSecure() || fso.isRemote()) {
1003 String ext = getExtension(fso);
1005 int cc = VALID.length;
1006 for (int i = 0; i < cc; i++) {
1007 if (VALID[i].compareToIgnoreCase(ext) == 0) {
1008 // Is the command present
1009 if (OPT_KEYS[i] != null &&
1010 FileManagerApplication.hasOptionalCommand(OPT_KEYS[i])) {
1021 * Method that converts an absolute path to a relative path
1023 * @param path The absolute path to convert
1024 * @param relativeTo The absolute path from which make path relative to (a folder)
1025 * @return String The relative path
1027 public static String toRelativePath(String path, String relativeTo) {
1028 // Normalize the paths
1029 File f1 = new File(path);
1030 File f2 = new File(relativeTo);
1031 String s1 = f1.getAbsolutePath();
1032 String s2 = f2.getAbsolutePath();
1033 if (!s2.endsWith(File.separator)) {
1034 s2 = s2 + File.separator;
1037 // If s2 contains s1 then the relative is replace of the start of the path
1038 if (s1.startsWith(s2)) {
1039 return s1.substring(s2.length());
1042 StringBuffer relative = new StringBuffer();
1044 File f3 = new File(s2);
1045 relative.append(String.format("..%s", File.separator)); //$NON-NLS-1$
1046 s2 = f3.getParent() + File.separator;
1047 } while (!s1.startsWith(s2) && !s1.startsWith(new File(s2).getAbsolutePath()));
1048 s2 = new File(s2).getAbsolutePath();
1049 return relative.toString() + s1.substring(s2.length());
1053 * Method that creates a {@link FileSystemObject} from a {@link File}
1055 * @param file The file or folder reference
1056 * @return FileSystemObject The file system object reference
1058 public static FileSystemObject createFileSystemObject(File file) {
1060 // The user and group name of the files. Use the defaults one for sdcards
1061 final String USER = "root"; //$NON-NLS-1$
1062 final String GROUP = "sdcard_r"; //$NON-NLS-1$
1064 // The user and group name of the files. In ChRoot, aosp give restrict access to
1065 // this user and group. This applies for permission also. This has no really much
1066 // interest if we not allow to change the permissions
1067 AID userAID = AIDHelper.getAIDFromName(USER);
1068 AID groupAID = AIDHelper.getAIDFromName(GROUP);
1069 User user = new User(userAID.getId(), userAID.getName());
1070 Group group = new Group(groupAID.getId(), groupAID.getName());
1071 Permissions perm = file.isDirectory()
1072 ? Permissions.createDefaultFolderPermissions()
1073 : Permissions.createDefaultFilePermissions();
1075 // Build a directory?
1076 Date lastModified = new Date(file.lastModified());
1077 if (file.isDirectory()) {
1083 lastModified, lastModified, lastModified); // The only date we have
1086 // Build a regular file
1093 lastModified, lastModified, lastModified); // The only date we have
1094 } catch (Exception e) {
1095 Log.e(TAG, "Exception retrieving the fso", e); //$NON-NLS-1$
1101 * Method that copies recursively to the destination
1103 * @param src The source file or folder
1104 * @param dst The destination file or folder
1105 * @param bufferSize The buffer size for the operation
1106 * @return boolean If the operation complete successfully
1107 * @throws ExecutionException If a problem was detected in the operation
1109 public static boolean copyRecursive(
1110 final File src, final File dst, int bufferSize, Program program)
1111 throws ExecutionException, CancelledOperationException {
1112 if (src.isDirectory()) {
1113 // Create the directory
1114 if (dst.exists() && !dst.isDirectory()) {
1116 String.format("Failed to check destionation dir: %s", dst)); //$NON-NLS-1$
1117 throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$
1119 if (!dst.exists()) {
1121 Log.e(TAG, String.format("Failed to create directory: %s", dst)); //$NON-NLS-1$
1125 File[] files = src.listFiles();
1126 if (files != null) {
1127 for (int i = 0; i < files.length; i++) {
1128 // Short circuit if we've been cancelled. Show's over :(
1129 if (program.isCancelled()) {
1130 throw new CancelledOperationException();
1133 if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize,
1140 // Copy the directory
1141 if (!bufferedCopy(src, dst,bufferSize, program)) {
1149 * Method that copies a file
1151 * @param src The source file
1152 * @param dst The destination file
1153 * @param bufferSize The buffer size for the operation
1154 * @return boolean If the operation complete successfully
1156 public static boolean bufferedCopy(final File src, final File dst,
1157 int bufferSize, Program program)
1158 throws ExecutionException, CancelledOperationException {
1159 BufferedInputStream bis = null;
1160 BufferedOutputStream bos = null;
1162 bis = new BufferedInputStream(new FileInputStream(src), bufferSize);
1163 bos = new BufferedOutputStream(new FileOutputStream(dst), bufferSize);
1165 byte[] data = new byte[bufferSize];
1166 while ((read = bis.read(data, 0, bufferSize)) != -1) {
1167 // Short circuit if we've been cancelled. Show's over :(
1168 if (program.isCancelled()) {
1169 throw new CancelledOperationException();
1171 bos.write(data, 0, read);
1175 } catch (Throwable e) {
1177 String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$
1179 // Check if this error is an out of space exception and throw that specifically.
1180 // ENOSPC -> Error No Space
1181 if (e.getCause() instanceof ErrnoException
1182 && ((ErrnoException)e.getCause()).errno == OsConstants.ENOSPC) {
1183 throw new ExecutionException(R.string.msgs_no_disk_space);
1184 } if (e instanceof CancelledOperationException) {
1185 // If the user cancelled this operation, let it through.
1186 throw (CancelledOperationException)e;
1195 } catch (Throwable e) {/**NON BLOCK**/}
1200 } catch (Throwable e) {/**NON BLOCK**/}
1201 if (program.isCancelled()) {
1202 if (!dst.delete()) {
1203 Log.e(TAG, "Failed to delete the dest file: " + dst);
1210 * Method that deletes a folder recursively
1212 * @param folder The folder to delete
1213 * @return boolean If the folder was deleted
1215 public static boolean deleteFolder(File folder) {
1216 File[] files = folder.listFiles();
1217 if (files != null) {
1218 for (int i = 0; i < files.length; i++) {
1219 if (files[i].isDirectory()) {
1220 if (!deleteFolder(files[i])) {
1224 if (!files[i].delete()) {
1230 return folder.delete();
1234 * Method that returns the canonical/absolute path of the path.<br/>
1235 * This method performs path resolution
1237 * @param path The path to convert
1238 * @return String The canonical/absolute path
1240 public static String getAbsPath(String path) {
1242 return new File(path).getCanonicalPath();
1243 } catch (Exception e) {
1244 return new File(path).getAbsolutePath();
1249 * Method that returns the .nomedia file
1251 * @param fso The folder that contains the .nomedia file
1252 * @return File The .nomedia file
1254 public static File getNoMediaFile(FileSystemObject fso) {
1257 file = new File(fso.getFullPath()).getCanonicalFile();
1258 } catch (Exception e) {
1259 file = new File(fso.getFullPath()).getAbsoluteFile();
1261 return new File(file, ".nomedia").getAbsoluteFile(); //$NON-NLS-1$
1265 * Method that ensures that the actual console has access to read the
1266 * {@link FileSystemObject} passed.
1268 * @param console The console
1269 * @param fso The {@link FileSystemObject} to check
1270 * @param executable The executable to associate to the {@link InsufficientPermissionsException}
1271 * @throws InsufficientPermissionsException If the console doesn't have enough rights
1273 public static void ensureReadAccess(
1274 Console console, FileSystemObject fso, SyncResultExecutable executable)
1275 throws InsufficientPermissionsException {
1277 if (console.isPrivileged()) {
1278 // Should have access
1281 if (console instanceof JavaConsole &&
1282 StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1283 // Java console runs in chrooted environment, and sdcard are always readable
1286 Identity identity = console.getIdentity();
1287 if (identity == null) {
1288 throw new InsufficientPermissionsException(executable);
1290 Permissions permissions = fso.getPermissions();
1291 User user = fso.getUser();
1292 Group group = fso.getGroup();
1293 List<Group> groups = identity.getGroups();
1294 if ( permissions == null || user == null || group == null) {
1295 throw new InsufficientPermissionsException(executable);
1298 if (permissions.getOthers().isRead()) {
1302 if (user.getId() == identity.getUser().getId() && permissions.getUser().isRead()) {
1306 if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isRead()) {
1310 int cc = groups.size();
1311 for (int i = 0; i < cc; i++) {
1312 Group g = groups.get(i);
1313 if (group.getId() == g.getId() && permissions.getGroup().isRead()) {
1318 } catch (Exception e) {
1319 Log.e(TAG, "Failed to check fso read permission,", e); //$NON-NLS-1$
1321 throw new InsufficientPermissionsException(executable);
1325 * Method that ensures that the actual console has access to write the
1326 * {@link FileSystemObject} passed.
1328 * @param console The console
1329 * @param fso The {@link FileSystemObject} to check
1330 * @param executable The executable to associate to the {@link InsufficientPermissionsException}
1331 * @throws InsufficientPermissionsException If the console doesn't have enough rights
1333 public static void ensureWriteAccess(
1334 Console console, FileSystemObject fso, SyncResultExecutable executable)
1335 throws InsufficientPermissionsException {
1337 if (console.isPrivileged()) {
1338 // Should have access
1341 if (console instanceof JavaConsole &&
1342 StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1343 // Java console runs in chrooted environment, and sdcard are always writeable
1346 Identity identity = console.getIdentity();
1347 if (identity == null) {
1348 throw new InsufficientPermissionsException(executable);
1350 Permissions permissions = fso.getPermissions();
1351 User user = fso.getUser();
1352 Group group = fso.getGroup();
1353 List<Group> groups = identity.getGroups();
1354 if ( permissions == null || user == null || group == null) {
1355 throw new InsufficientPermissionsException(executable);
1358 if (permissions.getOthers().isWrite()) {
1362 if (user.getId() == identity.getUser().getId() && permissions.getUser().isWrite()) {
1366 if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isWrite()) {
1370 int cc = groups.size();
1371 for (int i = 0; i < cc; i++) {
1372 Group g = groups.get(i);
1373 if (group.getId() == g.getId() && permissions.getGroup().isWrite()) {
1378 } catch (Exception e) {
1379 Log.e(TAG, "Failed to check fso write permission,", e); //$NON-NLS-1$
1381 throw new InsufficientPermissionsException(executable);
1385 * Method that ensures that the actual console has access to execute the
1386 * {@link FileSystemObject} passed.
1388 * @param console The console
1389 * @param fso The {@link FileSystemObject} to check
1390 * @param executable The executable to associate to the {@link InsufficientPermissionsException}
1391 * @throws InsufficientPermissionsException If the console doesn't have enough rights
1393 public static void ensureExecuteAccess(
1394 Console console, FileSystemObject fso, SyncResultExecutable executable)
1395 throws InsufficientPermissionsException {
1397 if (console.isPrivileged()) {
1398 // Should have access
1401 if (console instanceof JavaConsole &&
1402 StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1403 // Java console runs in chrooted environment, and sdcard are never executable
1404 throw new InsufficientPermissionsException(executable);
1406 Identity identity = console.getIdentity();
1407 if (identity == null) {
1408 throw new InsufficientPermissionsException(executable);
1410 Permissions permissions = fso.getPermissions();
1411 User user = fso.getUser();
1412 Group group = fso.getGroup();
1413 List<Group> groups = identity.getGroups();
1414 if ( permissions == null || user == null || group == null) {
1415 throw new InsufficientPermissionsException(executable);
1418 if (permissions.getOthers().isExecute()) {
1422 if (user.getId() == identity.getUser().getId() && permissions.getUser().isExecute()) {
1426 if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isExecute()) {
1430 int cc = groups.size();
1431 for (int i = 0; i < cc; i++) {
1432 Group g = groups.get(i);
1433 if (group.getId() == g.getId() && permissions.getGroup().isExecute()) {
1438 } catch (Exception e) {
1439 Log.e(TAG, "Failed to check fso execute permission,", e); //$NON-NLS-1$
1441 throw new InsufficientPermissionsException(executable);
1445 * Method that formats a filetime date with the specific system settings
1447 * @param ctx The current context
1448 * @param filetime The filetime date
1449 * @return String The filetime date formatted
1451 public static String formatFileTime(Context ctx, Date filetime) {
1452 synchronized (DATETIME_SYNC) {
1453 if (sReloadDateTimeFormats) {
1454 String defaultValue =
1455 ((ObjectStringIdentifier)FileManagerSettings.
1456 SETTINGS_FILETIME_FORMAT_MODE.getDefaultValue()).getId();
1457 String id = FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE.getId();
1458 sFiletimeFormatMode =
1459 FileTimeFormatMode.fromId(
1460 Preferences.getSharedPreferences().getString(id, defaultValue));
1461 if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) {
1462 sDateTimeFormatOrder = ctx.getString(R.string.datetime_format_order);
1463 sDateFormat = android.text.format.DateFormat.getDateFormat(ctx);
1464 sTimeFormat = android.text.format.DateFormat.getTimeFormat(ctx);
1465 } else if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.LOCALE) == 0) {
1467 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
1469 sDateFormat = new SimpleDateFormat(sFiletimeFormatMode.getFormat());
1471 sReloadDateTimeFormats = false;
1475 // Apply the user settings
1476 if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) {
1477 String date = sDateFormat.format(filetime);
1478 String time = sTimeFormat.format(filetime);
1479 return String.format(sDateTimeFormatOrder, date, time);
1481 return sDateFormat.format(filetime);
1486 * Method that create a new temporary filename
1488 * @param external If the file should be created in the external or the internal cache dir
1490 public static synchronized File createTempFilename(Context context, boolean external) {
1491 File tempDirectory = external ? context.getExternalCacheDir() : context.getCacheDir();
1494 UUID uuid = UUID.randomUUID();
1495 tempFile = new File(tempDirectory, uuid.toString());
1496 } while (tempFile.exists());
1501 * Method that delete a file or a folder
1503 * @param src The file or folder to delete
1504 * @return boolean If the operation was successfully
1506 public static boolean deleteFileOrFolder(File src) {
1507 if (src.isDirectory()) {
1508 return FileHelper.deleteFolder(src);
1510 return src.delete();
1514 * Method that checks if the source file passed belongs to (is under) the directory passed
1516 * @param src The file to check
1517 * @param dir The parent file to check
1518 * @return boolean If the source belongs to the directory
1520 public static boolean belongsToDirectory(File src, File dir) {
1524 return src.getAbsolutePath().startsWith(dir.getAbsolutePath());
1528 * Method that checks if both path are the same (by checking sensitive cases).
1530 * @param src The source path
1531 * @param dst The destination path
1532 * @return boolean If both are the same path
1534 public static boolean isSamePath(String src, String dst) {
1535 // This is only true if both are exactly the same path or the same file in insensitive
1537 File o1 = new File(src);
1538 File o2 = new File(dst);
1539 return o1.equals(o2);