Java tutorial
package org.lnicholls.galleon.util; /* * Copyright (C) 2003 Mike Kelley * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * See the file "COPYING" for more details. */ import java.io.*; import java.io.FileFilter; import java.util.ArrayList; import net.jimmc.jshortcut.JShellLink; import org.apache.commons.lang.SystemUtils; import org.apache.log4j.Logger; // TODO Keep shorcut names /* * Design goals: - Recursively scan a folder and subfolders to find all media files of a certain type, e.g. audio, * image. - Correctly handle Windows shortcuts, i.e. dereference them - Correctly handle playlists, i.e. transform them * to lists of files as if they were a folder - Detect folder recursion loops generated by shortcuts that point to a * parent folder and stop recursion - High performance: could be dealing with 100s of folders and 1000s of files, so * speed and memory usage are important - Enable sorting or shuffle of the resulting list in several manners: - * Randomize entire list of files (per TiVo HMO spec) Question: Do we want to remove duplicate media files? - Randomize * folders but keep files within folders in order - Randomize playlists but keep files within playlists in order */ public class FileGatherer { private static final Logger log = Logger.getLogger(FileGatherer.class.getName()); // Various static constants protected static final int INIT_LIST_SIZE = 100; protected static final int MAX_RECURSION_DEPTH = 20; public static interface GathererCallback { public void visit(File file, File originalFile); } // Dereference a Windows file shortcut. Returns null on error public static final File followLink(File file) { if (SystemUtils.IS_OS_WINDOWS) { if (!file.exists()) { if (log.isDebugEnabled()) log.debug("followLink() does not exist: " + file.getAbsolutePath()); return null; } try { JShellLink windowsShortcut = new JShellLink(file.getParent(), file.getName().substring(0, file.getName().lastIndexOf("."))); windowsShortcut.load(); file = new File(windowsShortcut.getPath()); return file; } catch (Exception ex) { Tools.logException(FileGatherer.class, ex, file.getAbsolutePath()); return null; } } return file; } // Check a file to see if it is a Windows shortcut. If not, return // the file, if it is, dereference the link and return the file. // If the link references another link, dereference recrusively. // // TODO Returns null if there was an exception. Because of recursion // this would crash with null pointer; would it be better to pass the // exception up? public static final File resolveLink(File file) { // Is this a Windows shortcut? if (SystemUtils.IS_OS_WINDOWS) { if (FileFilters.linkFilter.accept(file)) { return resolveLink(followLink(file)); } else { return file; } } return file; } // For each recurser instance, track how deep we are to avoid loops. TODO mwk88 // probably not the most elegant solution. public static final void gatherDirectory(File directory, FileFilter suffixFilter, boolean recursive, GathererCallback callback) { if (directory != null && directory.exists()) gatherDirectoryFromFileSystem(directory, suffixFilter, recursive, 0, callback); } // Gather all files in a directory. If recursive==true, recursively scan // subdirectories as well. private static final void gatherDirectoryFromFileSystem(File directory, FileFilter suffixFilter, boolean recursive, int recursionDepth, GathererCallback callback) { // Limit recursion in case there is a recursive shortcut if (recursionDepth >= MAX_RECURSION_DEPTH) { log.info("Max recursion depth reached: " + directory.getPath()); return; } ++recursionDepth; // Get the list of all files in this directory. These may be media files, // directories, playlists, Windows shortcuts, or other unrecognized file // that should be ignored. We do not filter at this stage because we need // to apply several different filters later and we don't want to repeat // the listFiles() call for performance reasons. File[] directories = directory.listFiles(FileFilters.directoryFilter); if (directories != null) { if (recursive && directories.length > 0) { for (int i = 0; i < directories.length; ++i) { File dir = resolveLink(directories[i]); if (dir != null) // broken shortcut { gatherDirectoryFromFileSystem(dir, suffixFilter, true, recursionDepth, callback); } } } } directories = null; File[] files = directory.listFiles(); if (files != null) { // Iterate through the list of files, taking the correct action by type. for (int i = 0; i < files.length; ++i) { // Before applying file filters, dereference shortcuts (if any) File file = resolveLink(files[i]); if (file != null) // broken shortcut { if (suffixFilter.accept(file)) try { callback.visit(file, files[i]); } catch (Throwable ex) { } } } } files = null; // Decrement recursion depth --recursionDepth; } }