• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source 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.android.ddmlib;
18 
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.HashMap;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 
26 /**
27  * Provides {@link Device} side file listing service.
28  * <p/>To get an instance for a known {@link Device}, call {@link Device#getFileListingService()}.
29  */
30 public final class FileListingService {
31 
32     /** Pattern to find filenames that match "*.apk" */
33     private final static Pattern sApkPattern =
34         Pattern.compile(".*\\.apk", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
35 
36     private final static String PM_FULL_LISTING = "pm list packages -f"; //$NON-NLS-1$
37 
38     /** Pattern to parse the output of the 'pm -lf' command.<br>
39      * The output format looks like:<br>
40      * /data/app/myapp.apk=com.mypackage.myapp */
41     private final static Pattern sPmPattern = Pattern.compile("^package:(.+?)=(.+)$"); //$NON-NLS-1$
42 
43     /** Top level data folder. */
44     public final static String DIRECTORY_DATA = "data"; //$NON-NLS-1$
45     /** Top level sdcard folder. */
46     public final static String DIRECTORY_SDCARD = "sdcard"; //$NON-NLS-1$
47     /** Top level mount folder. */
48     public final static String DIRECTORY_MNT = "mnt"; //$NON-NLS-1$
49     /** Top level system folder. */
50     public final static String DIRECTORY_SYSTEM = "system"; //$NON-NLS-1$
51     /** Top level temp folder. */
52     public final static String DIRECTORY_TEMP = "tmp"; //$NON-NLS-1$
53     /** Application folder. */
54     public final static String DIRECTORY_APP = "app"; //$NON-NLS-1$
55 
56     private final static String[] sRootLevelApprovedItems = {
57         DIRECTORY_DATA,
58         DIRECTORY_SDCARD,
59         DIRECTORY_SYSTEM,
60         DIRECTORY_TEMP,
61         DIRECTORY_MNT,
62     };
63 
64     public static final long REFRESH_RATE = 5000L;
65     /**
66      * Refresh test has to be slightly lower for precision issue.
67      */
68     static final long REFRESH_TEST = (long)(REFRESH_RATE * .8);
69 
70     /** Entry type: File */
71     public static final int TYPE_FILE = 0;
72     /** Entry type: Directory */
73     public static final int TYPE_DIRECTORY = 1;
74     /** Entry type: Directory Link */
75     public static final int TYPE_DIRECTORY_LINK = 2;
76     /** Entry type: Block */
77     public static final int TYPE_BLOCK = 3;
78     /** Entry type: Character */
79     public static final int TYPE_CHARACTER = 4;
80     /** Entry type: Link */
81     public static final int TYPE_LINK = 5;
82     /** Entry type: Socket */
83     public static final int TYPE_SOCKET = 6;
84     /** Entry type: FIFO */
85     public static final int TYPE_FIFO = 7;
86     /** Entry type: Other */
87     public static final int TYPE_OTHER = 8;
88 
89     /** Device side file separator. */
90     public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
91 
92     private static final String FILE_ROOT = "/"; //$NON-NLS-1$
93 
94 
95     /**
96      * Regexp pattern to parse the result from ls.
97      */
98     private static Pattern sLsPattern = Pattern.compile(
99         "^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+(\\S+)\\s+(\\S+)\\s+([\\d\\s,]*)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$
100 
101     private Device mDevice;
102     private FileEntry mRoot;
103 
104     private ArrayList<Thread> mThreadList = new ArrayList<Thread>();
105 
106     /**
107      * Represents an entry in a directory. This can be a file or a directory.
108      */
109     public final static class FileEntry {
110         /** Pattern to escape filenames for shell command consumption. */
111         private final static Pattern sEscapePattern = Pattern.compile(
112                 "([\\\\()*+?\"'#/\\s])"); //$NON-NLS-1$
113 
114         /**
115          * Comparator object for FileEntry
116          */
117         private static Comparator<FileEntry> sEntryComparator = new Comparator<FileEntry>() {
118             public int compare(FileEntry o1, FileEntry o2) {
119                 if (o1 instanceof FileEntry && o2 instanceof FileEntry) {
120                     FileEntry fe1 = (FileEntry)o1;
121                     FileEntry fe2 = (FileEntry)o2;
122                     return fe1.name.compareTo(fe2.name);
123                 }
124                 return 0;
125             }
126         };
127 
128         FileEntry parent;
129         String name;
130         String info;
131         String permissions;
132         String size;
133         String date;
134         String time;
135         String owner;
136         String group;
137         int type;
138         boolean isAppPackage;
139 
140         boolean isRoot;
141 
142         /**
143          * Indicates whether the entry content has been fetched yet, or not.
144          */
145         long fetchTime = 0;
146 
147         final ArrayList<FileEntry> mChildren = new ArrayList<FileEntry>();
148 
149         /**
150          * Creates a new file entry.
151          * @param parent parent entry or null if entry is root
152          * @param name name of the entry.
153          * @param type entry type. Can be one of the following: {@link FileListingService#TYPE_FILE},
154          * {@link FileListingService#TYPE_DIRECTORY}, {@link FileListingService#TYPE_OTHER}.
155          */
FileEntry(FileEntry parent, String name, int type, boolean isRoot)156         private FileEntry(FileEntry parent, String name, int type, boolean isRoot) {
157             this.parent = parent;
158             this.name = name;
159             this.type = type;
160             this.isRoot = isRoot;
161 
162             checkAppPackageStatus();
163         }
164 
165         /**
166          * Returns the name of the entry
167          */
getName()168         public String getName() {
169             return name;
170         }
171 
172         /**
173          * Returns the size string of the entry, as returned by <code>ls</code>.
174          */
getSize()175         public String getSize() {
176             return size;
177         }
178 
179         /**
180          * Returns the size of the entry.
181          */
getSizeValue()182         public int getSizeValue() {
183             return Integer.parseInt(size);
184         }
185 
186         /**
187          * Returns the date string of the entry, as returned by <code>ls</code>.
188          */
getDate()189         public String getDate() {
190             return date;
191         }
192 
193         /**
194          * Returns the time string of the entry, as returned by <code>ls</code>.
195          */
getTime()196         public String getTime() {
197             return time;
198         }
199 
200         /**
201          * Returns the permission string of the entry, as returned by <code>ls</code>.
202          */
getPermissions()203         public String getPermissions() {
204             return permissions;
205         }
206 
207         /**
208          * Returns the extra info for the entry.
209          * <p/>For a link, it will be a description of the link.
210          * <p/>For an application apk file it will be the application package as returned
211          * by the Package Manager.
212          */
getInfo()213         public String getInfo() {
214             return info;
215         }
216 
217         /**
218          * Return the full path of the entry.
219          * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator.
220          */
getFullPath()221         public String getFullPath() {
222             if (isRoot) {
223                 return FILE_ROOT;
224             }
225             StringBuilder pathBuilder = new StringBuilder();
226             fillPathBuilder(pathBuilder, false);
227 
228             return pathBuilder.toString();
229         }
230 
231         /**
232          * Return the fully escaped path of the entry. This path is safe to use in a
233          * shell command line.
234          * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator
235          */
getFullEscapedPath()236         public String getFullEscapedPath() {
237             StringBuilder pathBuilder = new StringBuilder();
238             fillPathBuilder(pathBuilder, true);
239 
240             return pathBuilder.toString();
241         }
242 
243         /**
244          * Returns the path as a list of segments.
245          */
getPathSegments()246         public String[] getPathSegments() {
247             ArrayList<String> list = new ArrayList<String>();
248             fillPathSegments(list);
249 
250             return list.toArray(new String[list.size()]);
251         }
252 
253         /**
254          * Returns true if the entry is a directory, false otherwise;
255          */
getType()256         public int getType() {
257             return type;
258         }
259 
260         /**
261          * Returns if the entry is a folder or a link to a folder.
262          */
isDirectory()263         public boolean isDirectory() {
264             return type == TYPE_DIRECTORY || type == TYPE_DIRECTORY_LINK;
265         }
266 
267         /**
268          * Returns the parent entry.
269          */
getParent()270         public FileEntry getParent() {
271             return parent;
272         }
273 
274         /**
275          * Returns the cached children of the entry. This returns the cache created from calling
276          * <code>FileListingService.getChildren()</code>.
277          */
getCachedChildren()278         public FileEntry[] getCachedChildren() {
279             return mChildren.toArray(new FileEntry[mChildren.size()]);
280         }
281 
282         /**
283          * Returns the child {@link FileEntry} matching the name.
284          * This uses the cached children list.
285          * @param name the name of the child to return.
286          * @return the FileEntry matching the name or null.
287          */
findChild(String name)288         public FileEntry findChild(String name) {
289             for (FileEntry entry : mChildren) {
290                 if (entry.name.equals(name)) {
291                     return entry;
292                 }
293             }
294             return null;
295         }
296 
297         /**
298          * Returns whether the entry is the root.
299          */
isRoot()300         public boolean isRoot() {
301             return isRoot;
302         }
303 
addChild(FileEntry child)304         void addChild(FileEntry child) {
305             mChildren.add(child);
306         }
307 
setChildren(ArrayList<FileEntry> newChildren)308         void setChildren(ArrayList<FileEntry> newChildren) {
309             mChildren.clear();
310             mChildren.addAll(newChildren);
311         }
312 
needFetch()313         boolean needFetch() {
314             if (fetchTime == 0) {
315                 return true;
316             }
317             long current = System.currentTimeMillis();
318             if (current-fetchTime > REFRESH_TEST) {
319                 return true;
320             }
321 
322             return false;
323         }
324 
325         /**
326          * Returns if the entry is a valid application package.
327          */
isApplicationPackage()328         public boolean isApplicationPackage() {
329             return isAppPackage;
330         }
331 
332         /**
333          * Returns if the file name is an application package name.
334          */
isAppFileName()335         public boolean isAppFileName() {
336             Matcher m = sApkPattern.matcher(name);
337             return m.matches();
338         }
339 
340         /**
341          * Recursively fills the pathBuilder with the full path
342          * @param pathBuilder a StringBuilder used to create the path.
343          * @param escapePath Whether the path need to be escaped for consumption by
344          * a shell command line.
345          */
fillPathBuilder(StringBuilder pathBuilder, boolean escapePath)346         protected void fillPathBuilder(StringBuilder pathBuilder, boolean escapePath) {
347             if (isRoot) {
348                 return;
349             }
350 
351             if (parent != null) {
352                 parent.fillPathBuilder(pathBuilder, escapePath);
353             }
354             pathBuilder.append(FILE_SEPARATOR);
355             pathBuilder.append(escapePath ? escape(name) : name);
356         }
357 
358         /**
359          * Recursively fills the segment list with the full path.
360          * @param list The list of segments to fill.
361          */
fillPathSegments(ArrayList<String> list)362         protected void fillPathSegments(ArrayList<String> list) {
363             if (isRoot) {
364                 return;
365             }
366 
367             if (parent != null) {
368                 parent.fillPathSegments(list);
369             }
370 
371             list.add(name);
372         }
373 
374         /**
375          * Sets the internal app package status flag. This checks whether the entry is in an app
376          * directory like /data/app or /system/app
377          */
checkAppPackageStatus()378         private void checkAppPackageStatus() {
379             isAppPackage = false;
380 
381             String[] segments = getPathSegments();
382             if (type == TYPE_FILE && segments.length == 3 && isAppFileName()) {
383                 isAppPackage = DIRECTORY_APP.equals(segments[1]) &&
384                     (DIRECTORY_SYSTEM.equals(segments[0]) || DIRECTORY_DATA.equals(segments[0]));
385             }
386         }
387 
388         /**
389          * Returns an escaped version of the entry name.
390          * @param entryName
391          */
escape(String entryName)392         private String escape(String entryName) {
393             return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$
394         }
395     }
396 
397     private class LsReceiver extends MultiLineReceiver {
398 
399         private ArrayList<FileEntry> mEntryList;
400         private ArrayList<String> mLinkList;
401         private FileEntry[] mCurrentChildren;
402         private FileEntry mParentEntry;
403 
404         /**
405          * Create an ls receiver/parser.
406          * @param currentChildren The list of current children. To prevent
407          *      collapse during update, reusing the same FileEntry objects for
408          *      files that were already there is paramount.
409          * @param entryList the list of new children to be filled by the
410          *      receiver.
411          * @param linkList the list of link path to compute post ls, to figure
412          *      out if the link pointed to a file or to a directory.
413          */
LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList, ArrayList<String> linkList)414         public LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList,
415                 ArrayList<String> linkList) {
416             mParentEntry = parentEntry;
417             mCurrentChildren = parentEntry.getCachedChildren();
418             mEntryList = entryList;
419             mLinkList = linkList;
420         }
421 
422         @Override
processNewLines(String[] lines)423         public void processNewLines(String[] lines) {
424             for (String line : lines) {
425                 // no need to handle empty lines.
426                 if (line.length() == 0) {
427                     continue;
428                 }
429 
430                 // run the line through the regexp
431                 Matcher m = sLsPattern.matcher(line);
432                 if (m.matches() == false) {
433                     continue;
434                 }
435 
436                 // get the name
437                 String name = m.group(7);
438 
439                 // if the parent is root, we only accept selected items
440                 if (mParentEntry.isRoot()) {
441                     boolean found = false;
442                     for (String approved : sRootLevelApprovedItems) {
443                         if (approved.equals(name)) {
444                             found = true;
445                             break;
446                         }
447                     }
448 
449                     // if it's not in the approved list we skip this entry.
450                     if (found == false) {
451                         continue;
452                     }
453                 }
454 
455                 // get the rest of the groups
456                 String permissions = m.group(1);
457                 String owner = m.group(2);
458                 String group = m.group(3);
459                 String size = m.group(4);
460                 String date = m.group(5);
461                 String time = m.group(6);
462                 String info = null;
463 
464                 // and the type
465                 int objectType = TYPE_OTHER;
466                 switch (permissions.charAt(0)) {
467                     case '-' :
468                         objectType = TYPE_FILE;
469                         break;
470                     case 'b' :
471                         objectType = TYPE_BLOCK;
472                         break;
473                     case 'c' :
474                         objectType = TYPE_CHARACTER;
475                         break;
476                     case 'd' :
477                         objectType = TYPE_DIRECTORY;
478                         break;
479                     case 'l' :
480                         objectType = TYPE_LINK;
481                         break;
482                     case 's' :
483                         objectType = TYPE_SOCKET;
484                         break;
485                     case 'p' :
486                         objectType = TYPE_FIFO;
487                         break;
488                 }
489 
490 
491                 // now check what we may be linking to
492                 if (objectType == TYPE_LINK) {
493                     String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
494 
495                     // we should have 2 segments
496                     if (segments.length == 2) {
497                         // update the entry name to not contain the link
498                         name = segments[0];
499 
500                         // and the link name
501                         info = segments[1];
502 
503                         // now get the path to the link
504                         String[] pathSegments = info.split(FILE_SEPARATOR);
505                         if (pathSegments.length == 1) {
506                             // the link is to something in the same directory,
507                             // unless the link is ..
508                             if ("..".equals(pathSegments[0])) { //$NON-NLS-1$
509                                 // set the type and we're done.
510                                 objectType = TYPE_DIRECTORY_LINK;
511                             } else {
512                                 // either we found the object already
513                                 // or we'll find it later.
514                             }
515                         }
516                     }
517 
518                     // add an arrow in front to specify it's a link.
519                     info = "-> " + info; //$NON-NLS-1$;
520                 }
521 
522                 // get the entry, either from an existing one, or a new one
523                 FileEntry entry = getExistingEntry(name);
524                 if (entry == null) {
525                     entry = new FileEntry(mParentEntry, name, objectType, false /* isRoot */);
526                 }
527 
528                 // add some misc info
529                 entry.permissions = permissions;
530                 entry.size = size;
531                 entry.date = date;
532                 entry.time = time;
533                 entry.owner = owner;
534                 entry.group = group;
535                 if (objectType == TYPE_LINK) {
536                     entry.info = info;
537                 }
538 
539                 mEntryList.add(entry);
540             }
541         }
542 
543         /**
544          * Queries for an already existing Entry per name
545          * @param name the name of the entry
546          * @return the existing FileEntry or null if no entry with a matching
547          * name exists.
548          */
getExistingEntry(String name)549         private FileEntry getExistingEntry(String name) {
550             for (int i = 0 ; i < mCurrentChildren.length; i++) {
551                 FileEntry e = mCurrentChildren[i];
552 
553                 // since we're going to "erase" the one we use, we need to
554                 // check that the item is not null.
555                 if (e != null) {
556                     // compare per name, case-sensitive.
557                     if (name.equals(e.name)) {
558                         // erase from the list
559                         mCurrentChildren[i] = null;
560 
561                         // and return the object
562                         return e;
563                     }
564                 }
565             }
566 
567             // couldn't find any matching object, return null
568             return null;
569         }
570 
isCancelled()571         public boolean isCancelled() {
572             return false;
573         }
574 
finishLinks()575         public void finishLinks() {
576             // TODO Handle links in the listing service
577         }
578     }
579 
580     /**
581      * Classes which implement this interface provide a method that deals with asynchronous
582      * result from <code>ls</code> command on the device.
583      *
584      * @see FileListingService#getChildren(com.android.ddmlib.FileListingService.FileEntry, boolean, com.android.ddmlib.FileListingService.IListingReceiver)
585      */
586     public interface IListingReceiver {
setChildren(FileEntry entry, FileEntry[] children)587         public void setChildren(FileEntry entry, FileEntry[] children);
588 
refreshEntry(FileEntry entry)589         public void refreshEntry(FileEntry entry);
590     }
591 
592     /**
593      * Creates a File Listing Service for a specified {@link Device}.
594      * @param device The Device the service is connected to.
595      */
FileListingService(Device device)596     FileListingService(Device device) {
597         mDevice = device;
598     }
599 
600     /**
601      * Returns the root element.
602      * @return the {@link FileEntry} object representing the root element or
603      * <code>null</code> if the device is invalid.
604      */
getRoot()605     public FileEntry getRoot() {
606         if (mDevice != null) {
607             if (mRoot == null) {
608                 mRoot = new FileEntry(null /* parent */, "" /* name */, TYPE_DIRECTORY,
609                         true /* isRoot */);
610             }
611 
612             return mRoot;
613         }
614 
615         return null;
616     }
617 
618     /**
619      * Returns the children of a {@link FileEntry}.
620      * <p/>
621      * This method supports a cache mechanism and synchronous and asynchronous modes.
622      * <p/>
623      * If <var>receiver</var> is <code>null</code>, the device side <code>ls</code>
624      * command is done synchronously, and the method will return upon completion of the command.<br>
625      * If <var>receiver</var> is non <code>null</code>, the command is launched is a separate
626      * thread and upon completion, the receiver will be notified of the result.
627      * <p/>
628      * The result for each <code>ls</code> command is cached in the parent
629      * <code>FileEntry</code>. <var>useCache</var> allows usage of this cache, but only if the
630      * cache is valid. The cache is valid only for {@link FileListingService#REFRESH_RATE} ms.
631      * After that a new <code>ls</code> command is always executed.
632      * <p/>
633      * If the cache is valid and <code>useCache == true</code>, the method will always simply
634      * return the value of the cache, whether a {@link IListingReceiver} has been provided or not.
635      *
636      * @param entry The parent entry.
637      * @param useCache A flag to use the cache or to force a new ls command.
638      * @param receiver A receiver for asynchronous calls.
639      * @return The list of children or <code>null</code> for asynchronous calls.
640      *
641      * @see FileEntry#getCachedChildren()
642      */
getChildren(final FileEntry entry, boolean useCache, final IListingReceiver receiver)643     public FileEntry[] getChildren(final FileEntry entry, boolean useCache,
644             final IListingReceiver receiver) {
645         // first thing we do is check the cache, and if we already have a recent
646         // enough children list, we just return that.
647         if (useCache && entry.needFetch() == false) {
648             return entry.getCachedChildren();
649         }
650 
651         // if there's no receiver, then this is a synchronous call, and we
652         // return the result of ls
653         if (receiver == null) {
654             doLs(entry);
655             return entry.getCachedChildren();
656         }
657 
658         // this is a asynchronous call.
659         // we launch a thread that will do ls and give the listing
660         // to the receiver
661         Thread t = new Thread("ls " + entry.getFullPath()) { //$NON-NLS-1$
662             @Override
663             public void run() {
664                 doLs(entry);
665 
666                 receiver.setChildren(entry, entry.getCachedChildren());
667 
668                 final FileEntry[] children = entry.getCachedChildren();
669                 if (children.length > 0 && children[0].isApplicationPackage()) {
670                     final HashMap<String, FileEntry> map = new HashMap<String, FileEntry>();
671 
672                     for (FileEntry child : children) {
673                         String path = child.getFullPath();
674                         map.put(path, child);
675                     }
676 
677                     // call pm.
678                     String command = PM_FULL_LISTING;
679                     try {
680                         mDevice.executeShellCommand(command, new MultiLineReceiver() {
681                             @Override
682                             public void processNewLines(String[] lines) {
683                                 for (String line : lines) {
684                                     if (line.length() > 0) {
685                                         // get the filepath and package from the line
686                                         Matcher m = sPmPattern.matcher(line);
687                                         if (m.matches()) {
688                                             // get the children with that path
689                                             FileEntry entry = map.get(m.group(1));
690                                             if (entry != null) {
691                                                 entry.info = m.group(2);
692                                                 receiver.refreshEntry(entry);
693                                             }
694                                         }
695                                     }
696                                 }
697                             }
698                             public boolean isCancelled() {
699                                 return false;
700                             }
701                         });
702                     } catch (Exception e) {
703                         // adb failed somehow, we do nothing.
704                     }
705                 }
706 
707 
708                 // if another thread is pending, launch it
709                 synchronized (mThreadList) {
710                     // first remove ourselves from the list
711                     mThreadList.remove(this);
712 
713                     // then launch the next one if applicable.
714                     if (mThreadList.size() > 0) {
715                         Thread t = mThreadList.get(0);
716                         t.start();
717                     }
718                 }
719             }
720         };
721 
722         // we don't want to run multiple ls on the device at the same time, so we
723         // store the thread in a list and launch it only if there's no other thread running.
724         // the thread will launch the next one once it's done.
725         synchronized (mThreadList) {
726             // add to the list
727             mThreadList.add(t);
728 
729             // if it's the only one, launch it.
730             if (mThreadList.size() == 1) {
731                 t.start();
732             }
733         }
734 
735         // and we return null.
736         return null;
737     }
738 
doLs(FileEntry entry)739     private void doLs(FileEntry entry) {
740         // create a list that will receive the list of the entries
741         ArrayList<FileEntry> entryList = new ArrayList<FileEntry>();
742 
743         // create a list that will receive the link to compute post ls;
744         ArrayList<String> linkList = new ArrayList<String>();
745 
746         try {
747             // create the command
748             String command = "ls -l " + entry.getFullPath(); //$NON-NLS-1$
749 
750             // create the receiver object that will parse the result from ls
751             LsReceiver receiver = new LsReceiver(entry, entryList, linkList);
752 
753             // call ls.
754             mDevice.executeShellCommand(command, receiver);
755 
756             // finish the process of the receiver to handle links
757             receiver.finishLinks();
758         } catch (Exception e) {
759             // catch all and do nothing.
760         }
761 
762 
763         // at this point we need to refresh the viewer
764         entry.fetchTime = System.currentTimeMillis();
765 
766         // sort the children and set them as the new children
767         Collections.sort(entryList, FileEntry.sEntryComparator);
768         entry.setChildren(entryList);
769     }
770 }
771