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++) {
209 double cleanSize = Math.round(aux * 100);
210 return Double.toString(cleanSize / 100) +
211 " " + res.getString(magnitude[i]); //$NON-NLS-1$
216 double cleanSize = Math.round(aux * 100);
217 return Double.toString(cleanSize / 100) +
218 " " + res.getString(magnitude[cc - 1]); //$NON-NLS-1$
222 * Method that returns if the file system object if the root directory.
224 * @param fso The file system object to check
225 * @return boolean if the file system object if the root directory
227 public static boolean isRootDirectory(FileSystemObject fso) {
228 if (fso.getName() == null) return true;
229 return fso.getName().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
233 * Method that returns if the folder if the root directory.
235 * @param folder The folder
236 * @return boolean if the folder if the root directory
238 public static boolean isRootDirectory(String folder) {
239 if (folder == null) return true;
240 return isRootDirectory(new File(folder));
244 * Method that returns if the folder if the root directory.
246 * @param folder The folder
247 * @return boolean if the folder if the root directory
249 public static boolean isRootDirectory(File folder) {
250 if (folder.getPath() == null) return true;
251 return folder.getPath().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
255 * Method that returns if the parent file system object if the root directory.
257 * @param fso The parent file system object to check
258 * @return boolean if the parent file system object if the root directory
260 public static boolean isParentRootDirectory(FileSystemObject fso) {
261 if (fso.getParent() == null) return true;
262 return fso.getParent().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
266 * Method that returns the name without the extension of a file system object.
268 * @param fso The file system object
269 * @return The name without the extension of the file system object.
271 public static String getName(FileSystemObject fso) {
272 return getName(fso.getName());
276 * Method that returns the name without the extension of a file system object.
278 * @param name The name of file system object
279 * @return The name without the extension of the file system object.
281 public static String getName(String name) {
282 String ext = getExtension(name);
283 if (ext == null) return name;
284 return name.substring(0, name.length() - ext.length() - 1);
288 * Method that returns the extension of a file system object.
290 * @param fso The file system object
291 * @return The extension of the file system object, or <code>null</code>
292 * if <code>fso</code> has no extension.
294 public static String getExtension(FileSystemObject fso) {
295 return getExtension(fso.getName());
299 * Method that returns the extension of a file system object.
301 * @param name The name of file system object
302 * @return The extension of the file system object, or <code>null</code>
303 * if <code>fso</code> has no extension.
305 public static String getExtension(String name) {
306 final char dot = '.';
307 int pos = name.lastIndexOf(dot);
308 if (pos == -1 || pos == 0) { // Hidden files doesn't have extensions
312 // Exceptions to the general extraction method
313 int cc = COMPRESSED_TAR.length;
314 for (int i = 0; i < cc; i++) {
315 if (name.endsWith("." + COMPRESSED_TAR[i])) { //$NON-NLS-1$
316 return COMPRESSED_TAR[i];
320 // General extraction method
321 return name.substring(pos + 1);
325 * Method that returns the parent directory of a file/folder
327 * @param path The file/folder
328 * @return String The parent directory
330 public static String getParentDir(String path) {
331 return getParentDir(new File(path));
335 * Method that returns the parent directory of a file/folder
337 * @param path The file/folder
338 * @return String The parent directory
340 public static String getParentDir(File path) {
341 String parent = path.getParent();
342 if (parent == null && path.getAbsolutePath().compareTo(FileHelper.ROOT_DIRECTORY) != 0) {
343 parent = FileHelper.ROOT_DIRECTORY;
349 * Method that evaluates if a path is relative.
351 * @param src The path to check
352 * @return boolean If a path is relative
354 public static boolean isRelativePath(String src) {
355 if (src.startsWith(CURRENT_DIRECTORY + File.separator)) {
358 if (src.startsWith(PARENT_DIRECTORY + File.separator)) {
361 if (src.indexOf(File.separator + CURRENT_DIRECTORY + File.separator) != -1) {
364 if (src.indexOf(File.separator + PARENT_DIRECTORY + File.separator) != -1) {
367 if (!src.startsWith(ROOT_DIRECTORY)) {
374 * Method that check if the file system object is a {@link Symlink} and
375 * has a link reference.
377 * @param fso The file system object to check
378 * @return boolean If file system object the has a link reference
380 public static boolean hasSymlinkRef(FileSystemObject fso) {
381 if (fso instanceof Symlink) {
382 return ((Symlink)fso).getLinkRef() != null;
388 * Method that check if the file system object is a {@link Symlink} and
389 * the link reference is a directory.
391 * @param fso The file system object to check
392 * @return boolean If file system object the link reference is a directory
394 public static boolean isSymlinkRefDirectory(FileSystemObject fso) {
395 if (!hasSymlinkRef(fso)) {
398 return ((Symlink)fso).getLinkRef() instanceof Directory;
402 * Method that check if the file system object is a {@link Symlink} and
403 * the link reference is a system file.
405 * @param fso The file system object to check
406 * @return boolean If file system object the link reference is a system file
408 public static boolean isSymlinkRefSystemFile(FileSystemObject fso) {
409 if (!hasSymlinkRef(fso)) {
412 return ((Symlink)fso).getLinkRef() instanceof SystemFile;
416 * Method that check if the file system object is a {@link Symlink} and
417 * the link reference is a block device.
419 * @param fso The file system object to check
420 * @return boolean If file system object the link reference is a block device
422 public static boolean isSymlinkRefBlockDevice(FileSystemObject fso) {
423 if (!hasSymlinkRef(fso)) {
426 return ((Symlink)fso).getLinkRef() instanceof BlockDevice;
430 * Method that check if the file system object is a {@link Symlink} and
431 * the link reference is a character device.
433 * @param fso The file system object to check
434 * @return boolean If file system object the link reference is a character device
436 public static boolean isSymlinkRefCharacterDevice(FileSystemObject fso) {
437 if (!hasSymlinkRef(fso)) {
440 return ((Symlink)fso).getLinkRef() instanceof CharacterDevice;
444 * Method that check if the file system object is a {@link Symlink} and
445 * the link reference is a named pipe.
447 * @param fso The file system object to check
448 * @return boolean If file system object the link reference is a named pipe
450 public static boolean isSymlinkRefNamedPipe(FileSystemObject fso) {
451 if (!hasSymlinkRef(fso)) {
454 return ((Symlink)fso).getLinkRef() instanceof NamedPipe;
458 * Method that check if the file system object is a {@link Symlink} and
459 * the link reference is a domain socket.
461 * @param fso The file system object to check
462 * @return boolean If file system object the link reference is a domain socket
464 public static boolean isSymlinkRefDomainSocket(FileSystemObject fso) {
465 if (!hasSymlinkRef(fso)) {
468 return ((Symlink)fso).getLinkRef() instanceof DomainSocket;
472 * Method that checks if a file system object is a directory (real o symlink).
474 * @param fso The file system object to check
475 * @return boolean If file system object is a directory
477 public static boolean isDirectory(FileSystemObject fso) {
478 if (fso instanceof Directory) {
481 if (isSymlinkRefDirectory(fso)) {
488 * Method that checks if a file system object is a system file (real o symlink).
490 * @param fso The file system object to check
491 * @return boolean If file system object is a system file
493 public static boolean isSystemFile(FileSystemObject fso) {
494 if (fso instanceof SystemFile) {
497 if (isSymlinkRefSystemFile(fso)) {
504 * Method that returns the real reference of a file system object
505 * (the reference file system object if the file system object is a symlink.
506 * Otherwise the same reference).
508 * @param fso The file system object to check
509 * @return FileSystemObject The real file system object reference
511 public static FileSystemObject getReference(FileSystemObject fso) {
512 if (hasSymlinkRef(fso)) {
513 return ((Symlink)fso).getLinkRef();
519 * Method that applies the configuration modes to the listed files
520 * (sort mode, hidden files, ...).
522 * @param files The listed files
523 * @param restrictions The restrictions to apply when displaying files
524 * @param chRooted If app run with no privileges
525 * @return List<FileSystemObject> The applied mode listed files
527 public static List<FileSystemObject> applyUserPreferences(
528 List<FileSystemObject> files, Map<DisplayRestrictions,
529 Object> restrictions, boolean chRooted) {
530 return applyUserPreferences(files, restrictions, false, chRooted);
534 * Method that applies the configuration modes to the listed files
535 * (sort mode, hidden files, ...).
537 * @param files The listed files
538 * @param restrictions The restrictions to apply when displaying files
539 * @param noSort If sort must be applied
540 * @param chRooted If app run with no privileges
541 * @return List<FileSystemObject> The applied mode listed files
543 public static List<FileSystemObject> applyUserPreferences(
544 List<FileSystemObject> files, Map<DisplayRestrictions, Object> restrictions,
545 boolean noSort, boolean chRooted) {
546 //Retrieve user preferences
547 SharedPreferences prefs = Preferences.getSharedPreferences();
548 FileManagerSettings sortModePref = FileManagerSettings.SETTINGS_SORT_MODE;
549 FileManagerSettings showDirsFirstPref = FileManagerSettings.SETTINGS_SHOW_DIRS_FIRST;
550 FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN;
551 FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM;
552 FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS;
554 //Remove all unnecessary files (no required by the user)
555 int cc = files.size();
556 for (int i = cc - 1; i >= 0; i--) {
557 FileSystemObject file = files.get(i);
560 if (!prefs.getBoolean(
561 showHiddenPref.getId(),
562 ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
563 if (file.isHidden()) {
570 if (!prefs.getBoolean(
571 showSystemPref.getId(),
572 ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
573 if (file instanceof SystemFile) {
580 if (!prefs.getBoolean(
581 showSymlinksPref.getId(),
582 ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
583 if (file instanceof Symlink) {
589 // Restrictions (only apply to files)
590 if (restrictions != null) {
591 if (!isDirectory(file)) {
592 if (!isDisplayAllowed(file, restrictions)) {
602 final boolean showDirsFirst =
604 showDirsFirstPref.getId(),
605 ((Boolean)showDirsFirstPref.getDefaultValue()).booleanValue());
606 final NavigationSortMode sortMode =
607 NavigationSortMode.fromId(
608 prefs.getInt(sortModePref.getId(),
609 ((ObjectIdentifier)sortModePref.getDefaultValue()).getId()));
610 Collections.sort(files, new Comparator<FileSystemObject>() {
612 public int compare(FileSystemObject lhs, FileSystemObject rhs) {
613 //Parent directory always goes first
614 boolean isLhsParentDirectory = lhs instanceof ParentDirectory;
615 boolean isRhsParentDirectory = rhs instanceof ParentDirectory;
616 if (isLhsParentDirectory || isRhsParentDirectory) {
617 if (isLhsParentDirectory && isRhsParentDirectory) {
620 return (isLhsParentDirectory) ? -1 : 1;
623 //Need to sort directory first?
625 boolean isLhsDirectory = FileHelper.isDirectory(lhs);
626 boolean isRhsDirectory = FileHelper.isDirectory(rhs);
627 if (isLhsDirectory || isRhsDirectory) {
628 if (isLhsDirectory && isRhsDirectory) {
630 return FileHelper.doCompare(lhs, rhs, sortMode);
632 return (isLhsDirectory) ? -1 : 1;
637 return FileHelper.doCompare(lhs, rhs, sortMode);
648 * Determines if a file system object complies w/ a user's display preferences implying that
649 * the user is interested in this file
650 * (sort mode, hidden files, ...).
652 * @param fso The file
653 * @param restrictions The restrictions to apply when displaying files
654 * @param chRooted If app run with no privileges
655 * @return boolean indicating user's interest
657 public static boolean compliesWithDisplayPreferences(
658 FileSystemObject fso, Map<DisplayRestrictions, Object> restrictions, boolean chRooted) {
659 //Retrieve user preferences
660 SharedPreferences prefs = Preferences.getSharedPreferences();
661 FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN;
662 FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM;
663 FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS;
666 if (!prefs.getBoolean(
667 showHiddenPref.getId(),
668 ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
669 if (fso.isHidden()) {
675 if (!prefs.getBoolean(
676 showSystemPref.getId(),
677 ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
678 if (fso instanceof SystemFile) {
684 if (!prefs.getBoolean(
685 showSymlinksPref.getId(),
686 ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
687 if (fso instanceof Symlink) {
692 // Restrictions (only apply to files)
693 if (restrictions != null) {
694 if (!isDirectory(fso)) {
695 if (!isDisplayAllowed(fso, restrictions)) {
706 * Method that check if a file should be displayed according to the restrictions
708 * @param fso The file system object to check
709 * @param restrictions The restrictions map
710 * @return boolean If the file should be displayed
712 private static boolean isDisplayAllowed(
713 FileSystemObject fso, Map<DisplayRestrictions, Object> restrictions) {
714 Iterator<DisplayRestrictions> it = restrictions.keySet().iterator();
715 while (it.hasNext()) {
716 DisplayRestrictions restriction = it.next();
717 Object value = restrictions.get(restriction);
721 switch (restriction) {
722 case CATEGORY_TYPE_RESTRICTION:
723 if (value instanceof MimeTypeCategory) {
724 MimeTypeCategory cat1 = (MimeTypeCategory)value;
725 // NOTE: We don't need the context here, because mime-type
726 // database should be loaded prior to this call
727 MimeTypeCategory cat2 = MimeTypeHelper.getCategory(null, fso);
728 if (cat1.compareTo(cat2) != 0) {
734 case MIME_TYPE_RESTRICTION:
735 String[] mimeTypes = null;
736 if (value instanceof String) {
737 mimeTypes = new String[] {(String) value};
738 } else if (value instanceof String[]) {
739 mimeTypes = (String[]) value;
741 if (mimeTypes != null) {
742 boolean matches = false;
743 for (String mimeType : mimeTypes) {
744 if (mimeType.compareTo(MimeTypeHelper.ALL_MIME_TYPES) == 0) {
748 // NOTE: We don't need the context here, because mime-type
749 // database should be loaded prior to this call
750 if (MimeTypeHelper.matchesMimeType(null, fso, mimeType)) {
761 case SIZE_RESTRICTION:
762 if (value instanceof Long) {
763 Long maxSize = (Long)value;
764 if (fso.getSize() > maxSize.longValue()) {
770 case DIRECTORY_ONLY_RESTRICTION:
771 if (value instanceof Boolean) {
772 Boolean directoryOnly = (Boolean) value;
773 if (directoryOnly.booleanValue() && !FileHelper.isDirectory(fso)) {
779 case LOCAL_FILESYSTEM_ONLY_RESTRICTION:
780 if (value instanceof Boolean) {
781 Boolean localOnly = (Boolean)value;
782 if (localOnly.booleanValue()) {
783 /** TODO Needed when CMFM gets networking **/
796 * Method that resolve the symbolic links of the list of files passed as argument.<br />
797 * This method invokes the {@link ResolveLinkCommand} in those files that have a valid
800 * @param context The current context
801 * @param files The listed files
803 public static void resolveSymlinks(Context context, List<FileSystemObject> files) {
804 int cc = files.size();
805 for (int i = 0; i < cc; i++) {
806 FileSystemObject fso = files.get(i);
807 resolveSymlink(context, fso);
812 * Method that resolves the symbolic link of a file passed in as argument.<br />
813 * This method invokes the {@link ResolveLinkCommand} on the file that has a valid
816 * @param context The current context
817 * @param fso FileSystemObject to resolve symlink
819 public static void resolveSymlink(Context context, FileSystemObject fso) {
820 if (fso instanceof Symlink && ((Symlink)fso).getLinkRef() == null) {
822 FileSystemObject symlink =
823 CommandHelper.resolveSymlink(context, fso.getFullPath(), null);
824 ((Symlink)fso).setLinkRef(symlink);
825 } catch (Throwable ex) {/**NON BLOCK**/}
830 * Method that do a comparison between 2 file system objects.
832 * @param fso1 The first file system objects
833 * @param fso2 The second file system objects
834 * @param mode The sort mode
835 * @return int a negative integer if {@code fso1} is less than {@code fso2};
836 * a positive integer if {@code fso1} is greater than {@code fso2};
837 * 0 if {@code fso1} has the same order as {@code fso2}.
839 public static int doCompare(
840 final FileSystemObject fso1,
841 final FileSystemObject fso2,
842 final NavigationSortMode mode) {
844 // Retrieve the user preference for case sensitive sort
845 boolean caseSensitive =
846 Preferences.getSharedPreferences().
848 FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.getId(),
849 ((Boolean)FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.
850 getDefaultValue()).booleanValue());
853 if (mode.getId() == NavigationSortMode.NAME_ASC.getId()) {
854 if (!caseSensitive) {
855 return fso1.getName().compareToIgnoreCase(fso2.getName());
857 return fso1.getName().compareTo(fso2.getName());
860 if (mode.getId() == NavigationSortMode.NAME_DESC.getId()) {
861 if (!caseSensitive) {
862 return fso1.getName().compareToIgnoreCase(fso2.getName()) * -1;
864 return fso1.getName().compareTo(fso2.getName()) * -1;
868 if (mode.getId() == NavigationSortMode.DATE_ASC.getId()) {
869 return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime());
872 if (mode.getId() == NavigationSortMode.DATE_DESC.getId()) {
873 return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime()) * -1;
877 if (mode.getId() == NavigationSortMode.SIZE_ASC.getId()) {
878 return Long.compare(fso1.getSize(), fso2.getSize());
881 if (mode.getId() == NavigationSortMode.SIZE_DESC.getId()) {
882 return Long.compare(fso1.getSize(), fso2.getSize()) * -1;
886 if (mode.getId() == NavigationSortMode.TYPE_ASC.getId()) {
887 // Shouldn't need context here, mimetypes should be loaded
888 return MimeTypeHelper.compareFSO(null, fso1, fso2);
891 if (mode.getId() == NavigationSortMode.TYPE_DESC.getId()) {
892 // Shouldn't need context here, mimetypes should be loaded
893 return MimeTypeHelper.compareFSO(null, fso1, fso2) * -1;
896 //Comparison between files directly
897 return fso1.compareTo(fso2);
901 * Method that add to the path the trailing slash
903 * @param path The path
904 * @return String The path with the trailing slash
906 public static String addTrailingSlash(String path) {
907 if (path == null) return null;
908 return path.endsWith(File.separator) ? path : path + File.separator;
912 * Method that cleans the path and removes the trailing slash
914 * @param path The path to clean
915 * @return String The path without the trailing slash
917 public static String removeTrailingSlash(String path) {
918 if (path == null) return null;
919 if (path.trim().compareTo(ROOT_DIRECTORY) == 0) return path;
920 if (path.endsWith(File.separator)) {
921 return path.substring(0, path.length()-1);
927 * Method that creates a new name based on the name of the {@link FileSystemObject}
928 * that is not current used by the filesystem.
930 * @param ctx The current context
931 * @param files The list of files of the current directory
932 * @param attemptedName The attempted name
933 * @param regexp The resource of the regular expression to create the new name
934 * @return String The new non-existing name
936 public static String createNonExistingName(
937 final Context ctx, final List<FileSystemObject> files,
938 final String attemptedName, int regexp) {
939 // Find a non-exiting name
940 String newName = attemptedName;
941 if (!isNameExists(files, newName)) return newName;
943 String name = FileHelper.getName(newName);
944 String ext = FileHelper.getExtension(newName);
946 ext = ""; //$NON-NLS-1$
948 ext = "." + ext; //$NON-NLS-1$
950 newName = ctx.getString(regexp, name, ext);
951 } while (isNameExists(files, newName));
956 * Method that checks if a name exists in the current directory.
958 * @param files The list of files of the current directory
959 * @param name The name to check
960 * @return boolean Indicate if the name exists in the current directory
962 public static boolean isNameExists(List<FileSystemObject> files, String name) {
963 //Verify if the name exists in the current file list
964 int cc = files.size();
965 for (int i = 0; i < cc; i++) {
966 FileSystemObject fso = files.get(i);
967 if (fso.getName().compareTo(name) == 0) {
975 * Method that returns is a {@link FileSystemObject} can be handled by this application
976 * allowing the uncompression of the file
978 * @param fso The file system object to verify
979 * @return boolean If the file is supported
981 @SuppressWarnings("nls")
982 public static boolean isSupportedUncompressedFile(FileSystemObject fso) {
983 // Valid uncompressed formats are:
984 final String[] VALID =
986 "tar", "tgz", "tar.gz", "tar.bz2", "tar.lzma",
987 "zip", "gz", "bz2", "lzma", "xz", "Z", "rar"
989 // Null values for required commands
990 final String[] OPT_KEYS =
992 null, null, null, null, null,
993 "unzip", null, null, "unlzma", "unxz", "uncompress", "unrar"
996 // Check that have a valid file
997 if (fso == null) return false;
999 // Only regular files
1000 if (isDirectory(fso) || fso instanceof Symlink) {
1003 // No in virtual filesystems
1004 if (fso.isSecure() || fso.isRemote()) {
1007 String ext = getExtension(fso);
1009 int cc = VALID.length;
1010 for (int i = 0; i < cc; i++) {
1011 if (VALID[i].compareToIgnoreCase(ext) == 0) {
1012 // Is the command present
1013 if (OPT_KEYS[i] != null &&
1014 FileManagerApplication.hasOptionalCommand(OPT_KEYS[i])) {
1025 * Method that converts an absolute path to a relative path
1027 * @param path The absolute path to convert
1028 * @param relativeTo The absolute path from which make path relative to (a folder)
1029 * @return String The relative path
1031 public static String toRelativePath(String path, String relativeTo) {
1032 // Normalize the paths
1033 File f1 = new File(path);
1034 File f2 = new File(relativeTo);
1035 String s1 = f1.getAbsolutePath();
1036 String s2 = f2.getAbsolutePath();
1037 if (!s2.endsWith(File.separator)) {
1038 s2 = s2 + File.separator;
1041 // If s2 contains s1 then the relative is replace of the start of the path
1042 if (s1.startsWith(s2)) {
1043 return s1.substring(s2.length());
1046 StringBuffer relative = new StringBuffer();
1048 File f3 = new File(s2);
1049 relative.append(String.format("..%s", File.separator)); //$NON-NLS-1$
1050 s2 = f3.getParent() + File.separator;
1051 } while (!s1.startsWith(s2) && !s1.startsWith(new File(s2).getAbsolutePath()));
1052 s2 = new File(s2).getAbsolutePath();
1053 return relative.toString() + s1.substring(s2.length());
1057 * Method that creates a {@link FileSystemObject} from a {@link File}
1059 * @param file The file or folder reference
1060 * @return FileSystemObject The file system object reference
1062 public static FileSystemObject createFileSystemObject(File file) {
1064 // The user and group name of the files. Use the defaults one for sdcards
1065 final String USER = "root"; //$NON-NLS-1$
1066 final String GROUP = "sdcard_r"; //$NON-NLS-1$
1068 // The user and group name of the files. In ChRoot, aosp give restrict access to
1069 // this user and group. This applies for permission also. This has no really much
1070 // interest if we not allow to change the permissions
1071 AID userAID = AIDHelper.getAIDFromName(USER);
1072 AID groupAID = AIDHelper.getAIDFromName(GROUP);
1073 User user = new User(userAID.getId(), userAID.getName());
1074 Group group = new Group(groupAID.getId(), groupAID.getName());
1075 Permissions perm = file.isDirectory()
1076 ? Permissions.createDefaultFolderPermissions()
1077 : Permissions.createDefaultFilePermissions();
1079 // Build a directory?
1080 Date lastModified = new Date(file.lastModified());
1081 if (file.isDirectory()) {
1087 lastModified, lastModified, lastModified); // The only date we have
1090 // Build a regular file
1097 lastModified, lastModified, lastModified); // The only date we have
1098 } catch (Exception e) {
1099 Log.e(TAG, "Exception retrieving the fso", e); //$NON-NLS-1$
1105 * Method that copies recursively to the destination
1107 * @param src The source file or folder
1108 * @param dst The destination file or folder
1109 * @param bufferSize The buffer size for the operation
1110 * @return boolean If the operation complete successfully
1111 * @throws ExecutionException If a problem was detected in the operation
1113 public static boolean copyRecursive(
1114 final File src, final File dst, int bufferSize, Program program)
1115 throws ExecutionException, CancelledOperationException {
1116 if (src.isDirectory()) {
1117 // Create the directory
1118 if (dst.exists() && !dst.isDirectory()) {
1120 String.format("Failed to check destionation dir: %s", dst)); //$NON-NLS-1$
1121 throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$
1123 if (!dst.exists()) {
1125 Log.e(TAG, String.format("Failed to create directory: %s", dst)); //$NON-NLS-1$
1129 File[] files = src.listFiles();
1130 if (files != null) {
1131 for (int i = 0; i < files.length; i++) {
1132 // Short circuit if we've been cancelled. Show's over :(
1133 if (program.isCancelled()) {
1134 throw new CancelledOperationException();
1137 if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize,
1144 // Copy the directory
1145 if (!bufferedCopy(src, dst,bufferSize, program)) {
1153 * Method that copies a file
1155 * @param src The source file
1156 * @param dst The destination file
1157 * @param bufferSize The buffer size for the operation
1158 * @return boolean If the operation complete successfully
1160 public static boolean bufferedCopy(final File src, final File dst,
1161 int bufferSize, Program program)
1162 throws ExecutionException, CancelledOperationException {
1163 BufferedInputStream bis = null;
1164 BufferedOutputStream bos = null;
1166 bis = new BufferedInputStream(new FileInputStream(src), bufferSize);
1167 bos = new BufferedOutputStream(new FileOutputStream(dst), bufferSize);
1169 byte[] data = new byte[bufferSize];
1170 while ((read = bis.read(data, 0, bufferSize)) != -1) {
1171 // Short circuit if we've been cancelled. Show's over :(
1172 if (program.isCancelled()) {
1173 throw new CancelledOperationException();
1175 bos.write(data, 0, read);
1179 } catch (Throwable e) {
1181 String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$
1184 // delete the destination file if it exists since the operation failed
1188 } catch (Throwable t) {/**NON BLOCK**/}
1190 // Check if this error is an out of space exception and throw that specifically.
1191 // ENOSPC -> Error No Space
1192 if (e.getCause() instanceof ErrnoException
1193 && ((ErrnoException)e.getCause()).errno == OsConstants.ENOSPC) {
1194 throw new ExecutionException(R.string.msgs_no_disk_space);
1195 } if (e instanceof CancelledOperationException) {
1196 // If the user cancelled this operation, let it through.
1197 throw (CancelledOperationException)e;
1206 } catch (Throwable e) {/**NON BLOCK**/}
1211 } catch (Throwable e) {/**NON BLOCK**/}
1212 if (program.isCancelled()) {
1213 if (!dst.delete()) {
1214 Log.e(TAG, "Failed to delete the dest file: " + dst);
1221 * Method that deletes a folder recursively
1223 * @param folder The folder to delete
1224 * @return boolean If the folder was deleted
1226 public static boolean deleteFolder(File folder) {
1227 File[] files = folder.listFiles();
1228 if (files != null) {
1229 for (int i = 0; i < files.length; i++) {
1230 if (files[i].isDirectory()) {
1231 if (!deleteFolder(files[i])) {
1235 if (!files[i].delete()) {
1241 return folder.delete();
1245 * Method that returns the canonical/absolute path of the path.<br/>
1246 * This method performs path resolution
1248 * @param path The path to convert
1249 * @return String The canonical/absolute path
1251 public static String getAbsPath(String path) {
1253 return new File(path).getCanonicalPath();
1254 } catch (Exception e) {
1255 return new File(path).getAbsolutePath();
1260 * Method that returns the .nomedia file
1262 * @param fso The folder that contains the .nomedia file
1263 * @return File The .nomedia file
1265 public static File getNoMediaFile(FileSystemObject fso) {
1268 file = new File(fso.getFullPath()).getCanonicalFile();
1269 } catch (Exception e) {
1270 file = new File(fso.getFullPath()).getAbsoluteFile();
1272 return new File(file, ".nomedia").getAbsoluteFile(); //$NON-NLS-1$
1276 * Method that ensures that the actual console has access to read the
1277 * {@link FileSystemObject} passed.
1279 * @param console The console
1280 * @param fso The {@link FileSystemObject} to check
1281 * @param executable The executable to associate to the {@link InsufficientPermissionsException}
1282 * @throws InsufficientPermissionsException If the console doesn't have enough rights
1284 public static void ensureReadAccess(
1285 Console console, FileSystemObject fso, SyncResultExecutable executable)
1286 throws InsufficientPermissionsException {
1288 if (console.isPrivileged()) {
1289 // Should have access
1292 if (console instanceof JavaConsole &&
1293 StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1294 // Java console runs in chrooted environment, and sdcard are always readable
1297 Identity identity = console.getIdentity();
1298 if (identity == null) {
1299 throw new InsufficientPermissionsException(executable);
1301 Permissions permissions = fso.getPermissions();
1302 User user = fso.getUser();
1303 Group group = fso.getGroup();
1304 List<Group> groups = identity.getGroups();
1305 if ( permissions == null || user == null || group == null) {
1306 throw new InsufficientPermissionsException(executable);
1309 if (permissions.getOthers().isRead()) {
1313 if (user.getId() == identity.getUser().getId() && permissions.getUser().isRead()) {
1317 if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isRead()) {
1321 int cc = groups.size();
1322 for (int i = 0; i < cc; i++) {
1323 Group g = groups.get(i);
1324 if (group.getId() == g.getId() && permissions.getGroup().isRead()) {
1329 } catch (Exception e) {
1330 Log.e(TAG, "Failed to check fso read permission,", e); //$NON-NLS-1$
1332 throw new InsufficientPermissionsException(executable);
1336 * Method that ensures that the actual console has access to write the
1337 * {@link FileSystemObject} passed.
1339 * @param console The console
1340 * @param fso The {@link FileSystemObject} to check
1341 * @param executable The executable to associate to the {@link InsufficientPermissionsException}
1342 * @throws InsufficientPermissionsException If the console doesn't have enough rights
1344 public static void ensureWriteAccess(
1345 Console console, FileSystemObject fso, SyncResultExecutable executable)
1346 throws InsufficientPermissionsException {
1348 if (console.isPrivileged()) {
1349 // Should have access
1352 if (console instanceof JavaConsole &&
1353 StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1354 // Java console runs in chrooted environment, and sdcard are always writeable
1357 Identity identity = console.getIdentity();
1358 if (identity == null) {
1359 throw new InsufficientPermissionsException(executable);
1361 Permissions permissions = fso.getPermissions();
1362 User user = fso.getUser();
1363 Group group = fso.getGroup();
1364 List<Group> groups = identity.getGroups();
1365 if ( permissions == null || user == null || group == null) {
1366 throw new InsufficientPermissionsException(executable);
1369 if (permissions.getOthers().isWrite()) {
1373 if (user.getId() == identity.getUser().getId() && permissions.getUser().isWrite()) {
1377 if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isWrite()) {
1381 int cc = groups.size();
1382 for (int i = 0; i < cc; i++) {
1383 Group g = groups.get(i);
1384 if (group.getId() == g.getId() && permissions.getGroup().isWrite()) {
1389 } catch (Exception e) {
1390 Log.e(TAG, "Failed to check fso write permission,", e); //$NON-NLS-1$
1392 throw new InsufficientPermissionsException(executable);
1396 * Method that ensures that the actual console has access to execute the
1397 * {@link FileSystemObject} passed.
1399 * @param console The console
1400 * @param fso The {@link FileSystemObject} to check
1401 * @param executable The executable to associate to the {@link InsufficientPermissionsException}
1402 * @throws InsufficientPermissionsException If the console doesn't have enough rights
1404 public static void ensureExecuteAccess(
1405 Console console, FileSystemObject fso, SyncResultExecutable executable)
1406 throws InsufficientPermissionsException {
1408 if (console.isPrivileged()) {
1409 // Should have access
1412 if (console instanceof JavaConsole &&
1413 StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1414 // Java console runs in chrooted environment, and sdcard are never executable
1415 throw new InsufficientPermissionsException(executable);
1417 Identity identity = console.getIdentity();
1418 if (identity == null) {
1419 throw new InsufficientPermissionsException(executable);
1421 Permissions permissions = fso.getPermissions();
1422 User user = fso.getUser();
1423 Group group = fso.getGroup();
1424 List<Group> groups = identity.getGroups();
1425 if ( permissions == null || user == null || group == null) {
1426 throw new InsufficientPermissionsException(executable);
1429 if (permissions.getOthers().isExecute()) {
1433 if (user.getId() == identity.getUser().getId() && permissions.getUser().isExecute()) {
1437 if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isExecute()) {
1441 int cc = groups.size();
1442 for (int i = 0; i < cc; i++) {
1443 Group g = groups.get(i);
1444 if (group.getId() == g.getId() && permissions.getGroup().isExecute()) {
1449 } catch (Exception e) {
1450 Log.e(TAG, "Failed to check fso execute permission,", e); //$NON-NLS-1$
1452 throw new InsufficientPermissionsException(executable);
1456 * Method that formats a filetime date with the specific system settings
1458 * @param ctx The current context
1459 * @param filetime The filetime date
1460 * @return String The filetime date formatted
1462 public static String formatFileTime(Context ctx, Date filetime) {
1463 synchronized (DATETIME_SYNC) {
1464 if (sReloadDateTimeFormats) {
1465 String defaultValue =
1466 ((ObjectStringIdentifier)FileManagerSettings.
1467 SETTINGS_FILETIME_FORMAT_MODE.getDefaultValue()).getId();
1468 String id = FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE.getId();
1469 sFiletimeFormatMode =
1470 FileTimeFormatMode.fromId(
1471 Preferences.getSharedPreferences().getString(id, defaultValue));
1472 if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) {
1473 sDateTimeFormatOrder = ctx.getString(R.string.datetime_format_order);
1474 sDateFormat = android.text.format.DateFormat.getDateFormat(ctx);
1475 sTimeFormat = android.text.format.DateFormat.getTimeFormat(ctx);
1476 } else if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.LOCALE) == 0) {
1478 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
1480 sDateFormat = new SimpleDateFormat(sFiletimeFormatMode.getFormat());
1482 sReloadDateTimeFormats = false;
1486 // Apply the user settings
1487 if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) {
1488 String date = sDateFormat.format(filetime);
1489 String time = sTimeFormat.format(filetime);
1490 return String.format(sDateTimeFormatOrder, date, time);
1492 return sDateFormat.format(filetime);
1497 * Method that create a new temporary filename
1499 * @param external If the file should be created in the external or the internal cache dir
1501 public static synchronized File createTempFilename(Context context, boolean external) {
1502 File tempDirectory = external ? context.getExternalCacheDir() : context.getCacheDir();
1505 UUID uuid = UUID.randomUUID();
1506 tempFile = new File(tempDirectory, uuid.toString());
1507 } while (tempFile.exists());
1512 * Method that delete a file or a folder
1514 * @param src The file or folder to delete
1515 * @return boolean If the operation was successfully
1517 public static boolean deleteFileOrFolder(File src) {
1518 if (src.isDirectory()) {
1519 return FileHelper.deleteFolder(src);
1521 return src.delete();
1525 * Method that checks if the source file passed belongs to (is under) the directory passed
1527 * @param src The file to check
1528 * @param dir The parent file to check
1529 * @return boolean If the source belongs to the directory
1531 public static boolean belongsToDirectory(File src, File dir) {
1535 return src.getAbsolutePath().startsWith(dir.getAbsolutePath());
1539 * Method that checks if both path are the same (by checking sensitive cases).
1541 * @param src The source path
1542 * @param dst The destination path
1543 * @return boolean If both are the same path
1545 public static boolean isSamePath(String src, String dst) {
1546 // This is only true if both are exactly the same path or the same file in insensitive
1548 File o1 = new File(src);
1549 File o2 = new File(dst);
1550 return o1.equals(o2);