OSDN Git Service

Sizes: Let disk usage show sizes as doubles
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / util / FileHelper.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.cyanogenmod.filemanager.util;
18
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;
25
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;
59
60 import java.io.BufferedInputStream;
61 import java.io.BufferedOutputStream;
62 import java.io.File;
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;
73 import java.util.Map;
74 import java.util.UUID;
75
76 /**
77  * A helper class with useful methods for deal with files.
78  */
79 public final class FileHelper {
80
81     private static final String TAG = "FileHelper"; //$NON-NLS-1$
82
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$
87
88     /**
89      * Special extension for compressed tar files
90      */
91     private static final String[] COMPRESSED_TAR =
92         {
93          "tar.gz", "tar.bz2", "tar.lzma" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
94         };
95
96     /**
97      * The root directory.
98      * @hide
99      */
100     public static final String ROOT_DIRECTORY = "/";  //$NON-NLS-1$
101
102     /**
103      * The parent directory string.
104      * @hide
105      */
106     public static final String PARENT_DIRECTORY = "..";  //$NON-NLS-1$
107
108     /**
109      * The current directory string.
110      * @hide
111      */
112     public static final String CURRENT_DIRECTORY = ".";  //$NON-NLS-1$
113
114     /**
115      * The administrator user.
116      * @hide
117      */
118     public static final String USER_ROOT = "root";  //$NON-NLS-1$
119
120     /**
121      * The newline string.
122      * @hide
123      */
124     public static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
125
126     // The date/time formats objects
127     /**
128      * @hide
129      */
130     public final static Object DATETIME_SYNC = new Object();
131     /**
132      * @hide
133      */
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;
139
140     /**
141      * Constructor of <code>FileHelper</code>.
142      */
143     private FileHelper() {
144         super();
145     }
146
147     /**
148      * Method that check if a file is a symbolic link.
149      *
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
153      */
154     public static boolean isSymlink(File file) throws IOException {
155         return file.getAbsolutePath().compareTo(file.getCanonicalPath()) != 0;
156     }
157
158     /**
159      * Method that resolves a symbolic link to the real file or directory.
160      *
161      * @param file File to check
162      * @return File The real file or directory
163      * @throws IOException If real file couldn't be resolved
164      */
165     public static File resolveSymlink(File file) throws IOException {
166         return file.getCanonicalFile();
167     }
168
169     /**
170      * Method that returns a more human readable of the size
171      * of a file system object.
172      *
173      * @param fso File system object
174      * @return String The human readable size (void if fso don't supports size)
175      */
176     public static String getHumanReadableSize(FileSystemObject fso) {
177         //Only if has size
178         if (fso instanceof Directory) {
179             return ""; //$NON-NLS-1$
180         }
181         if (hasSymlinkRef(fso)) {
182             if (isSymlinkRefDirectory(fso)) {
183                 return ""; //$NON-NLS-1$
184             }
185             return getHumanReadableSize(((Symlink)fso).getLinkRef().getSize());
186         }
187         return getHumanReadableSize(fso.getSize());
188     }
189
190     /**
191      * Method that returns a more human readable of a size in bytes.
192      *
193      * @param size The size in bytes
194      * @return String The human readable size
195      */
196     public static String getHumanReadableSize(long size) {
197         Resources res = FileManagerApplication.getInstance().getResources();
198         final int[] magnitude = {
199                                  R.string.size_bytes,
200                                  R.string.size_kilobytes,
201                                  R.string.size_megabytes,
202                                  R.string.size_gigabytes
203                                 };
204
205         double aux = size;
206         int cc = magnitude.length;
207         for (int i = 0; i < cc; i++) {
208             if (aux < 1024) {
209                 double cleanSize = Math.round(aux * 100);
210                 return Double.toString(cleanSize / 100) +
211                         " " + res.getString(magnitude[i]); //$NON-NLS-1$
212             } else {
213                 aux = aux / 1024;
214             }
215         }
216         double cleanSize = Math.round(aux * 100);
217         return Double.toString(cleanSize / 100) +
218                 " " + res.getString(magnitude[cc - 1]); //$NON-NLS-1$
219     }
220
221     /**
222      * Method that returns if the file system object if the root directory.
223      *
224      * @param fso The file system object to check
225      * @return boolean if the file system object if the root directory
226      */
227     public static boolean isRootDirectory(FileSystemObject fso) {
228         if (fso.getName() == null) return true;
229         return fso.getName().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
230     }
231
232     /**
233      * Method that returns if the folder if the root directory.
234      *
235      * @param folder The folder
236      * @return boolean if the folder if the root directory
237      */
238     public static boolean isRootDirectory(String folder) {
239         if (folder == null) return true;
240         return isRootDirectory(new File(folder));
241     }
242
243     /**
244      * Method that returns if the folder if the root directory.
245      *
246      * @param folder The folder
247      * @return boolean if the folder if the root directory
248      */
249     public static boolean isRootDirectory(File folder) {
250         if (folder.getPath() == null) return true;
251         return folder.getPath().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
252     }
253
254     /**
255      * Method that returns if the parent file system object if the root directory.
256      *
257      * @param fso The parent file system object to check
258      * @return boolean if the parent file system object if the root directory
259      */
260     public static boolean isParentRootDirectory(FileSystemObject fso) {
261         if (fso.getParent() == null) return true;
262         return fso.getParent().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
263     }
264
265     /**
266      * Method that returns the name without the extension of a file system object.
267      *
268      * @param fso The file system object
269      * @return The name without the extension of the file system object.
270      */
271     public static String getName(FileSystemObject fso) {
272         return getName(fso.getName());
273     }
274
275     /**
276      * Method that returns the name without the extension of a file system object.
277      *
278      * @param name The name of file system object
279      * @return The name without the extension of the file system object.
280      */
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);
285     }
286
287     /**
288      * Method that returns the extension of a file system object.
289      *
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.
293      */
294     public static String getExtension(FileSystemObject fso) {
295         return getExtension(fso.getName());
296     }
297
298     /**
299      * Method that returns the extension of a file system object.
300      *
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.
304      */
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
309             return null;
310         }
311
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];
317             }
318         }
319
320         // General extraction method
321         return name.substring(pos + 1);
322     }
323
324     /**
325      * Method that returns the parent directory of a file/folder
326      *
327      * @param path The file/folder
328      * @return String The parent directory
329      */
330     public static String getParentDir(String path) {
331         return getParentDir(new File(path));
332     }
333
334     /**
335      * Method that returns the parent directory of a file/folder
336      *
337      * @param path The file/folder
338      * @return String The parent directory
339      */
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;
344         }
345         return parent;
346     }
347
348     /**
349      * Method that evaluates if a path is relative.
350      *
351      * @param src The path to check
352      * @return boolean If a path is relative
353      */
354     public static boolean isRelativePath(String src) {
355         if (src.startsWith(CURRENT_DIRECTORY + File.separator)) {
356             return true;
357         }
358         if (src.startsWith(PARENT_DIRECTORY + File.separator)) {
359             return true;
360         }
361         if (src.indexOf(File.separator + CURRENT_DIRECTORY + File.separator) != -1) {
362             return true;
363         }
364         if (src.indexOf(File.separator + PARENT_DIRECTORY + File.separator) != -1) {
365             return true;
366         }
367         if (!src.startsWith(ROOT_DIRECTORY)) {
368             return true;
369         }
370         return false;
371     }
372
373     /**
374      * Method that check if the file system object is a {@link Symlink} and
375      * has a link reference.
376      *
377      * @param fso The file system object to check
378      * @return boolean If file system object the has a link reference
379      */
380     public static boolean hasSymlinkRef(FileSystemObject fso) {
381         if (fso instanceof Symlink) {
382             return ((Symlink)fso).getLinkRef() != null;
383         }
384         return false;
385     }
386
387     /**
388      * Method that check if the file system object is a {@link Symlink} and
389      * the link reference is a directory.
390      *
391      * @param fso The file system object to check
392      * @return boolean If file system object the link reference is a directory
393      */
394     public static boolean isSymlinkRefDirectory(FileSystemObject fso) {
395         if (!hasSymlinkRef(fso)) {
396             return false;
397         }
398         return ((Symlink)fso).getLinkRef() instanceof Directory;
399     }
400
401     /**
402      * Method that check if the file system object is a {@link Symlink} and
403      * the link reference is a system file.
404      *
405      * @param fso The file system object to check
406      * @return boolean If file system object the link reference is a system file
407      */
408     public static boolean isSymlinkRefSystemFile(FileSystemObject fso) {
409         if (!hasSymlinkRef(fso)) {
410             return false;
411         }
412         return ((Symlink)fso).getLinkRef() instanceof SystemFile;
413     }
414
415     /**
416      * Method that check if the file system object is a {@link Symlink} and
417      * the link reference is a block device.
418      *
419      * @param fso The file system object to check
420      * @return boolean If file system object the link reference is a block device
421      */
422     public static boolean isSymlinkRefBlockDevice(FileSystemObject fso) {
423         if (!hasSymlinkRef(fso)) {
424             return false;
425         }
426         return ((Symlink)fso).getLinkRef() instanceof BlockDevice;
427     }
428
429     /**
430      * Method that check if the file system object is a {@link Symlink} and
431      * the link reference is a character device.
432      *
433      * @param fso The file system object to check
434      * @return boolean If file system object the link reference is a character device
435      */
436     public static boolean isSymlinkRefCharacterDevice(FileSystemObject fso) {
437         if (!hasSymlinkRef(fso)) {
438             return false;
439         }
440         return ((Symlink)fso).getLinkRef() instanceof CharacterDevice;
441     }
442
443     /**
444      * Method that check if the file system object is a {@link Symlink} and
445      * the link reference is a named pipe.
446      *
447      * @param fso The file system object to check
448      * @return boolean If file system object the link reference is a named pipe
449      */
450     public static boolean isSymlinkRefNamedPipe(FileSystemObject fso) {
451         if (!hasSymlinkRef(fso)) {
452             return false;
453         }
454         return ((Symlink)fso).getLinkRef() instanceof NamedPipe;
455     }
456
457     /**
458      * Method that check if the file system object is a {@link Symlink} and
459      * the link reference is a domain socket.
460      *
461      * @param fso The file system object to check
462      * @return boolean If file system object the link reference is a domain socket
463      */
464     public static boolean isSymlinkRefDomainSocket(FileSystemObject fso) {
465         if (!hasSymlinkRef(fso)) {
466             return false;
467         }
468         return ((Symlink)fso).getLinkRef() instanceof DomainSocket;
469     }
470
471     /**
472      * Method that checks if a file system object is a directory (real o symlink).
473      *
474      * @param fso The file system object to check
475      * @return boolean If file system object is a directory
476      */
477     public static boolean isDirectory(FileSystemObject fso) {
478         if (fso instanceof Directory) {
479             return true;
480         }
481         if (isSymlinkRefDirectory(fso)) {
482             return true;
483         }
484         return false;
485     }
486
487     /**
488      * Method that checks if a file system object is a system file (real o symlink).
489      *
490      * @param fso The file system object to check
491      * @return boolean If file system object is a system file
492      */
493     public static boolean isSystemFile(FileSystemObject fso) {
494         if (fso instanceof SystemFile) {
495             return true;
496         }
497         if (isSymlinkRefSystemFile(fso)) {
498             return true;
499         }
500         return false;
501     }
502
503     /**
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).
507      *
508      * @param fso The file system object to check
509      * @return FileSystemObject The real file system object reference
510      */
511     public static FileSystemObject getReference(FileSystemObject fso) {
512         if (hasSymlinkRef(fso)) {
513             return ((Symlink)fso).getLinkRef();
514         }
515         return fso;
516     }
517
518     /**
519      * Method that applies the configuration modes to the listed files
520      * (sort mode, hidden files, ...).
521      *
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
526      */
527     public static List<FileSystemObject> applyUserPreferences(
528                     List<FileSystemObject> files, Map<DisplayRestrictions,
529                     Object> restrictions, boolean chRooted) {
530         return applyUserPreferences(files, restrictions, false, chRooted);
531     }
532
533     /**
534      * Method that applies the configuration modes to the listed files
535      * (sort mode, hidden files, ...).
536      *
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
542      */
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;
553
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);
558
559             //Hidden files
560             if (!prefs.getBoolean(
561                     showHiddenPref.getId(),
562                     ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
563                 if (file.isHidden()) {
564                     files.remove(i);
565                     continue;
566                 }
567             }
568
569             //System files
570             if (!prefs.getBoolean(
571                     showSystemPref.getId(),
572                     ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
573                 if (file instanceof SystemFile) {
574                     files.remove(i);
575                     continue;
576                 }
577             }
578
579             //Symlinks files
580             if (!prefs.getBoolean(
581                     showSymlinksPref.getId(),
582                     ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
583                 if (file instanceof Symlink) {
584                     files.remove(i);
585                     continue;
586                 }
587             }
588
589             // Restrictions (only apply to files)
590             if (restrictions != null) {
591                 if (!isDirectory(file)) {
592                     if (!isDisplayAllowed(file, restrictions)) {
593                         files.remove(i);
594                         continue;
595                     }
596                 }
597             }
598         }
599
600         //Apply sort mode
601         if (!noSort) {
602             final boolean showDirsFirst =
603                     prefs.getBoolean(
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>() {
611                 @Override
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) {
618                             return 0;
619                         }
620                         return (isLhsParentDirectory) ? -1 : 1;
621                     }
622
623                     //Need to sort directory first?
624                     if (showDirsFirst) {
625                         boolean isLhsDirectory = FileHelper.isDirectory(lhs);
626                         boolean isRhsDirectory = FileHelper.isDirectory(rhs);
627                         if (isLhsDirectory || isRhsDirectory) {
628                             if (isLhsDirectory && isRhsDirectory) {
629                                 //Apply sort mode
630                                 return FileHelper.doCompare(lhs, rhs, sortMode);
631                             }
632                             return (isLhsDirectory) ? -1 : 1;
633                         }
634                     }
635
636                     //Apply sort mode
637                     return FileHelper.doCompare(lhs, rhs, sortMode);
638                 }
639
640             });
641         }
642
643         //Return the files
644         return files;
645     }
646
647     /**
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, ...).
651      *
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
656      */
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;
664
665         //Hidden files
666         if (!prefs.getBoolean(
667                 showHiddenPref.getId(),
668                 ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) {
669             if (fso.isHidden()) {
670                 return false;
671             }
672         }
673
674         //System files
675         if (!prefs.getBoolean(
676                 showSystemPref.getId(),
677                 ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) {
678             if (fso instanceof SystemFile) {
679                 return false;
680             }
681         }
682
683         //Symlinks files
684         if (!prefs.getBoolean(
685                 showSymlinksPref.getId(),
686                 ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) {
687             if (fso instanceof Symlink) {
688                 return false;
689             }
690         }
691
692         // Restrictions (only apply to files)
693         if (restrictions != null) {
694             if (!isDirectory(fso)) {
695                 if (!isDisplayAllowed(fso, restrictions)) {
696                     return false;
697                 }
698             }
699         }
700
701         // all checks passed
702         return true;
703     }
704
705     /**
706      * Method that check if a file should be displayed according to the restrictions
707      *
708      * @param fso The file system object to check
709      * @param restrictions The restrictions map
710      * @return boolean If the file should be displayed
711      */
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);
718             if (value == null) {
719                 continue;
720             }
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) {
729                             return false;
730                         }
731                     }
732                     break;
733
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;
740                     }
741                     if (mimeTypes != null) {
742                         boolean matches = false;
743                         for (String mimeType : mimeTypes) {
744                             if (mimeType.compareTo(MimeTypeHelper.ALL_MIME_TYPES) == 0) {
745                                 matches = true;
746                                 break;
747                             }
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)) {
751                                 matches = true;
752                                 break;
753                             }
754                         }
755                         if (!matches) {
756                             return false;
757                         }
758                     }
759                     break;
760
761                 case SIZE_RESTRICTION:
762                     if (value instanceof Long) {
763                         Long maxSize = (Long)value;
764                         if (fso.getSize() > maxSize.longValue()) {
765                             return false;
766                         }
767                     }
768                     break;
769
770                 case DIRECTORY_ONLY_RESTRICTION:
771                     if (value instanceof Boolean) {
772                         Boolean directoryOnly = (Boolean) value;
773                         if (directoryOnly.booleanValue() && !FileHelper.isDirectory(fso)) {
774                             return false;
775                         }
776                     }
777                     break;
778
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 **/
784                         }
785                     }
786                     break;
787
788                 default:
789                     break;
790             }
791         }
792         return true;
793     }
794
795     /**
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
798      * symlink reference
799      *
800      * @param context The current context
801      * @param files The listed files
802      */
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);
808         }
809     }
810
811     /**
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
814      * symlink reference
815      *
816      * @param context The current context
817      * @param fso FileSystemObject to resolve symlink
818      */
819     public static void resolveSymlink(Context context, FileSystemObject fso) {
820         if (fso instanceof Symlink && ((Symlink)fso).getLinkRef() == null) {
821             try {
822                 FileSystemObject symlink =
823                         CommandHelper.resolveSymlink(context, fso.getFullPath(), null);
824                 ((Symlink)fso).setLinkRef(symlink);
825             } catch (Throwable ex) {/**NON BLOCK**/}
826         }
827     }
828
829     /**
830      * Method that do a comparison between 2 file system objects.
831      *
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}.
838      */
839     public static int doCompare(
840             final FileSystemObject fso1,
841             final FileSystemObject fso2,
842             final NavigationSortMode mode) {
843
844         // Retrieve the user preference for case sensitive sort
845         boolean caseSensitive =
846                 Preferences.getSharedPreferences().
847                     getBoolean(
848                         FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.getId(),
849                         ((Boolean)FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.
850                                 getDefaultValue()).booleanValue());
851
852         //Name (ascending)
853         if (mode.getId() == NavigationSortMode.NAME_ASC.getId()) {
854             if (!caseSensitive) {
855                 return fso1.getName().compareToIgnoreCase(fso2.getName());
856             }
857             return fso1.getName().compareTo(fso2.getName());
858         }
859         //Name (descending)
860         if (mode.getId() == NavigationSortMode.NAME_DESC.getId()) {
861             if (!caseSensitive) {
862                 return fso1.getName().compareToIgnoreCase(fso2.getName()) * -1;
863             }
864             return fso1.getName().compareTo(fso2.getName()) * -1;
865         }
866
867         //Date (ascending)
868         if (mode.getId() == NavigationSortMode.DATE_ASC.getId()) {
869             return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime());
870         }
871         //Date (descending)
872         if (mode.getId() == NavigationSortMode.DATE_DESC.getId()) {
873             return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime()) * -1;
874         }
875
876         //Size (ascending)
877         if (mode.getId() == NavigationSortMode.SIZE_ASC.getId()) {
878             return Long.compare(fso1.getSize(), fso2.getSize());
879         }
880         //Size (descending)
881         if (mode.getId() == NavigationSortMode.SIZE_DESC.getId()) {
882             return Long.compare(fso1.getSize(), fso2.getSize()) * -1;
883         }
884
885         //Type (ascending)
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);
889         }
890         //Type (descending)
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;
894         }
895
896         //Comparison between files directly
897         return fso1.compareTo(fso2);
898     }
899
900     /**
901      * Method that add to the path the trailing slash
902      *
903      * @param path The path
904      * @return String The path with the trailing slash
905      */
906     public static String addTrailingSlash(String path) {
907         if (path == null) return null;
908         return path.endsWith(File.separator) ? path : path + File.separator;
909     }
910
911     /**
912      * Method that cleans the path and removes the trailing slash
913      *
914      * @param path The path to clean
915      * @return String The path without the trailing slash
916      */
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);
922         }
923         return path;
924     }
925
926     /**
927      * Method that creates a new name based on the name of the {@link FileSystemObject}
928      * that is not current used by the filesystem.
929      *
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
935      */
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;
942         do {
943             String name  = FileHelper.getName(newName);
944             String ext  = FileHelper.getExtension(newName);
945             if (ext == null) {
946                 ext = ""; //$NON-NLS-1$
947             } else {
948                 ext = "." + ext; //$NON-NLS-1$
949             }
950             newName = ctx.getString(regexp, name, ext);
951         } while (isNameExists(files, newName));
952         return newName;
953     }
954
955     /**
956      * Method that checks if a name exists in the current directory.
957      *
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
961      */
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) {
968                 return true;
969             }
970         }
971         return false;
972     }
973
974     /**
975      * Method that returns is a {@link FileSystemObject} can be handled by this application
976      * allowing the uncompression of the file
977      *
978      * @param fso The file system object to verify
979      * @return boolean If the file is supported
980      */
981     @SuppressWarnings("nls")
982     public static boolean isSupportedUncompressedFile(FileSystemObject fso) {
983         // Valid uncompressed formats are:
984         final String[] VALID =
985                 {
986                     "tar", "tgz", "tar.gz", "tar.bz2", "tar.lzma",
987                     "zip", "gz", "bz2", "lzma", "xz", "Z", "rar"
988                 };
989         // Null values for required commands
990         final String[] OPT_KEYS =
991                 {
992                     null, null, null, null, null,
993                     "unzip", null, null, "unlzma", "unxz", "uncompress", "unrar"
994                 };
995
996         // Check that have a valid file
997         if (fso == null) return false;
998
999         // Only regular files
1000         if (isDirectory(fso) || fso instanceof Symlink) {
1001             return false;
1002         }
1003         // No in virtual filesystems
1004         if (fso.isSecure() || fso.isRemote()) {
1005             return false;
1006         }
1007         String ext = getExtension(fso);
1008         if (ext != null) {
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])) {
1015                         return true;
1016                     }
1017                     return false;
1018                 }
1019             }
1020         }
1021         return false;
1022     }
1023
1024     /**
1025      * Method that converts an absolute path to a relative path
1026      *
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
1030      */
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;
1039         }
1040
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());
1044         }
1045
1046         StringBuffer relative = new StringBuffer();
1047         do {
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());
1054     }
1055
1056     /**
1057      * Method that creates a {@link FileSystemObject} from a {@link File}
1058      *
1059      * @param file The file or folder reference
1060      * @return FileSystemObject The file system object reference
1061      */
1062     public static FileSystemObject createFileSystemObject(File file) {
1063         try {
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$
1067
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();
1078
1079             // Build a directory?
1080             Date lastModified = new Date(file.lastModified());
1081             if (file.isDirectory()) {
1082                 return
1083                     new Directory(
1084                             file.getName(),
1085                             file.getParent(),
1086                             user, group, perm,
1087                             lastModified, lastModified, lastModified); // The only date we have
1088             }
1089
1090             // Build a regular file
1091             return
1092                 new RegularFile(
1093                         file.getName(),
1094                         file.getParent(),
1095                         user, group, perm,
1096                         file.length(),
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$
1100         }
1101         return null;
1102     }
1103
1104     /**
1105      * Method that copies recursively to the destination
1106      *
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
1112      */
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()) {
1119                 Log.e(TAG,
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$
1122             }
1123             if (!dst.exists()) {
1124                 if (!dst.mkdir()) {
1125                     Log.e(TAG, String.format("Failed to create directory: %s", dst)); //$NON-NLS-1$
1126                     return false;
1127                 }
1128             }
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();
1135                     }
1136
1137                     if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize,
1138                                        program)) {
1139                         return false;
1140                     }
1141                 }
1142             }
1143         } else {
1144             // Copy the directory
1145             if (!bufferedCopy(src, dst,bufferSize, program)) {
1146                 return false;
1147             }
1148         }
1149         return true;
1150     }
1151
1152     /**
1153      * Method that copies a file
1154      *
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
1159      */
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;
1165         try {
1166             bis = new BufferedInputStream(new FileInputStream(src), bufferSize);
1167             bos = new BufferedOutputStream(new FileOutputStream(dst), bufferSize);
1168             int read = 0;
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();
1174                 }
1175                 bos.write(data, 0, read);
1176             }
1177             return true;
1178
1179         } catch (Throwable e) {
1180             Log.e(TAG,
1181                     String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$
1182
1183             try {
1184                 // delete the destination file if it exists since the operation failed
1185                 if (dst.exists()) {
1186                     dst.delete();
1187                 }
1188             } catch (Throwable t) {/**NON BLOCK**/}
1189
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;
1198             }
1199
1200             return false;
1201         } finally {
1202             try {
1203                 if (bis != null) {
1204                     bis.close();
1205                 }
1206             } catch (Throwable e) {/**NON BLOCK**/}
1207             try {
1208                 if (bos != null) {
1209                     bos.close();
1210                 }
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);
1215                 }
1216             }
1217         }
1218     }
1219
1220     /**
1221      * Method that deletes a folder recursively
1222      *
1223      * @param folder The folder to delete
1224      * @return boolean If the folder was deleted
1225      */
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])) {
1232                         return false;
1233                     }
1234                 } else {
1235                     if (!files[i].delete()) {
1236                         return false;
1237                     }
1238                 }
1239             }
1240         }
1241         return folder.delete();
1242     }
1243
1244     /**
1245      * Method that returns the canonical/absolute path of the path.<br/>
1246      * This method performs path resolution
1247      *
1248      * @param path The path to convert
1249      * @return String The canonical/absolute path
1250      */
1251     public static String getAbsPath(String path) {
1252         try {
1253             return new File(path).getCanonicalPath();
1254         } catch (Exception e) {
1255             return new File(path).getAbsolutePath();
1256         }
1257     }
1258
1259     /**
1260      * Method that returns the .nomedia file
1261      *
1262      * @param fso The folder that contains the .nomedia file
1263      * @return File The .nomedia file
1264      */
1265     public static File getNoMediaFile(FileSystemObject fso) {
1266         File file = null;
1267         try {
1268             file = new File(fso.getFullPath()).getCanonicalFile();
1269         } catch (Exception e) {
1270             file = new File(fso.getFullPath()).getAbsoluteFile();
1271         }
1272         return new File(file, ".nomedia").getAbsoluteFile(); //$NON-NLS-1$
1273     }
1274
1275     /**
1276      * Method that ensures that the actual console has access to read the
1277      * {@link FileSystemObject} passed.
1278      *
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
1283      */
1284     public static void ensureReadAccess(
1285             Console console, FileSystemObject fso, SyncResultExecutable executable)
1286             throws InsufficientPermissionsException {
1287         try {
1288             if (console.isPrivileged()) {
1289                 // Should have access
1290                 return;
1291             }
1292             if (console instanceof JavaConsole &&
1293                     StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1294                 // Java console runs in chrooted environment, and sdcard are always readable
1295                 return;
1296             }
1297             Identity identity = console.getIdentity();
1298             if (identity == null) {
1299                 throw new InsufficientPermissionsException(executable);
1300             }
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);
1307             }
1308             // Check others
1309             if (permissions.getOthers().isRead()) {
1310                 return;
1311             }
1312             // Check user
1313             if (user.getId() == identity.getUser().getId() && permissions.getUser().isRead()) {
1314                 return;
1315             }
1316             // Check group
1317             if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isRead()) {
1318                 return;
1319             }
1320             // Check groups
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()) {
1325                     return;
1326                 }
1327             }
1328
1329         } catch (Exception e) {
1330             Log.e(TAG, "Failed to check fso read permission,", e); //$NON-NLS-1$
1331         }
1332         throw new InsufficientPermissionsException(executable);
1333     }
1334
1335     /**
1336      * Method that ensures that the actual console has access to write the
1337      * {@link FileSystemObject} passed.
1338      *
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
1343      */
1344     public static void ensureWriteAccess(
1345             Console console, FileSystemObject fso, SyncResultExecutable executable)
1346             throws InsufficientPermissionsException {
1347         try {
1348             if (console.isPrivileged()) {
1349                 // Should have access
1350                 return;
1351             }
1352             if (console instanceof JavaConsole &&
1353                     StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
1354                 // Java console runs in chrooted environment, and sdcard are always writeable
1355                 return;
1356             }
1357             Identity identity = console.getIdentity();
1358             if (identity == null) {
1359                 throw new InsufficientPermissionsException(executable);
1360             }
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);
1367             }
1368             // Check others
1369             if (permissions.getOthers().isWrite()) {
1370                 return;
1371             }
1372             // Check user
1373             if (user.getId() == identity.getUser().getId() && permissions.getUser().isWrite()) {
1374                 return;
1375             }
1376             // Check group
1377             if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isWrite()) {
1378                 return;
1379             }
1380             // Check groups
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()) {
1385                     return;
1386                 }
1387             }
1388
1389         } catch (Exception e) {
1390             Log.e(TAG, "Failed to check fso write permission,", e); //$NON-NLS-1$
1391         }
1392         throw new InsufficientPermissionsException(executable);
1393     }
1394
1395     /**
1396      * Method that ensures that the actual console has access to execute the
1397      * {@link FileSystemObject} passed.
1398      *
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
1403      */
1404     public static void ensureExecuteAccess(
1405             Console console, FileSystemObject fso, SyncResultExecutable executable)
1406             throws InsufficientPermissionsException {
1407         try {
1408             if (console.isPrivileged()) {
1409                 // Should have access
1410                 return;
1411             }
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);
1416             }
1417             Identity identity = console.getIdentity();
1418             if (identity == null) {
1419                 throw new InsufficientPermissionsException(executable);
1420             }
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);
1427             }
1428             // Check others
1429             if (permissions.getOthers().isExecute()) {
1430                 return;
1431             }
1432             // Check user
1433             if (user.getId() == identity.getUser().getId() && permissions.getUser().isExecute()) {
1434                 return;
1435             }
1436             // Check group
1437             if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isExecute()) {
1438                 return;
1439             }
1440             // Check groups
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()) {
1445                     return;
1446                 }
1447             }
1448
1449         } catch (Exception e) {
1450             Log.e(TAG, "Failed to check fso execute permission,", e); //$NON-NLS-1$
1451         }
1452         throw new InsufficientPermissionsException(executable);
1453     }
1454
1455     /**
1456      * Method that formats a filetime date with the specific system settings
1457      *
1458      * @param ctx The current context
1459      * @param filetime The filetime date
1460      * @return String The filetime date formatted
1461      */
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) {
1477                     sDateFormat =
1478                             DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
1479                 } else {
1480                     sDateFormat = new SimpleDateFormat(sFiletimeFormatMode.getFormat());
1481                 }
1482                 sReloadDateTimeFormats = false;
1483             }
1484         }
1485
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);
1491         } else {
1492             return sDateFormat.format(filetime);
1493         }
1494     }
1495
1496     /**
1497      * Method that create a new temporary filename
1498      *
1499      * @param external If the file should be created in the external or the internal cache dir
1500      */
1501     public static synchronized File createTempFilename(Context context, boolean external) {
1502         File tempDirectory = external ? context.getExternalCacheDir() : context.getCacheDir();
1503         File tempFile;
1504         do {
1505             UUID uuid = UUID.randomUUID();
1506             tempFile = new File(tempDirectory, uuid.toString());
1507         } while (tempFile.exists());
1508         return tempFile;
1509     }
1510
1511     /**
1512      * Method that delete a file or a folder
1513      *
1514      * @param src The file or folder to delete
1515      * @return boolean If the operation was successfully
1516      */
1517     public static boolean deleteFileOrFolder(File src) {
1518         if (src.isDirectory()) {
1519             return FileHelper.deleteFolder(src);
1520         }
1521         return src.delete();
1522     }
1523
1524     /**
1525      * Method that checks if the source file passed belongs to (is under) the directory passed
1526      *
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
1530      */
1531     public static boolean belongsToDirectory(File src, File dir) {
1532         if (dir.isFile()) {
1533             return false;
1534         }
1535         return src.getAbsolutePath().startsWith(dir.getAbsolutePath());
1536     }
1537
1538     /**
1539      * Method that checks if both path are the same (by checking sensitive cases).
1540      *
1541      * @param src The source path
1542      * @param dst The destination path
1543      * @return boolean If both are the same path
1544      */
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
1547         // file systems
1548         File o1 = new File(src);
1549         File o2 = new File(dst);
1550         return o1.equals(o2);
1551     }
1552 }