org.lnicholls.galleon.util.FileGatherer.java Source code

Java tutorial

Introduction

Here is the source code for org.lnicholls.galleon.util.FileGatherer.java

Source

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;
    }
}